From 4a46417832eecda474654808c2b18b63bdcf2e9a Mon Sep 17 00:00:00 2001
From: Yoshinori Okuji <yo@nexedi.com>
Date: Mon, 24 May 2004 16:37:46 +0000
Subject: [PATCH] Rewrite collectMovement to make it generic. Fix many bugs in
 mergeDeliveryList.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@910 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5/Tool/SimulationTool.py | 367 ++++++++++++++++++----------
 1 file changed, 241 insertions(+), 126 deletions(-)

diff --git a/product/ERP5/Tool/SimulationTool.py b/product/ERP5/Tool/SimulationTool.py
index 42396d4454..2c1dd2aa04 100755
--- a/product/ERP5/Tool/SimulationTool.py
+++ b/product/ERP5/Tool/SimulationTool.py
@@ -32,7 +32,7 @@ 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
+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
 
@@ -40,7 +40,6 @@ from zLOG import LOG
 
 from Products.ERP5.Capacity.GLPK import solve
 from Numeric import zeros, resize
-import cPickle
 
 # Solver Registration
 is_initialized = 0
@@ -210,18 +209,50 @@ class SimulationTool (Folder, UniqueObject):
 
     #######################################################
     # Movement Group Collection / Delivery Creation
-    def collectMovement(self, movement_list):
+    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):
 
       class RootGroup:
 
-        def __init__(self,movement=None):
+        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.
+          """
+          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),
+                        )
+          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):
-          self.group_list.append(OrderGroup(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)
@@ -258,9 +289,6 @@ class SimulationTool (Folder, UniqueObject):
             order_relative_url = order_value.getRelativeUrl()
           self.order = order_relative_url
 
-        def appendGroup(self, movement):
-          self.group_list.append(PathGroup(movement))
-
         def test(self,movement):
           if hasattr(movement, 'getRootAppliedRule'):
             order_value = movement.getRootAppliedRule().getCausalityValue(
@@ -294,9 +322,6 @@ class SimulationTool (Folder, UniqueObject):
           self.source_section = movement.getSourceSection()
           self.destination_section = movement.getDestinationSection()
 
-        def appendGroup(self, movement):
-          self.group_list.append(DateGroup(movement))
-
         def test(self,movement):
           if movement.getSource() == self.source and \
             movement.getDestination() == self.destination and \
@@ -315,9 +340,6 @@ class SimulationTool (Folder, UniqueObject):
           self.start_date = movement.getStartDate()
           self.stop_date = movement.getStopDate()
 
-        def appendGroup(self, movement):
-          self.group_list.append(ResourceGroup(movement))
-
         def test(self,movement):
           if movement.getStartDate() == self.start_date and \
             movement.getStopDate() == self.stop_date :
@@ -325,30 +347,58 @@ class SimulationTool (Folder, UniqueObject):
           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 appendGroup(self, movement):
-          self.group_list.append(VariantGroup(movement))
-
         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()
+
+        def test(self,movement):
+          # we must have the same number of categories
+          categories_identity = 0
+          if len(self.base_category_list) == len(movement.getVariationBaseCategoryList()) :
+            for category in movement.getVariationBaseCategoryList() :
+              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()
 
-        def appendGroup(self, movement):
-          pass
-
         def test(self,movement):
           # we must have the same number of categories
           categories_identity = 0
@@ -1097,6 +1147,9 @@ class SimulationTool (Folder, UniqueObject):
 
       return result
 
+    # Used for mergeDeliveryList.
+    class MergeDeliveryListError(Exception): pass
+
     security.declareProtected( Permissions.ModifyPortalContent, 'mergeDeliveryList' )
     def mergeDeliveryList(self, delivery_list):
       """
