From d096472ae07808bb35499906e5af4e064a65da77 Mon Sep 17 00:00:00 2001
From: Romain Courteaud <romain@nexedi.com>
Date: Mon, 18 Apr 2005 08:15:33 +0000
Subject: [PATCH] First submission.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@2891 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5/Document/DeliveryBuilder.py      | 343 ++++++++++++++++++
 product/ERP5/PropertySheet/DeliveryBuilder.py |  87 +++++
 2 files changed, 430 insertions(+)
 create mode 100755 product/ERP5/Document/DeliveryBuilder.py
 create mode 100755 product/ERP5/PropertySheet/DeliveryBuilder.py

diff --git a/product/ERP5/Document/DeliveryBuilder.py b/product/ERP5/Document/DeliveryBuilder.py
new file mode 100755
index 0000000000..b19af03220
--- /dev/null
+++ b/product/ERP5/Document/DeliveryBuilder.py
@@ -0,0 +1,343 @@
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Romain Courteaud <romain@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+from AccessControl import ClassSecurityInfo
+from Products.CMFCore.utils import getToolByName
+from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5.Document.Predicate import Predicate
+from Products.ERP5.Document.Amount import Amount
+from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
+from Products.ERP5 import MovementGroup
+
+from zLOG import LOG
+
+class DeliveryBuilder(XMLObject, Amount, Predicate):
+  """
+    Delivery Builder objects allow to gather multiple Simulation Movements
+    into a single Delivery. 
+
+    The initial quantity property of the Delivery Line is calculated by
+    summing quantities of related Simulation Movements.
+
+    Delivery Builders are called for example whenever an order is confirmed.
+    They are also called globaly in order to gather any confirmed or above 
+    Simulation Movement which was not associated to any Delivery Line. 
+    Such movements are called orphaned Simulation Movements.
+
+    Delivery Builder objects are provided with a set a parameters to achieve 
+    their goal:
+
+    A path definition: source, destination, etc. which defines the general 
+    kind of movements it applies.
+
+    simulation_select_method which defines how to query all Simulation 
+    Movements which meet certain criteria (including the above path path 
+    definition).
+
+    collect_order_list which defines how to group selected movements 
+    according to gathering rules.
+
+    delivery_select_method which defines how to select existing Delivery 
+    which may eventually be updated with selected simulation movements.
+
+    delivery_module, delivery_type and delivery_line_type which define the 
+    module and portal types for newly built Deliveries and Delivery Lines.
+
+    Delivery Builders can also be provided with optional parameters to 
+    restrict selection to a given root Applied Rule caused by a single Order
+    or to Simulation Movements related to a limited set of existing 
+    Deliveries.
+  """
+
+  # CMF Type Definition
+  meta_type = 'ERP5 Delivery Builder'
+  portal_type = 'Delivery Builder'
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.View)
+
+  # Default Properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Arrow
+                    , PropertySheet.Amount
+                    , PropertySheet.Comment
+                    , PropertySheet.DeliveryBuilder
+                    )
+
+  def build(self, applied_rule=None):
+    """
+      Build deliveries from a list of movements
+
+      Delivery Builders can also be provided with optional parameters to
+      restrict selection to a given root Applied Rule caused by a single Order
+      or to Simulation Movements related to a limited set of existing
+    """
+    # Select
+    movement_list = self.selectMovement(applied_rule=applied_rule)
+    # Collect
+    root_group = self.collectMovement(movement_list)
+    # And finally build
+    delivery_list = self.buildDeliveryList(root_group)
+    return delivery_list
+
+  def selectMovement(self, applied_rule=None):
+    """
+      defines how to query all Simulation Movements which meet certain criteria
+      (including the above path path definition).
+
+      First, select movement matching to criteria define on DeliveryBuilder
+      Then, call script simulation_select_method to restrict movement_list
+    """
+    movement_list = []
+    kw = {}
+    # We only search Simulation Movement
+    kw['portal_type'] = 'Simulation Movement'
+    # Search only child movement from this applied rule
+    if applied_rule is not None:
+      kw['parent_uid'] = applied_rule.getUid()
+    # XXX Add profile query
+    # Add resource query
+    if self.resource_portal_type != '':
+      kw['resourceType'] = self.resource_portal_type
+
+    if self.simulation_select_method_id in ['', None]:
+      kw.update(self.portal_catalog.buildSQLQuery(**kw))
+      movement_list = [x.getObject() for x in self.portal_catalog(**kw)]
+    else:
+      select_method = getattr(self, self.simulation_select_method_id) 
+      movement_list = select_method(kw)
+      sql_query = select_method(kw, src__=1)
+
+    # XXX  Add predicate test
+    return movement_list
+
+  def getCollectOrderList(self):
+    """
+      Simply method to get the 3 collect order lists define on a
+      DeliveryBuilder
+    """
+    return self.getDeliveryCollectOrderList()+\
+           self.getDeliveryLineCollectOrderList()+\
+           self.getDeliveryCellCollectOrderList()
+
+  def collectMovement(self, movement_list):
+    """
+      group movements in the way we want. Thanks to this method, we are able 
+      to retrieve movement classed by order, resource, criterion,....
+
+      movement_list : the list of movement wich we want to group
+
+      check_list : the list of classes used to group movements. The order
+                   of the list is important and determines by what we will
+                   group movement first
+                   Typically, check_list is :
+                   [DateMovementGroup,PathMovementGroup,...]
+    """
+    class_list = []
+    for class_name in self.getCollectOrderList():
+      class_list.append(getattr(MovementGroup, class_name))
+
+    my_root_group = MovementGroup.RootMovementGroup(class_list=class_list)
+    for movement in movement_list:
+      my_root_group.append(movement,class_list=class_list)
+
+    return my_root_group
+
+  def buildDeliveryList(self, movement_group):
+    """
+      Build deliveries from a list of movements
+    """
+    delivery_module = getattr(self, self.getDeliveryModule())
+
+    delivery_list,\
+    reindexable_movement_list = self._deliveryGroupProcessing(
+                                            delivery_module,
+                                            movement_group,
+                                            self.getDeliveryCollectOrderList(),
+                                            {})
+
+    for movement in reindexable_movement_list:
+      # We have to use 'immediate' to bypass the activity tool,
+      # because we will depend on these objects when we try to call 
+      # buildInvoiceList
+      movement.immediateReindexObject() 
+
+    return delivery_list
+
+  def _deliveryGroupProcessing(self, delivery_module, movement_group, 
+                               collect_order_list, property_dict):
+    """
+      Build empty delivery from a list of movement
+    """
+    delivery_list = []
+    reindexable_movement_list = []
+
+    # Get current properties from current movement group
+    # And fill property_dict
+    property_dict.update(movement_group.getGroupEditDict())
+
+
+    if collect_order_list != []:
+      # Get sorted movement for each delivery
+      for group in movement_group.getGroupList():
+        new_delivery_list, \
+        new_reindexable_movement_list = self._deliveryGroupProcessing(
+            delivery_module,
+            group,
+            collect_order_list[1:],
+            property_dict.copy())
+
+        delivery_list.extend(new_delivery_list)
+        reindexable_movement_list.extend(new_reindexable_movement_list)
+
+    else:
+      # Create delivery
+      new_delivery_id = str(delivery_module.generateNewId())
+      delivery = delivery_module.newContent(
+                                type_name=self.getDeliveryPortalType(),
+                                id=new_delivery_id)
+      # Put properties on delivery
+      delivery.edit(**property_dict)
+
+      # Then, create delivery line
+      for group in movement_group.getGroupList():
+        reindexable_movement_list = self._deliveryLineGroupProcessing(
+                                    delivery,
+                                    group,
+                                    self.getDeliveryLineCollectOrderList()[1:],
+                                    {})
+
+      delivery_list.append(delivery)
+
+    # XXX temporary
+    reindexable_movement_list = []
+    return delivery_list, reindexable_movement_list
+      
+  def _deliveryLineGroupProcessing(self, delivery, movement_group,
+                                   collect_order_list, property_dict):
+    """
+      Build delivery line from a list of movement on a delivery
+    """
+    # Get current properties from current movement group
+    # And fill property_dict
+    property_dict.update(movement_group.getGroupEditDict())
+
+    if collect_order_list != []:
+      # Get sorted movement for each delivery line
+      for group in movement_group.getGroupList():
+        self._deliveryLineGroupProcessing(
+          delivery, group, collect_order_list[1:], property_dict.copy())
+    else:
+      # Create delivery line
+      new_delivery_line_id = str(delivery.generateNewId())
+      delivery_line = delivery.newContent(
+                                type_name=self.getDeliveryLinePortalType(),
+                                id=new_delivery_line_id)
+      # Put properties on delivery line
+      delivery_line.edit(**property_dict)
+
+      # Set variation category list on line
+      line_variation_category_list = []
+      for movement in movement_group.getMovementList():
+        line_variation_category_list.extend(
+                                      movement.getVariationCategoryList())
+      # erase double
+      line_variation_category_list = dict([(x, 1) for x in\
+                                          line_variation_category_list]).keys()
+      delivery_line.setVariationCategoryList(line_variation_category_list)
+
+      # Then, create delivery movement (delivery cell or complete delivery
+      # line)
+      for group in movement_group.getGroupList():
+        reindexable_movement_list = self._deliveryCellGroupProcessing(
+                                    delivery_line,
+                                    group,
+                                    self.getDeliveryCellCollectOrderList()[1:],
+                                    {})
+
+  def _deliveryCellGroupProcessing(self, delivery_line, movement_group,
+                                   collect_order_list, property_dict):
+    """
+      Build delivery cell from a list of movement on a delivery line
+      or complete delivery line
+    """
+    # Get current properties from current movement group
+    # And fill property_dict
+    property_dict.update(movement_group.getGroupEditDict())
+
+    if collect_order_list != []:
+      # Get sorted movement for each delivery line
+      for group in movement_group.getGroupList():
+        self._deliveryLineGroupProcessing(
+          delivery_line, group, collect_order_list[1:], property_dict.copy())
+    else:
+      movement_list = movement_group.getMovementList()
+      if len(movement_list) != 1:
+        raise "CollectError", "DeliveryBuilder: %s unable to distinct those\
+              movements: %s" % (self.getId(), str(movement_list))
+      else:
+        # decide if we create a cell or if we update the line
+        # Decision can only be made with variation_category_list
+        movement = movement_list[0]
+        movement_variation_category_list = movement.getVariationCategoryList()
+        if movement_variation_category_list == []:
+          # update line
+          object_to_update = delivery_line
+        else:
+          # create a new cell
+          base_id = 'movement'
+          cell_key = movement_variation_category_list
+          if not delivery_line.hasCell(base_id=base_id, *cell_key ):
+            cell = delivery_line.newCell(base_id=base_id,\
+                       portal_type=self.getDeliveryCellPortalType(), *cell_key)
+            cell.setCategoryList(cell_key)
+            # XXX hardcoded value
+            cell.setMappedValuePropertyList(['quantity', 'price'])
+            cell.setMembershipCriterionCategoryList(cell_key)
+            cell.setMembershipCriterionBaseCategoryList(movement.\
+                                          getVariationBaseCategoryList())
+            object_to_update = cell
+          else:
+            raise 'MatrixError', 'Cell: %s already exists on %s' %\
+                  (str(cell_key), str(delivery_line))
+
+        # XXX hardcoded value
+        # Now, only 1 movement is possible, so copy from this movement
+        property_dict['quantity'] = movement.getQuantity()
+        property_dict['price'] = movement.getPrice()
+                  
+        # Update properties on object (quantity, price...)
+        object_to_update.edit(**property_dict)
+
+        # Update simulation movement
+        movement._setDeliveryValue(object_to_update)
diff --git a/product/ERP5/PropertySheet/DeliveryBuilder.py b/product/ERP5/PropertySheet/DeliveryBuilder.py
new file mode 100755
index 0000000000..03c181eb98
--- /dev/null
+++ b/product/ERP5/PropertySheet/DeliveryBuilder.py
@@ -0,0 +1,87 @@
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Romain Courteaud <romain@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+from Products.CMFCore.Expression import Expression
+
+class DeliveryBuilder:
+  """
+    Properties which allow to define a generic Delivery Builder.
+  """
+  _properties = (
+    { 'id'          : 'delivery_module',
+      'description' : 'Define the module name where to create the delivery.',
+      'type'        : 'string',
+      'mode'        : 'w' },
+    { 'id'          : 'delivery_portal_type',
+      'description' : 'Define the portal type of the delivery.',
+      'type'        : 'string',
+      'mode'        : 'w' },
+    { 'id'          : 'delivery_line_portal_type',
+      'description' : 'Define the portal type of the delivery line.',
+      'type'        : 'string',
+      'mode'        : 'w' },
+    { 'id'          : 'delivery_cell_portal_type',
+      'description' : 'Define the portal type of the delivery cell.',
+      'type'        : 'string',
+      'mode'        : 'w' },
+
+    { 'id'          : 'resource_portal_type',
+      'description' : 'Define the portal type of the resource.',
+      'type'        : 'string',
+      'mode'        : 'w' },
+
+    { 'id'          : 'simulation_select_method_id',
+      'description' : 'defines how to query all Simulation Movements which\
+                       meet certain criteria',
+      'type'        : 'string',
+      'mode'        : 'w' },
+    { 'id'          : 'delivery_collect_order',
+      'description' : 'defines how to group selected movements according\
+                       to gathering rules',
+      'type'        : 'string',
+      'multivalued' : 1,
+      'mode'        : 'w' },
+    { 'id'          : 'delivery_line_collect_order',
+      'description' : 'defines how to group selected movements according\
+                       to gathering rules',
+      'type'        : 'string',
+      'multivalued' : 1,
+      'mode'        : 'w' },
+    { 'id'          : 'delivery_cell_collect_order',
+      'description' : 'defines how to group selected movements according\
+                       to gathering rules',
+      'type'        : 'string',
+      'multivalued' : 1,
+      'mode'        : 'w' },
+    { 'id'          : 'delivery_select_method_id',
+      'description' : 'defines how to select existing Delivery which may\
+                       eventually be updated with selected simulation\
+                       movements',
+      'type'        : 'string',
+      'mode'        : 'w' },
+  )
-- 
2.30.9