From 2b267bdcdd6fe30b152dbc87f8c86c2fd0a13254 Mon Sep 17 00:00:00 2001
From: Sebastien Robin <seb@nexedi.com>
Date: Thu, 30 Sep 2004 08:35:31 +0000
Subject: [PATCH] it has been integrated

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@1806 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 .../ERP5/Tool/SimulationTool.py.gm_proposal   | 1620 -----------------
 1 file changed, 1620 deletions(-)
 delete mode 100755 product/ERP5/Tool/SimulationTool.py.gm_proposal

diff --git a/product/ERP5/Tool/SimulationTool.py.gm_proposal b/product/ERP5/Tool/SimulationTool.py.gm_proposal
deleted file mode 100755
index 66bdcb2fcb..0000000000
--- a/product/ERP5/Tool/SimulationTool.py.gm_proposal
+++ /dev/null
@@ -1,1620 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
-#                    Jean-Paul Smets-Solanes <jp@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 UniqueObject
-
-from AccessControl import ClassSecurityInfo
-from Globals import InitializeClass, DTMLFile
-from Products.ERP5Type.Document.Folder import Folder
-from Products.ERP5Type import Permissions
-from Products.ERP5.ERP5Globals import default_section_category, order_type_list, delivery_type_list, current_inventory_state_list, discount_type_list, simulated_movement_type_list, container_type_list, payment_condition_type_list, invoice_movement_type_list
-
-from Products.ERP5 import _dtmldir
-
-from zLOG import LOG
-
-from Products.ERP5.Capacity.GLPK import solve
-from Numeric import zeros, resize
-
-# Solver Registration
-is_initialized = 0
-delivery_solver_dict = {}
-delivery_solver_list = []
-
-def registerDeliverySolver(solver):
-    global delivery_solver_list, delivery_solver_dict
-    #LOG('Register Solver', 0, str(solver.__name__))
-    delivery_solver_list.append(solver)
-    delivery_solver_dict[solver.__name__] = solver
-
-target_solver_dict = {}
-target_solver_list = []
-
-def registerTargetSolver(solver):
-    global target_solver_list, target_solver_dict
-    #LOG('Register Solver', 0, str(solver.__name__))
-    target_solver_list.append(solver)
-    target_solver_dict[solver.__name__] = solver
-
-class Target:
-
-  def __init__(self, **kw):
-    """
-      Defines a target (target_quantity, start_date, stop_date)
-    """
-    self.__dict__.update(kw)
-
-class SimulationTool (Folder, UniqueObject):
-    """
-    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
-    #
-    manage_options = ( ( { 'label'      : 'Overview'
-                         , 'action'     : 'manage_overview'
-                         }
-                        ,
-                        )
-                     + Folder.manage_options
-                     )
-
-    security.declareProtected( Permissions.ManagePortal, 'manage_overview' )
-    manage_overview = DTMLFile( 'explainSimulationTool', _dtmldir )
-
-    # Filter content (ZMI))
-    def __init__(self):
-        return Folder.__init__(self, SimulationTool.id)
-
-    # Filter content (ZMI))
-    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 initialize(self):
-      """
-        Update values of simulation movements based on delivery
-        target values and solver
-      """
-      from Products.ERP5.TargetSolver import Reduce, Defer, SplitAndDefer, CopyToTarget
-      from Products.ERP5.DeliverySolver import Distribute, Copy
-
-    def isInitialized(self):
-      global is_initialized
-      return is_initialized
-
-    def newDeliverySolver(self, solver_id, *args, **kw):
-      """
-        Returns a solver instance
-      """
-      if not self.isInitialized(): self.initialize()
-      solver = delivery_solver_dict[solver_id](self, *args, **kw)
-      return solver
-
-    def applyDeliverySolver(self, movement, solver):
-      """
-        Update values of simulation movements based on delivery
-        target values and solver.
-
-        movement  --  a delivery line or cell
-
-        solver    --  a delivery solver
-      """
-      if not self.isInitialized(): self.initialize()
-      solver.solve(movement)
-
-    def newTargetSolver(self, solver_id, *args, **kw):
-      """
-        Returns a solver instance
-      """
-      if not self.isInitialized(): self.initialize()
-      solver = target_solver_dict[solver_id](self, *args, **kw)
-      return solver
-
-    def applyTargetSolver(self, movement, solver, new_target=None):
-      """
-        Update upper targets based on new targets
-
-        movement  --  a simulation movement
-
-        solver    --  a target solver
-
-        new_target--  new target values for that movement
-      """
-      if new_target is None:
-        # Default behaviour is to solve target based on
-        # target defined by Delivery
-        # it must be overriden in recursive upward update
-        # to make sure
-        new_target = Target(target_quantity = movement.getQuantity(),
-                            target_start_date = movement.getStartDate(),
-                            target_stop_date = movement.getStopDate())
-      if not self.isInitialized(): self.initialize()
-      solver.solve(movement, new_target)
-
-    def closeTargetSolver(self, solver):
-      return solver.close()
-
-    def showTargetSolver(self, solver):
-      #LOG("SimulationTool",0,"in showTargetSolver")
-      return str(solver.__dict__)
-
-
-    #######################################################
-    # Stock Management
-    security.declareProtected(Permissions.AccessContentsInformation, 'getInventory')
-    def getInventory(self, resource_uid=None, at_date = None, section = None, node = None,
-            node_category=None, section_category=default_section_category, simulation_state=None,
-            ignore_variation=0, **kw):
-      result = self.Resource_zGetInventory(resource_uid = resource_uid,
-                                           to_date=at_date,
-                                           section=section, node=node,
-                                           node_category=node_category,
-                                           section_category=section_category)
-      if len(result) > 0:
-        return result[0].inventory
-      return 0.0
-
-    #######################################################
-    # Movement Group Collection / Delivery Creation
-    def collectMovement(self, movement_list,
-                        check_order = 1, check_path = 1, check_date = 1, check_criterion = 0,
-                        check_resource = 1, check_base_variant = 0, check_variant = 1):
-      current_order = 2
-      check_list = [check_order, check_path, check_date, check_criterion, check_resource, check_base_variant, check_variant]
-      for i in range(len(check_list)):
-        if check_list[i]:
-          check_list[i] = current_order
-          current_order += 1
-      check_order = check_list[0]
-      check_path = check_list[1]
-      check_date = check_list[2]
-      check_criterion = check_list[3]
-      check_resource = check_list[4]
-      check_base_variant = check_list[5]
-      check_variant = check_list[6]
-    
-      return orderedCollectMovement(movement_list=movement_list,
-                                    check_order=check_order,
-                                    check_path=check_path,
-                                    check_date=check_date,
-                                    check_criterion=check_criterion,
-                                    check_resource=check_resource,
-                                    check_base_variant=check_base_variant,
-                                    check_variant=check_variant)
-    
-                                    
-    def orderedCollectMovement(self, movement_list,
-                        check_order = 2, check_path = 3, check_date = 4, check_criterion = 0,
-                        check_resource = 5, check_base_variant = 0, check_variant = 6):
-      LOG('orderedCollectMovement :', 0, 'movement_list = %s' % repr(movement_list))
-
-      class RootGroup:
-
-        def getNestedClass(self, class_list):
-          for a in class_list:
-            if a[0]:
-              return a[1]
-          return None
-
-        def setNestedClass(self):
-          """
-            This sets an appropriate nested class.
-          """
-          def cmpfunc(a,b):
-            return cmp(a[0],b[0])
-          
-          class_list = [(1, RootGroup),
-                        (check_order, OrderGroup),
-                        (check_path, PathGroup),
-                        (check_date, DateGroup),
-                        (check_criterion, CriterionGroup),
-                        (check_resource, ResourceGroup),
-                        (check_base_variant, BaseVariantGroup),
-                        (check_variant, VariantGroup),
-                        ]
-          class_list.sort(cmpfunc)
-          for i in range(len(class_list)):
-            if class_list[i][1] == self.__class__:
-              break
-          else:
-            raise RuntimeError, "no appropriate nested class is found for %s" % str(self)
-
-          self.nested_class = self.getNestedClass(class_list[i+1:])
-
-        def __init__(self, movement=None):
-          self.nested_class = None
-          self.setNestedClass()
-          self.movement_list = []
-          self.group_list = []
-          if movement is not None :
-            self.append(movement)
-
-        def appendGroup(self, movement):
-          if self.nested_class is not None:
-            self.group_list.append(self.nested_class(movement))
-
-        def append(self,movement):
-          self.movement_list.append(movement)
-          movement_in_group = 0
-          for group in self.group_list :
-            if group.test(movement) :
-              group.append(movement)
-              movement_in_group = 1
-              break
-          if movement_in_group == 0 :
-            self.appendGroup(movement)
-
-      class OrderGroup(RootGroup):
-
-        def __init__(self,movement):
-          RootGroup.__init__(self,movement)
-          if hasattr(movement, 'getRootAppliedRule'):
-            # This is a simulation movement
-            order_value = movement.getRootAppliedRule().getCausalityValue(
-                                                      portal_type=order_type_list)
-            if order_value is None:
-              # In some cases (ex. DeliveryRule), there is no order
-              # we may consider a PackingList as the order in the OrderGroup
-              order_value = movement.getRootAppliedRule().getCausalityValue(
-                              portal_type=delivery_type_list)
-          else:
-            # This is a temp movement
-            order_value = None
-          if order_value is None:
-            order_relative_url = None
-          else:
-            # get the id of the enclosing delivery
-            # for this cell or line
-            order_relative_url = order_value.getRelativeUrl()
-          self.order = order_relative_url
-
-        def test(self,movement):
-          if hasattr(movement, 'getRootAppliedRule'):
-            order_value = movement.getRootAppliedRule().getCausalityValue(
-                                                        portal_type=order_type_list)
-
-            if order_value is None:
-              # In some cases (ex. DeliveryRule), there is no order
-              # we may consider a PackingList as the order in the OrderGroup
-              order_value = movement.getRootAppliedRule().getCausalityValue(
-                              portal_type=delivery_type_list)
-          else:
-            # This is a temp movement
-            order_value = None
-          if order_value is None:
-            order_relative_url = None
-          else:
-            # get the id of the enclosing delivery
-            # for this cell or line
-            order_relative_url = order_value.getRelativeUrl()
-          if order_relative_url == self.order:
-            return 1
-          else :
-            return 0
-
-      class PathGroup(RootGroup):
-
-        def __init__(self,movement):
-          RootGroup.__init__(self,movement)
-          self.source = movement.getSource()
-          self.destination = movement.getDestination()
-          self.source_section = movement.getSourceSection()
-          self.destination_section = movement.getDestinationSection()
-
-        def test(self,movement):
-          if movement.getSource() == self.source and \
-            movement.getDestination() == self.destination and \
-            movement.getSourceSection() == self.source_section and \
-            movement.getDestinationSection() == self.destination_section :
-            return 1
-          else :
-            return 0
-
-      class DateGroup(RootGroup):
-
-        def __init__(self,movement):
-          RootGroup.__init__(self,movement)
-          self.target_start_date = movement.getTargetStartDate()
-          self.target_stop_date = movement.getTargetStopDate()
-          self.start_date = movement.getStartDate()
-          self.stop_date = movement.getStopDate()
-
-        def test(self,movement):
-          if movement.getStartDate() == self.start_date and \
-            movement.getStopDate() == self.stop_date :
-            return 1
-          else :
-            return 0
-
-      class CriterionGroup(RootGroup):
-
-        def __init__(self,movement):
-          RootGroup.__init__(self,movement)
-          if hasattr(movement, 'getGroupCriterion'):
-            self.criterion = movement.getGroupCriterion()
-          else:
-            self.criterion = None
-
-        def test(self,movement):
-          # we must have the same criterion
-          if hasattr(movement, 'getGroupCriterion'):
-            criterion = movement.getGroupCriterion()
-          else:
-            criterion = None
-          return self.criterion == criterion
-
-      class ResourceGroup(RootGroup):
-
-        def __init__(self,movement):
-          RootGroup.__init__(self,movement)
-          self.resource = movement.getResource()
-
-        def test(self,movement):
-          if movement.getResource() == self.resource :
-            return 1
-          else :
-            return 0
-
-      class BaseVariantGroup(RootGroup):
-
-        def __init__(self,movement):
-          RootGroup.__init__(self,movement)
-          self.base_category_list = movement.getVariationBaseCategoryList()
-          if self.base_category_list is None:
-            LOG('BaseVariantGroup __init__', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict())))
-            self.base_category_list = []
-
-        def test(self,movement):
-          # we must have the same number of categories
-          categories_identity = 0
-          #LOG('BaseVariantGroup', 0, 'self.base_category_list = %s, movement = %s, movement.getVariationBaseCategoryList() = %s' % (repr(self.base_category_list), repr(movement), repr(movement.getVariationBaseCategoryList())))
-          movement_base_category_list = movement.getVariationBaseCategoryList()
-          if movement_base_category_list is None:
-            LOG('BaseVariantGroup test', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict())))
-            movement_base_category_list = []
-          if len(self.base_category_list) == len(movement_base_category_list):
-            for category in movement_base_category_list:
-              if not category in self.base_category_list :
-                break
-            else :
-              categories_identity = 1
-          return categories_identity
-
-      class VariantGroup(RootGroup):
-
-        def __init__(self,movement):
-          RootGroup.__init__(self,movement)
-          self.category_list = movement.getVariationCategoryList()
-          if self.category_list is None:
-            LOG('VariantGroup __init__', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict())))
-            self.category_list = []
-
-        def test(self,movement):
-          # we must have the same number of categories
-          categories_identity = 0
-          movement_category_list = movement.getVariationCategoryList()
-          if movement_category_list is None:
-            LOG('VariantGroup test', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict())))
-            movement_category_list = []
-          if len(self.category_list) == len(movement_category_list):
-            for category in movement_category_list:
-              if not category in self.category_list :
-                break
-            else :
-              categories_identity = 1
-          return categories_identity
-
-      my_root_group = RootGroup()
-      for movement in movement_list :
-        if not movement in my_root_group.movement_list :
-          my_root_group.append(movement)
-
-      return my_root_group
-
-    def buildOrderList(self, movement_group):
-      # Build orders from a list of movements (attached to orders)
-      order_list = []
-
-      if movement_group is not None:
-        for order_group in movement_group.group_list:
-          if order_group.order is None:
-            # Only build if there is not order yet
-            for path_group in order_group.group_list :
-              if path_group.destination.find('site/Stock_PF') >=0 :
-                # Build a Production Order
-                delivery_module = self.ordre_fabrication
-                delivery_type = 'Production Order'
-                delivery_line_type = delivery_type + ' Line'
-                delivery_cell_type = 'Delivery Cell'
-              else:
-                # Build a Purchase Order
-                delivery_module = self.commande_achat
-                delivery_type = 'Purchase Order'
-                delivery_line_type = delivery_type + ' Line'
-                delivery_cell_type = 'Delivery Cell'
-              # we create a new delivery for each DateGroup
-              for date_group in path_group.group_list :
-
-                for resource_group in date_group.group_list :
-
-                  # Create a new production Order for each resource (Modele)
-                  modele_url_items = resource_group.resource.split('/')
-                  modele_id = modele_url_items[len(modele_url_items)-1]
-                  try :
-                    modele_object = self.getPortalObject().modele[modele_id]
-                  except :
-                    modele_object = None
-                  if modele_object is not None :
-                    of_description = modele_id + ' ' + modele_object.getDefaultDestinationTitle('')
-                  else :
-                    of_description = modele_id
-
-                  new_delivery_id = str(delivery_module.generateNewId())
-                  self.portal_types.constructContent(type_name = delivery_type,
-                                                      container = delivery_module,
-                                                      id = new_delivery_id,
-                                                      target_start_date = date_group.start_date,
-                                                      target_stop_date = date_group.stop_date,
-                                                      start_date = date_group.start_date,
-                                                      stop_date = date_group.stop_date,
-                                                      source = path_group.source,
-                                                      destination = path_group.destination,
-                                                      source_section = path_group.source_section,
-                                                      destination_section = path_group.destination_section,
-                                                      description = of_description,
-                                                      title = new_delivery_id
-                                                    )
-                  delivery = delivery_module[new_delivery_id]
-                  # the new delivery is added to the order_list
-                  order_list.append(delivery)
-
-                  # Create each delivery_line in the new delivery
-
-                  new_delivery_line_id = str(delivery.generateNewId())
-                  self.portal_types.constructContent(type_name = delivery_line_type,
-                  container = delivery,
-                  id = new_delivery_line_id,
-                  resource = resource_group.resource,
-                  )
-                  delivery_line = delivery[new_delivery_line_id]
-
-                  line_variation_category_list = []
-                  line_variation_base_category_dict = {}
-
-                  # compute line_variation_base_category_list and
-                  # line_variation_category_list for new delivery_line
-                  for variant_group in resource_group.group_list :
-                    for variation_item in variant_group.category_list :
-                      if not variation_item in line_variation_category_list :
-                        line_variation_category_list.append(variation_item)
-                        variation_base_category_items = variation_item.split('/')
-                        if len(variation_base_category_items) > 0 :
-                          line_variation_base_category_dict[variation_base_category_items[0]] = 1
-
-                  # update variation_base_category_list and line_variation_category_list for delivery_line
-                  line_variation_base_category_list = line_variation_base_category_dict.keys()
-                  delivery_line.setVariationBaseCategoryList(line_variation_base_category_list)
-                  delivery_line.setVariationCategoryList(line_variation_category_list)
-
-                  # IMPORTANT : delivery cells are automatically created during setVariationCategoryList
-
-                  # update target_quantity for each delivery_cell
-                  for variant_group in resource_group.group_list :
-                    #LOG('Variant_group examin',0,str(variant_group.category_list))
-                    object_to_update = None
-                    # if there is no variation of the resource, update delivery_line with quantities and price
-                    if len(variant_group.category_list) == 0 :
-                      object_to_update = delivery_line
-                    # else find which delivery_cell is represented by variant_group
-                    else :
-                      categories_identity = 0
-                      #LOG('Before Check cell',0,str(delivery_cell_type))
-                      #LOG('Before Check cell',0,str(delivery_line.contentValues()))
-                      for delivery_cell in delivery_line.contentValues(filter={'portal_type':'Delivery Cell'}) :
-                        #LOG('Check cell',0,str(delivery_cell))
-                        #LOG('Check cell',0,str(variant_group.category_list))
-                        #LOG('Check cell',0,str(delivery_cell.getVariationCategoryList()))
-                        if len(variant_group.category_list) == len(delivery_cell.getVariationCategoryList()) :
-                          #LOG('Parse category',0,str(delivery_cell.getVariationCategoryList()))
-                          for category in delivery_cell.getVariationCategoryList() :
-                            if not category in variant_group.category_list :
-                              #LOG('Not found category',0,str(category))
-                              break
-                          else :
-                            categories_identity = 1
-
-                        if categories_identity :
-                          object_to_update = delivery_cell
-                          break
-
-                    # compute target_quantity, quantity and price for delivery_cell or delivery_line and
-                    # build relation between simulation_movement and delivery_cell or delivery_line
-                    if object_to_update is not None :
-                      cell_target_quantity = 0
-                      for movement in variant_group.movement_list :
-                        cell_target_quantity += movement.getConvertedTargetQuantity()
-                      # We do not create a relation or modifu anything
-                      # since planification of this movement will create new applied rule
-                      object_to_update.edit(target_quantity = cell_target_quantity,
-                                            quantity = cell_target_quantity,
-                                            force_update = 1)
-
-      return order_list
-
-                      
-                      
-                      
-                      
-    def buildDeliveryList(self, movement_group):
-      # Build deliveries from a list of movements
-      
-      
-      def orderGroupProcessing(order_group, delivery_list, reindexable_movement_list, **kw):
-        
-        # Order should never be None
-        LOG("buildDeliveryList", 0, str(order_group.__dict__))
-        if order_group.order is not None:
-          order = self.portal_categories.resolveCategory(order_group.order)
-          if order is not None:
-            # define some variables
-            LOG("order", 0, str(order.__dict__))
-            if order.getPortalType() == 'Purchase Order' :
-              delivery_module = order.getPortalObject().livraison_achat
-              delivery_type = 'Purchase Packing List'
-              delivery_line_type = delivery_type + ' Line'
-              delivery_cell_type = 'Delivery Cell'
-            else :
-              delivery_module = order.getPortalObject().livraison_vente
-              delivery_type = 'Sales Packing List'
-              delivery_line_type = delivery_type + ' Line'
-              delivery_cell_type = 'Delivery Cell'
-          else : # should never be none
-            LOG("order is None", 0, str(order.__dict__))
-            return -1
-        else: # order is None
-          order = None
-          # possible when we build deliveries for tranfer of property
-          delivery_module = self.getPortalObject().livraison_vente
-          delivery_type = 'Sales Packing List'
-          delivery_line_type = delivery_type + ' Line'
-          delivery_cell_type = 'Delivery Cell'
-
-        for path_group in order_group.group_list:
-          pathGroupProcessing(path_group=path_group,
-                              delivery_module=delivery_module,
-                              delivery_type=delivery_type,
-                              delivery_line_type=delivery_line_type,
-                              delivery_cell_type=delivery_cell_type,
-                              order=order,
-                              delivery_list=delivery_list,
-                              reindexable_movement_list=reindexable_movement_list, **kw)
-          
-        return 0
-      
-      
-      def pathGroupProcessing(path_group, delivery_module, delivery_type, delivery_line_type, delivery_cell_type, order, delivery_list, reindexable_movement_list, default_rule_id=None, **kw):
-        # we create a new delivery for each DateGroup
-
-        
-        if default_rule_id is 'default_amortisation_rule':
-          pass
-        else:
-          # if path is internal ???
-          # JPS NEW
-          if path_group.source is None or path_group.destination is None:
-            # Production Path
-            LOG("Builder",0, "Strange Path %s " % path_group.source)
-            LOG("Builder",0, "Strange Path %s " % path_group.destination)
-  
-          if path_group.source is None or path_group.destination is None:
-            delivery_module = self.rapport_fabrication
-            delivery_type = 'Production Report'
-            delivery_line_type = 'Production Report Line'
-            delivery_cell_type = 'Production Report Cell'
-          elif path_group.destination.find('site/Stock_PF') >= 0 and \
-              path_group.source.find('site/Piquage') >= 0:
-            delivery_module = self.livraison_fabrication
-            delivery_type = 'Production Packing List'
-            delivery_line_type = delivery_type + ' Line'
-            delivery_cell_type = 'Delivery Cell'
-          elif path_group.source.find('site/Stock_MP') >= 0 and \
-              path_group.destination.find('site/Piquage') >= 0:
-            delivery_module = self.livraison_fabrication
-            delivery_type = 'Production Packing List'
-            delivery_line_type = delivery_type + ' Line'
-            delivery_cell_type = 'Delivery Cell'
-
-          for date_group in path_group.group_list :
-            dateGroupProcessing(date_group=date_group,
-                                path_group=path_group,
-                                delivery_module=delivery_module,
-                                delivery_type=delivery_type,
-                                delivery_line_type=delivery_line_type,
-                                delivery_cell_type=delivery_cell_type,
-                                order=order,
-                                delivery_list=delivery_list,
-                                reindexable_movement_list=reindexable_movement_list,
-                                default_rule_id=default_rule_id, **kw)
-  
-      
-      def dateGroupProcessing(date_group, path_group, delivery_module, delivery_type, delivery_line_type, delivery_cell_type, order, delivery_list, reindexable_movement_list, default_rule_id=None, resource=None, **kw):
-        
-        if default_rule_id == 'default_amortisation_rule':
-          accounting_transaction_data_list = {}
-          
-          for path_group in date_group.group_list:
-            source_section = path_group.source_section
-            destination_section = path_group.destination_section
-            source = path_group.source
-            destination = path_group.destination
-            
-            accounting_transaction_data = accounting_transaction_data_list.get( (source_section, destination_section), None)
-            if accounting_transaction_data is None:
-              accounting_transaction_data_list[ (source_section, destination_section) ] = {}
-              accounting_transaction_data = accounting_transaction_data_list.get( (source_section, destination_section), None)
-            quantity = 0
-            source_movement_list = []
-            
-            for movement in path_group.movement_list:
-              if movement.getDeliveryValue() is None:
-                quantity += movement.getQuantity()
-                source_movement_list.append(movement)
-                LOG('buildDeliveryList :', 0, 'adding movement %s : quantity = %s' % (repr(movement), repr(movement.getQuantity())))
-              else:
-                LOG('buildDeliveryList :', 0, 'movement %s... delivery value = %s' % (repr(movement), repr(movement.getDeliveryValue())))
-          
-            accounting_transaction_data[ (source, destination) ] = (quantity, source_movement_list)
-            if len(source_movement_list) == 0:
-              LOG('buildDeliveryList :', 0, 'deleting transaction line because no source movement list... path_group.movement_list = %s' % repr(path_group.movement_list))
-              del accounting_transaction_data[ (source, destination) ]
-          
-          for (source_section, destination_section), accounting_transaction_data in accounting_transaction_data_list.items():
-            if len(accounting_transaction_data.items()) > 0:
-              new_delivery_id = str(delivery_module.generateNewId())
-              self.portal_types.constructContent(type_name = delivery_type,
-                                                container = delivery_module,
-                                                id = new_delivery_id,
-                                                target_start_date = date_group.start_date,
-                                                target_stop_date = date_group.stop_date,
-                                                start_date = date_group.start_date,
-                                                stop_date = date_group.stop_date,
-                                                source_section = source_section,
-                                                destination_section = destination_section
-                                                )
-              accounting_transaction = delivery_module[new_delivery_id]
-              accounting_transaction.setResource(resource)
-              for (source, destination), (quantity, source_movement_list) in accounting_transaction_data.items():
-                new_transaction_line_id = str(accounting_transaction.generateNewId())
-                self.portal_types.constructContent(type_name = delivery_line_type,
-                                                  container = accounting_transaction,
-                                                  id = new_transaction_line_id,
-                                                  source = source,
-                                                  destination = destination)
-                accounting_transaction_line = accounting_transaction[new_transaction_line_id]
-                accounting_transaction_line.setQuantity(quantity)
-                LOG('buildDeliveryList :', 0, 'setting resource for line %s... resource = %s' % (repr(accounting_transaction_line), repr(resource)))
-                accounting_transaction_line.setResource(resource)
-                for movement in source_movement_list:
-                  LOG('buildDeliveryList :', 0, 'setting delivery value... movement = %s, accounting_transaction_line = %s' % (repr(movement), repr(accounting_transaction_line)))
-                  movement.setDeliveryValue(accounting_transaction_line)
-                  LOG('buildDeliveryList :', 0, 'after setting it, movement.delivery_value = %s' % repr(movement.getDeliveryValue()))
-                  movement.recursiveImmediateReindexObject()
-                                              
-        else:
-          # Create a new packing list
-          new_delivery_id = str(delivery_module.generateNewId())
-          self.portal_types.constructContent(type_name = delivery_type,
-                                    container = delivery_module,
-                                    id = new_delivery_id,
-                                    target_start_date = date_group.start_date,
-                                    target_stop_date = date_group.stop_date,
-                                    start_date = date_group.start_date,
-                                    stop_date = date_group.stop_date,
-                                    source = path_group.source,
-                                    destination = path_group.destination,
-                                    source_section = path_group.source_section,
-                                    destination_section = path_group.destination_section
-                                    )
-          delivery = delivery_module[new_delivery_id]
-          if order is not None :
-            delivery.edit(title = order.getTitle(),
-                          causality_value = order,
-                          incoterm = order.getIncoterm(),
-                          delivery_mode = order.getDeliveryMode()
-                          )
-          # the new delivery is added to the delivery_list
-          delivery_list.append(delivery)
-  #        LOG('Livraison cr้้e',0,str(delivery.getId()))
-  
-          # Create each delivery_line in the new delivery
-  
-          for resource_group in date_group.group_list :
-            resourceGroupProcessing(resource_group=resource_group,
-                                    delivery=delivery,
-                                    delivery_type=delivery_type,
-                                    delivery_line_type=delivery_line_type,
-                                    delivery_cell_type=delivery_cell_type,
-                                    delivery_list=delivery_list,
-                                    reindexable_movement_list=reindexable_movement_list, **kw)
-      
-      
-      def resourceGroupProcessing(resource_group, delivery, delivery_type, delivery_line_type, delivery_cell_type, delivery_list, reindexable_movement_list, delivery_module=None, default_rule_id=None, **kw):
-          
-        if default_rule_id == 'default_amortisation_rule':
-          resource = resource_group.resource
-          for date_group in resource_group.group_list:
-            dateGroupProcessing(date_group=date_group,
-                                path_group=None,
-                                delivery_module=delivery_module,
-                                delivery_type=delivery_type,
-                                delivery_line_type=delivery_line_type,
-                                delivery_cell_type=delivery_cell_type,
-                                order=None,
-                                delivery_list=delivery_list,
-                                reindexable_movement_list=reindexable_movement_list,
-                                default_rule_id=default_rule_id,
-                                resource=resource)
-        else:
-      
-          if delivery_type == 'Production Report':
-            if resource_group.resource.find('operation') == 0:
-              delivery_line_type = 'Production Report Operation'
-            else:
-              delivery_line_type = 'Production Report Component'
-  
-          new_delivery_line_id = str(delivery.generateNewId())
-          self.portal_types.constructContent(type_name = delivery_line_type,
-                                            container = delivery,
-                                            id = new_delivery_line_id,
-                                            resource = resource_group.resource,
-                                            )
-          delivery_line = delivery[new_delivery_line_id]
-          
-          line_variation_category_list = []
-          line_variation_base_category_dict = {}
-  
-          # compute line_variation_base_category_list and
-          # line_variation_category_list for new delivery_line
-          for variant_group in resource_group.group_list :
-            for variation_item in variant_group.category_list :
-              if not variation_item in line_variation_category_list :
-                line_variation_category_list.append(variation_item)
-                variation_base_category_items = variation_item.split('/')
-                if len(variation_base_category_items) > 0 :
-                  line_variation_base_category_dict[variation_base_category_items[0]] = 1
-  
-          # update variation_base_category_list and line_variation_category_list for delivery_line
-          line_variation_base_category_list = line_variation_base_category_dict.keys()
-          delivery_line._setVariationBaseCategoryList(line_variation_base_category_list)
-          delivery_line.setVariationCategoryList(line_variation_category_list)
-  
-          # IMPORTANT : delivery cells are automatically created during setVariationCategoryList
-  
-          # update target_quantity for each delivery_cell
-          for variant_group in resource_group.group_list :
-            #LOG('Variant_group examin?,0,str(variant_group.category_list))
-            object_to_update = None
-            # if there is no variation of the resource, update delivery_line with quantities and price
-            if len(variant_group.category_list) == 0 :
-              object_to_update = delivery_line
-            # else find which delivery_cell is represented by variant_group
-            else :
-              categories_identity = 0
-              #LOG('Before Check cell',0,str(delivery_cell_type))
-              #LOG('Before Check cell',0,str(delivery_line.contentValues()))
-              for delivery_cell in delivery_line.contentValues(
-                                                    filter={'portal_type':delivery_cell_type}) :
-                #LOG('Check cell',0,str(delivery_cell))
-                if len(variant_group.category_list) == len(delivery_cell.getVariationCategoryList()) :
-                  #LOG('Parse category',0,str(delivery_cell.getVariationCategoryList()))
-                  for category in delivery_cell.getVariationCategoryList() :
-                    if not category in variant_group.category_list :
-                      #LOG('Not found category',0,str(category))
-                      break
-                  else :
-                    categories_identity = 1
-  
-                if categories_identity :
-                  object_to_update = delivery_cell
-                  break
-  
-            # compute target_quantity, quantity and price for delivery_cell or delivery_line and
-            # build relation between simulation_movement and delivery_cell or delivery_line
-            if object_to_update is not None :
-              cell_target_quantity = 0
-              cell_total_price = 0
-              for movement in variant_group.movement_list :
-                LOG('SimulationTool, movement.getPhysicalPath',0,movement.getPhysicalPath())
-                LOG('SimulationTool, movement.showDict',0,movement.showDict())
-                cell_target_quantity += movement.getNetConvertedTargetQuantity()
-                try:
-                  cell_total_price += movement.getNetConvertedTargetQuantity()*movement.getPrice() # XXX WARNING - ADD PRICED QUANTITY
-                except:
-                  cell_total_price = None
-  
-                if movement.getPortalType() == 'Simulation Movement' :
-                  # update every simulation_movement
-                  # we set delivery_value and target dates and quantity
-                  movement._setDeliveryValue(object_to_update)
-                  movement._setTargetQuantity(movement.getTargetQuantity())
-                  movement._setQuantity(movement.getTargetQuantity())
-                  movement._setEfficiency(movement.getTargetEfficiency())
-                  movement._setTargetStartDate(movement.getTargetStartDate())
-                  movement._setTargetStopDate(movement.getTargetStopDate())
-                  movement._setStartDate(movement.getTargetStartDate())
-                  movement._setStopDate(movement.getTargetStopDate())
-                  # We will reindex later
-                  reindexable_movement_list.append(movement)
-  
-              if cell_target_quantity <> 0 and cell_total_price is not None:
-                average_price = cell_total_price/cell_target_quantity
-              else :
-                average_price = 0
-              #LOG('object mis ?jour',0,str(object_to_update.getRelativeUrl()))
-              object_to_update._edit(target_quantity = cell_target_quantity,
-                                    quantity = cell_target_quantity,
-                                    price = average_price,
-                                    force_update = 1,
-                                    )
-
-      
-                                  
-      delivery_list = []
-      reindexable_movement_list = []
-      
-
-      if movement_group is not None:
-        # Verify the rule used to build the movements
-        default_rule_id = None
-        if len(movement_group.movement_list) > 0:
-          f = getattr(movement_group.movement_list[0], 'getRootAppliedRule', None)
-          if f is not None:
-            applied_rule = f()
-            default_rule_id = applied_rule.getSpecialiseId()
-        
-        
-        LOG('buildDeliveryList :', 0, 'default_rule = %s' % repr(default_rule_id))
-        if default_rule_id == 'default_amortisation_rule':
-          LOG('buildDeliveryList :', 0, 'default_rule is default_amortisation_rule')
-          delivery_module = self.getPortalObject().accounting
-          delivery_type = 'Amortisation Transaction'
-          delivery_line_type = delivery_type + ' Line'
-          delivery_cell_type = None
-          
-          for resource_group in movement_group.group_list:
-            resourceGroupProcessing(resource_group=resource_group,
-                                    delivery=None,
-                                    delivery_module=delivery_module,
-                                    delivery_type=delivery_type,
-                                    delivery_line_type=delivery_line_type,
-                                    delivery_cell_type=delivery_cell_type,
-                                    delivery_list=delivery_list,
-                                    reindexable_movement_list=reindexable_movement_list,
-                                    default_rule_id=default_rule_id)
-          for movement in movement_group.movement_list:
-            movement.immediateReindexObject()
-          
-          
-        else:
-          for order_group in movement_group.group_list:
-            if orderGroupProcessing(order_group=order_group,
-                                    delivery_list=delivery_list,
-                                    reindexable_movement_list=reindexable_movement_list) == -1:
-              return delivery_list
-              
-                
-      # If we reach this point, it means we could
-      # create deliveries
-      # get_transaction().commit()
-      # DO NOT USE COMMIT BECAUSE OF WORKFLOW
-
-      # Now, let us index what must be indexed
-      # Since we comitted changes, there should be no risk of conflict
-      for movement in reindexable_movement_list:
-        movement.reindexObject() # we do it now because we need to
-                                 # update category relation
-
-      # Now return deliveries which were created
-      return delivery_list
-
-    #######################################################
-    # 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.getTargetStartDate()
-      stop_date = movement.getTargetStopDate()
-      # 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 type(resource_aggregation_base_category) not in (type([]), type(())):
-              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=current_inventory_state_list):
-      section_value = self.portal_categories.resolveCategory(section_category)
-      node_value = self.portal_categories.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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.getTargetQuantity(), '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.immediateReindexObject()
-          m.reindexObject()
-          #m.activate(priority=7).immediateReindexObject() # Too slow
-
-      return result
-
-    # Used for mergeDeliveryList.
-    class MergeDeliveryListError(Exception): pass
-
-    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.
-        Return the main delivery.
-      """
-      # Sanity checks.
-      if len(delivery_list) == 0:
-        raise self.MergeDeliveryListError, "No delivery is passed"
-      elif len(delivery_list) == 1:
-        raise self.MergeDeliveryListError, "Only one delivery is passed"
-
-      main_delivery = delivery_list[0]
-      delivery_list = delivery_list[1:]
-
-      # Another sanity check. It is necessary for them to be identical in some attributes.
-      for delivery in delivery_list:
-        for attr in ('portal_type', 'simulation_state',
-                     'source', 'destination',
-                     'source_section', 'destination_section',
-                     'source_decision', 'destination_decision',
-                     'source_administration', 'destination_administration',
-                     'source_payment', 'destination_payment'):
-          main_value = main_delivery.getProperty(attr)
-          value = delivery.getProperty(attr)
-          if  main_value != value:
-            raise self.MergeDeliveryListError, \
-              "%s is not the same between %s and %s (%s and %s)" % (attr, delivery.getId(), main_delivery.getId(), value, main_value)
-
-      # One more sanity check. Check if discounts are the same, if any.
-      main_discount_list = main_delivery.contentValues(filter = {'portal_type': discount_type_list})
-      for delivery in delivery_list:
-        discount_list = delivery.contentValues(filter = {'portal_type': discount_type_list})
-        if len(main_discount_list) != len(discount_list):
-          raise self.MergeDeliveryListError, "Discount is not the same between %s and %s" % (delivery.getId(), main_delivery.getId())
-        for discount in discount_list:
-          for main_discount in main_discount_list:
-            if discount.getDiscount() == main_discount.getDiscount() \
-               and discount.getDiscountRatio() == main_discount.getDiscountRatio() \
-               and discount.getDiscountType() == main_discount.getDiscountType() \
-               and discount.getImmediateDiscount() == main_discount.getImmediateDiscount():
-              break
-          else:
-            raise self.MergeDeliveryListError, "Discount is not the same between %s and %s" % (delivery.getId(), main_delivery.getId())
-
-      # One more sanity check. Check if payment conditions are the same, if any.
-      main_payment_condition_list = main_delivery.contentValues(filter = {'portal_type': payment_condition_type_list})
-      for delivery in delivery_list:
-        payment_condition_list = delivery.contentValues(filter = {'portal_type': payment_condition_type_list})
-        if len(main_payment_condition_list) != len(payment_condition_list):
-          raise self.MergeDeliveryListError, "Payment Condition is not the same between %s and %s" % (delivery.getId(), main_delivery.getId())
-        for condition in payment_condition_list:
-          for main_condition in main_payment_condition_list:
-            if condition.getPaymentMode() == main_condition.getPaymentMode() \
-               and condition.getPaymentAdditionalTerm() == main_condition.getPaymentAdditionalTerm() \
-               and condition.getPaymentAmount() == main_condition.getPaymentAmount() \
-               and condition.getPaymentEndOfMonth() == main_condition.getPaymentEndOfMonth() \
-               and condition.getPaymentRatio() == main_condition.getPaymentRatio() \
-               and condition.getPaymentTerm() == main_condition.getPaymentTerm():
-              break
-          else:
-            raise self.MergeDeliveryListError, "Payment Condition is not the same between %s and %s" % (delivery.getId(), main_delivery.getId())
-
-      # Make sure that all activities are flushed, to get simulation movements from delivery cells.
-      for delivery in delivery_list:
-        for order in delivery.getCausalityValueList(portal_type = order_type_list):
-          for applied_rule in order.getCausalityRelatedValueList(portal_type = 'Applied Rule'):
-            applied_rule.flushActivity(invoke = 1)
-        for causality_related_delivery in delivery.getCausalityValueList(portal_type = delivery_type_list):
-          for applied_rule in causality_related_delivery.getCausalityRelatedValueList(portal_type = 'Applied Rule'):
-            applied_rule.flushActivity(invoke = 1)
-
-      # Get a list of simulated movements and invoice movements.
-      main_simulated_movement_list = main_delivery.getSimulatedMovementList()
-      main_invoice_movement_list = main_delivery.getInvoiceMovementList()
-      simulated_movement_list = main_simulated_movement_list[:]
-      invoice_movement_list = main_invoice_movement_list[:]
-      for delivery in delivery_list:
-        simulated_movement_list.extend(delivery.getSimulatedMovementList())
-        invoice_movement_list.extend(delivery.getInvoiceMovementList())
-
-      #for movement in simulated_movement_list + invoice_movement_list:
-      #  parent = movement.aq_parent
-      #  LOG('mergeDeliveryList', 0, 'movement = %s, parent = %s, movement.getPortalType() = %s, parent.getPortalType() = %s' % (repr(movement), repr(parent), repr(movement.getPortalType()), repr(parent.getPortalType())))
-
-      LOG('mergeDeliveryList', 0, 'simulated_movement_list = %s, invoice_movement_list = %s' % (str(simulated_movement_list), str(invoice_movement_list)))
-      for main_movement_list, movement_list in \
-        ((main_simulated_movement_list, simulated_movement_list),
-         (main_invoice_movement_list, invoice_movement_list)):
-        root_group = self.collectMovement(movement_list,
-                                          check_order = 0,
-                                          check_path = 0,
-                                          check_date = 0,
-                                          check_criterion = 1,
-                                          check_resource = 1,
-                                          check_base_variant = 1,
-                                          check_variant = 1)
-        for criterion_group in root_group.group_list:
-          LOG('mergeDeliveryList dump tree', 0, 'criterion = %s, movement_list = %s, group_list = %s' % (repr(criterion_group.criterion), repr(criterion_group.movement_list), repr(criterion_group.group_list)))
-          for resource_group in criterion_group.group_list:
-            LOG('mergeDeliveryList dump tree', 0, 'resource = %s, movement_list = %s, group_list = %s' % (repr(resource_group.resource), repr(resource_group.movement_list), repr(resource_group.group_list)))
-            for base_variant_group in resource_group.group_list:
-              LOG('mergeDeliveryList dump tree', 0, 'base_category_list = %s, movement_list = %s, group_list = %s' % (repr(base_variant_group.base_category_list), repr(base_variant_group.movement_list), repr(base_variant_group.group_list)))
-              for variant_group in base_variant_group.group_list:
-                LOG('mergeDeliveryList dump tree', 0, 'category_list = %s, movement_list = %s, group_list = %s' % (repr(variant_group.category_list), repr(variant_group.movement_list), repr(variant_group.group_list)))
-
-        for criterion_group in root_group.group_list:
-          for resource_group in criterion_group.group_list:
-            for base_variant_group in resource_group.group_list:
-              # Get a list of categories.
-              category_dict = {}
-              for variant_group in base_variant_group.group_list:
-                for category in variant_group.category_list:
-                  category_dict[category] = 1
-              category_list = category_dict.keys()
-
-              # Try to find a delivery line.
-              delivery_line = None
-              for movement in base_variant_group.movement_list:
-                if movement in main_movement_list:
-                  if movement.aq_parent.getPortalType() in simulated_movement_type_list \
-                    or movement.aq_parent.getPortalType() in invoice_movement_type_list:
-                    delivery_line = movement.aq_parent
-                  else:
-                    delivery_line = movement
-                  LOG('mergeDeliveryList', 0, 'delivery_line %s is found: criterion = %s, resource = %s, base_category_list = %s' % (repr(delivery_line), repr(criterion_group.criterion), repr(resource_group.resource), repr(base_variant_group.base_category_list)))
-                  break
-
-              if delivery_line is None:
-                # Not found. So create a new delivery line.
-                movement = base_variant_group.movement_list[0]
-                if movement.aq_parent.getPortalType() in simulated_movement_type_list \
-                  or movement.aq_parent.getPortalType() in invoice_movement_type_list:
-                  delivery_line_type = movement.aq_parent.getPortalType()
-                else:
-                  delivery_line_type = movement.getPortalType()
-                delivery_line = main_delivery.newContent(portal_type = delivery_line_type,
-                                                         resource = resource_group.resource)
-                LOG('mergeDeliveryList', 0, 'New delivery_line %s is created: criterion = %s, resource = %s, base_category_list = %s' % (repr(delivery_line), repr(criterion_group.criterion), repr(resource_group.resource), repr(base_variant_group.base_category_list)))
-
-              # Update the base categories and categories.
-              #LOG('mergeDeliveryList', 0, 'base_category_list = %s, category_list = %s' % (repr(base_category_list), repr(category_list)))
-              delivery_line.setVariationBaseCategoryList(base_variant_group.base_category_list)
-              delivery_line.setVariationCategoryList(category_list)
-
-              object_to_update = None
-              for variant_group in base_variant_group.group_list:
-                if len(variant_group.category_list) == 0:
-                  object_to_update = delivery_line
-                else:
-                  for delivery_cell in delivery_line.contentValues():
-                    predicate_value_list = delivery_cell.getPredicateValueList()
-                    LOG('mergeDeliveryList', 0, 'delivery_cell = %s, predicate_value_list = %s, variant_group.category_list = %s' % (repr(delivery_cell), repr(predicate_value_list), repr(variant_group.category_list)))
-                    if len(predicate_value_list) == len(variant_group.category_list):
-                      for category in variant_group.category_list:
-                        if category not in predicate_value_list:
-                          break
-                      else:
-                        object_to_update = delivery_cell
-                        break
-
-                #LOG('mergeDeliveryList', 0, 'object_to_update = %s' % repr(object_to_update))
-                if object_to_update is not None:
-                  cell_price = object_to_update.getPrice() or 0.0
-                  cell_quantity = object_to_update.getQuantity() or 0.0
-                  cell_target_quantity = object_to_update.getNetConvertedTargetQuantity() or 0.0
-                  cell_total_price = cell_target_quantity * cell_price
-                  cell_category_list = list(object_to_update.getCategoryList())
-
-                  for movement in variant_group.movement_list:
-                    if movement in main_movement_list:
-                      continue
-                    LOG('mergeDeliveryList', 0, 'movement = %s' % repr(movement))
-                    cell_quantity += movement.getQuantity()
-                    cell_target_quantity += movement.getNetConvertedTargetQuantity()
-                    try:
-                      # XXX WARNING - ADD PRICED QUANTITY
-                      cell_price = movement.getPrice()
-                      cell_total_price += movement.getNetConvertedTargetQuantity() * cell_price
-                    except:
-                      cell_total_price = None
-                    for category in movement.getCategoryList():
-                      if category not in cell_category_list:
-                        cell_category_list.append(category)
-                    # Make sure that simulation movements point to an appropriate delivery line or
-                    # delivery cell.
-                    if hasattr(movement, 'getDeliveryRelatedValueList'):
-                      for simulation_movement in \
-                        movement.getDeliveryRelatedValueList(portal_type = 'Simulation Movement'):
-                        simulation_movement.setDeliveryValue(object_to_update)
-                        #simulation_movement.reindexObject()
-                    if hasattr(movement, 'getOrderRelatedValueList'):
-                      for simulation_movement in \
-                        movement.getOrderRelatedValueList(portal_type = 'Simulation Movement'):
-                        simulation_movement.setOrderValue(object_to_update)
-                        #simulation_movement.reindexObject()
-
-                  if cell_target_quantity != 0 and cell_total_price is not None:
-                    average_price = cell_total_price / cell_target_quantity
-                  else:
-                    average_price = 0
-
-                  LOG('mergeDeliveryList', 0, 'object_to_update = %s, cell_category_list = %s, cell_target_quantity = %s, cell_quantity = %s, average_price = %s' % (repr(object_to_update), repr(cell_category_list), repr(cell_target_quantity), repr(cell_quantity), repr(average_price)))
-                  object_to_update.setCategoryList(cell_category_list)
-                  if object_to_update.getPortalType() in simulated_movement_type_list:
-                    object_to_update.edit(target_quantity = cell_target_quantity,
-                                          quantity = cell_quantity,
-                                          price = average_price,
-                                          )
-                  elif object_to_update.getPortalType() in invoice_movement_type_list:
-                    # Invoices do not have target quantities, and the price never change.
-                    object_to_update.edit(quantity = cell_quantity,
-                                          price = cell_price,
-                                          )
-                  else:
-                    raise self.MergeDeliveryListError, "Unknown portal type %s" % str(object_to_update.getPortalType())
-                  #object_to_update.immediateReindexObject()
-                else:
-                  raise self.MergeDeliveryListError, "No object to update"
-
-      # Merge containers. Just copy them from other deliveries into the main.
-      for delivery in delivery_list:
-        container_id_list = delivery.contentIds(filter = {'portal_type': container_type_list})
-        if len(container_id_list) > 0:
-          copy_data = delivery.manage_copyObjects(ids = container_id_list)
-          new_id_list = main_delivery.manage_pasteObjects(copy_data)
-
-      # Unify the list of causality.
-      causality_list = main_delivery.getCausalityValueList()
-      for delivery in delivery_list:
-        for causality in delivery.getCausalityValueList():
-          if causality not in causality_list:
-            causality_list.append(causality)
-      LOG("mergeDeliveryList", 0, "causality_list = %s" % str(causality_list))
-      main_delivery.setCausalityValueList(causality_list)
-
-      # Cancel deliveries.
-      for delivery in delivery_list:
-        LOG("mergeDeliveryList", 0, "cancelling %s" % repr(delivery))
-        delivery.cancel()
-
-      # Reindex the main delivery.
-      main_delivery.reindexObject()
-
-      return main_delivery
-
-
-InitializeClass(SimulationTool)
-- 
2.30.9