@@ -1106,18 +1159,17 @@ class SimulationTool (Folder, UniqueObject):
         The others are cancelled.
         Return the main delivery.
       """
-      class MergeDeliveryListError(Exception): pass
 
       # Sanity checks.
       if len(delivery_list) == 0:
-        raise MergeDeliveryListError, "No delivery is passed"
+        raise self.MergeDeliveryListError, "No delivery is passed"
       elif len(delivery_list) == 1:
-        raise MergeDeliveryListError, "Only one delivery is passed"
+        raise self.MergeDeliveryListError, "Only one delivery is passed"
 
       main_delivery = delivery_list[0]
       delivery_list = delivery_list[1:]
 
-      # One more sanity check.
+      # 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',
@@ -1128,118 +1180,181 @@ class SimulationTool (Folder, UniqueObject):
           main_value = main_delivery.getProperty(attr)
           value = delivery.getProperty(attr)
           if  main_value != value:
-            raise MergeDeliveryListError, \
-              "In %s of %s, %s is different from %s" % (attr, delivery.getId(), value, main_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 movements.
-      movement_list = []
+      # 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:
-        movement_list.extend(delivery.getMovementList())
-
-      group_list = main_delivery.collectMovement(movement_list)
-      for group in group_list:
-        # First, try to find a delivery line which matches this group in the main delivery.
-        for delivery_line in main_delivery.contentValues():
-          if group.resource_id == delivery_line.getResourceId() and \
-            group.variation_base_category_list == delivery_line.getVariationBaseCategoryList():
-            # Found. Update the list of variation categories.
-            delivery_line.setVariationCategoryList(group.variation_category_list)
-            break
-        else:
-          # Create a new delivery line.
-          delivery_line_type = None
-          for delivery in delivery_list:
-            for delivery_line in delivery.contentValues():
-              delivery_line_type = delivery_line.getPortalType()
-              break
-          delivery_line = main_delivery.newContent(portal_type = delivery_line_type,
-                                                   resource = group.resource)
-          delivery_line.setVariationBaseCategoryList(group.variation_base_category_list)
-          delivery_line.setVariationCategoryList(group.variation_category_list)
-
-        # Make variant groups. Since Python cannot use a list as an index of a hash,
-        # use a picked object as an index instead.
-        variant_map = {}
-        for movement in group.movement_list:
-          predicate_list = movement.getPredicateValueList()
-          predicate_list.sort()
-          pickled_predicate_list = cPickle.dumps(predicate_list)
-          if pickled_predicate_list in variant_map:
-            variant_map[pickled_predicate_list].append(movement)
-          else:
-            variant_map[pickled_predicate_list] = [movement]
+        simulated_movement_list.extend(delivery.getSimulatedMovementList())
+        invoice_movement_list.extend(delivery.getInvoiceMovementList())
+
+      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:
+          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
 
-        for pickled_predicate_list,movement_list in variant_map.items():
-          predicate_list = cPickle.loads(pickled_predicate_list)
-          object_to_update = None
-          if len(predicate_list) == 0:
-            object_to_update = delivery_line
-          else:
-            identical = 0
-            for delivery_cell in delivery_line.contentValues():
-              if len(delivery_cell.getPredicateValueList()) == len(predicate_list):
-                for category in delivery_cell.getPredicateValueList():
-                  if category not in predicate_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:
-                  identical = 1
-                  break
+                  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)
+
+              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()
+                    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
 
-            if identical:
-              object_to_update = delivery_cell
-
-          LOG("mergeDeliveryList", 0, "predicate_list = %s, movement_list = %s, object_to_update = %s" % (repr(predicate_list), repr(movement_list), repr(object_to_update)))
-          if object_to_update is not None:
-            cell_quantity = object_to_update.getQuantity()
-            cell_target_quantity = object_to_update.getNetConvertedTargetQuantity()
-            cell_total_price = cell_target_quantity * object_to_update.getPrice()
-            cell_category_list = list(object_to_update.getCategoryList())
-            LOG("mergeDeliveryList", 0, "cell_target_quantity = %s, cell_quantity = %s, cell_total_price = %s, cell_category_list = %s" % (repr(cell_target_quantity), repr(cell_quantity), repr(cell_total_price), repr(cell_category_list)))
-
-            for movement in movement_list :
-              cell_quantity += movement.getQuantity()
-              cell_target_quantity += movement.getNetConvertedTargetQuantity()
-              try:
-                # XXX WARNING - ADD PRICED QUANTITY
-                cell_total_price += movement.getNetConvertedTargetQuantity() * movement.getPrice()
-              except:
-                cell_total_price = None
-              for category in movement.getCategoryList():
-                if category not in cell_category_list:
-                  cell_category_list.append(category)
-              LOG("mergeDeliveryList", 0, "movement = %s, cell_target_quantity = %s, cell_quantity = %s, cell_total_price = %s, cell_category_list = %s" % (repr(movement), repr(cell_target_quantity), repr(cell_quantity), repr(cell_total_price), repr(cell_category_list)))
-              # 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('object mis ?jour',0,str(object_to_update.getRelativeUrl()))
-            object_to_update.setCategoryList(cell_category_list)
-            object_to_update.edit(target_quantity = cell_target_quantity,
-                                  quantity = cell_quantity,
-                                  price = average_price,
-                                  )
-            #object_to_update.reindexObject()
-          else:
-            raise MergeDeliveryListError, "No object to update"
+                LOG('mergeDeliveryList', 0, 'object_to_update = %s' % repr(object_to_update))
+                if object_to_update is not None:
+                  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 * (object_to_update.getPrice() or 0.0)
+                  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_total_price += movement.getNetConvertedTargetQuantity() * movement.getPrice()
+                    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, 'cell_category_list = %s' % repr(cell_category_list))
+                  object_to_update.setCategoryList(cell_category_list)
+                  object_to_update.edit(target_quantity = cell_target_quantity,
+                                        quantity = cell_quantity,
+                                        price = average_price,
+                                        )
+                  #object_to_update.reindexObject()
+                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()
@@ -1261,4 +1376,4 @@ class SimulationTool (Folder, UniqueObject):
       return main_delivery
 
 
-InitializeClass(SimulationTool)
+InitializeClass(SimulationTool)
\ No newline at end of file
-- 
2.30.9