[Erp5-report] r30631 - /erp5/trunk/products/ERP5/mixin/rule.py

nobody at svn.erp5.org nobody at svn.erp5.org
Sun Nov 15 18:33:21 CET 2009


Author: jp
Date: Sun Nov 15 18:33:21 2009
New Revision: 30631

URL: http://svn.erp5.org?rev=30631&view=rev
Log:
Initial upload with some draft pseudo code. (incomplete) which show how _buildMovementCollectionDiff will become.

Added:
    erp5/trunk/products/ERP5/mixin/rule.py

Added: erp5/trunk/products/ERP5/mixin/rule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/mixin/rule.py?rev=30631&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/mixin/rule.py (added)
+++ erp5/trunk/products/ERP5/mixin/rule.py [utf8] Sun Nov 15 18:33:21 2009
@@ -1,0 +1,275 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsibility 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
+# guarantees 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+##############################################################################
+
+import zope.interface
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, interfaces
+
+class RuleMixin:
+  """
+  Provides generic methods and helper methods to implement
+  IRule and 
+  """
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Declarative interfaces
+  zope.interface.implements(interfaces.IRule,
+                            interfaces.IMovementCollectionUpdater,)
+
+  # Implementation of IRule
+  def constructNewAppliedRule(self, context, id=None, 
+                              activate_kw=None, **kw):
+    """
+    Create a new applied rule in the context.
+
+    An applied rule is an instanciation of a Rule. The applied rule is
+    linked to the Rule through the `specialise` relation. The newly
+    created rule should thus point to self.
+
+    context -- usually, a parent simulation movement of the
+               newly created applied rule
+
+    activate_kw -- activity parameters, required to control
+                   activity constraints
+
+    kw -- XXX-JPS probably wrong interface specification
+    """
+    portal_types = getToolByName(self, 'portal_types')
+    if id is None:
+      id = context.generateNewId()
+    if getattr(aq_base(context), id, None) is None:
+      context.newContent(id=id,
+                         portal_type='Applied Rule',
+                         specialise_value=self,
+                         activate_kw=activate_kw)
+    return context.get(id)
+
+  def expand(self, applied_rule, **kw):
+    """
+    Expand this applied rule to create new documents inside the
+    applied rule.
+
+    At expand time, we must replace or compensate certain
+    properties. However, if some properties were overwriten
+    by a decision (ie. a resource if changed), then we
+    should not try to compensate such a decision.
+    """
+    # Update movements
+    #  NOTE-JPS: it is OK to make rounding a standard parameter of rules
+    #            altough rounding in simulation is not recommended at all
+    self.updateMovementCollection(applied_rule, movement_generator=self._geMovementGenerator())
+    # And forward expand
+    for movement in applied_rule.getMovementList():
+      movement.expand(**kw)      
+
+  # Implementation of IMovementCollectionUpdater
+  def getMovementCollectionDiff(self, context, rounding=False, movement_generator=None):
+    """
+    Return a IMovementCollectionDiff by comparing movements
+    the list of movements of context and the list of movements
+    generated by movement_generator on context.
+
+    context -- an IMovementCollection usually, possibly
+               an IMovementList or an IMovement
+
+    movement_generator -- an optional IMovementGenerator
+                          (if not specified, a context implicit 
+                          IMovementGenerator will be used)
+    """
+    # We suppose here that we have an IMovementCollection in hand
+    decision_movement_list = context.getMovementList()
+    prevision_movement_list = movement_generator(self._geMovementGeneratorContext(),
+            movement_list=self._geMovementGeneratorMovementList(), rounding=rounding)
+
+    # Get divergence testers
+    tester_list = self._getMatchingTesterList()
+    if len(tester_list) == 0:
+      raise ValueError("It is not possible to match movements without divergence testers")
+
+    # Create small groups of movements per hash keys
+    decision_movement_dict = {}
+    for movement in decision_movement_list:
+      tester_key = []
+      for tester in tester_list:
+        if tester.test(movement):
+          tester_key.append(tester.generateHashKey(movement))
+        else:
+          tester_key.append(None)
+      tester_key = tuple(tester_key)
+      decision_movement_dict.setdefaults(tester_key, []).append(movement)
+    prevision_movement_dict = {}
+    for movement in prevision_movement_list:
+      tester_key = []
+      for tester in tester_list:
+        if tester.test(movement):
+          tester_key.append(tester.generateHashKey(movement))
+        else:
+          tester_key.append(None)
+      tester_key = tuple(tester_key)
+      prevision_movement_dict.setdefaults(tester_key, []).append(movement)
+
+    # Build the diff
+    movement_collection_diff = self._buildMovementCollectionDiff(tester_list,
+                                                                 decision_movement_dict,
+                                                                 prevision_movement_dict,)
+
+    # Return result
+    return movement_collection_diff
+                  
+  def updateMovementCollection(self, context, rounding=False, movement_generator=None):
+    """
+    Invoke getMovementCollectionDiff and update context with 
+    the resulting IMovementCollectionDiff.
+
+    context -- an IMovementCollection usually, possibly
+               an IMovementList or an IMovement
+
+    movement_generator -- an optional IMovementGenerator
+                          (if not specified, a context implicit 
+                          IMovementGenerator will be used)
+    """
+    movement_diff = self.getMovementCollectionDiff(context, 
+                 rounding=rounding, movement_generator=movement_generator)
+
+    # Apply Diff
+    for movement in movement_diff.getDeletableMovementList():
+      movement.getParentValue().deleteContent(movement.getId())
+    for movement in movement_diff.getUpdatableMovementList():
+      kw = movement_diff.getMovementPropertyDict(movement)
+      movement.edit(**kw)
+    for movement in movement_diff.getNewMovementList():
+      # This cas is easy, cause it is an applied rule
+      kw = movement_diff.getMovementPropertyDict(movement)
+      movement = context.newContent(portal_type='Simulation Movement')
+      movement.edit(**kw)
+      
+  # Placeholder for methods to override
+  def _geMovementGenerator(self):
+    """
+    Return the movement generator to use in the expand process
+    """
+    raise NotImplementedError
+
+  def _geMovementGeneratorContext(self):
+    """
+    Return the movement generator context to use for expand
+    """
+    raise NotImplementedError
+
+  def _geMovementGeneratorMovementList(self):
+    """
+    Return the movement lists to provide to the movement generator
+    """
+    raise NotImplementedError
+
+  def _getMatchingTesterList(self):
+    """
+    Return the applicable divergence testers which must 
+    be used to match movements and build the diff (ie.
+    not all divergence testers of the Rule)
+    """
+    raise NotImplementedError
+
+  def _getQuantityTesterList(self):
+    """
+    Return the applicable divergence testers which must 
+    be used to match movements and build the diff (ie.
+    not all divergence testers of the Rule)
+    """
+    raise NotImplementedError
+
+  def _buildMovementCollectionDiff(self, tester_list,
+                     decision_movement_dict, prevision_movement_dict):
+    """
+    Build the diff of movements, based on the rule policy
+    which must take into account for example the risk
+    of theft. This is the most important method to override.
+
+    NOTE: XXX-JPS there is probably a way to make parts of this generic
+    and make implementation even easier and simpler.
+    """
+    raise NotImplementedError
+    # Sample implementation bellow
+    def _compare(tester_list, prevision_movement, decision_movement):
+      for tester in tester_list:
+        if not tester.compare(prevision_movement, decision_movement):
+          return False
+      return True
+
+    # Find movements to add or compensate (all updates go through compensate)
+    new_movement_list = []
+    compensation_movement_list = []
+    for tester_key in prevision_movement_dict.keys():
+      if decision_movement_dict.has_key(tester_key):
+        # We have found now a small group of movements with same key
+        #  we can now start comparing
+        for prevision_movement in prevision_movement_dict[tester_key]:
+          prevision_quantity = prevision_movement.getQuantity()
+          decision_quantity = 0.0
+          compare_movement = None
+          for decision_movement in decision_movement_dict[tester_key]:
+            if  _compare(tester_list, prevision_movement, decision_movement):
+              decision_quantity += decision_movement.getQuantity()
+              compare_movement = decision_movement
+          # Test globaly
+          if compare_movement is None:
+            new_movement_list.append(prevision_movement)
+          else:
+            # Build a fake movement with total quantity
+            compare_movement = compare_movement.asContext(quantity=decision_quantity)
+            # And compare it to the expected quantity
+            if not _compare(self._getQuantityTesterList(), prevision_movement, compare_movement):
+              # Find any movement which is no frozen and update
+              updated_movement = None
+              for decision_movement in decision_movement_dict[tester_key]:
+                if not decision_movement.isFrozen():
+                  updated_movement = decision_movement
+                  decision_movement.setQuantity(prevision_quantity-decision_quantity)
+                  break
+              if updated_movement is not None:
+                updatable_list.append(decision_movement, {})
+              else:
+                # Else compensate
+                compensation_movement_list.append(compare_movement.asContext(
+                                     quantity=prevision_quantity-decision_quantity))
+      else;
+        new_movement_list.extend(prevision_movement_dict[tester_key])
+
+    # Find movements to delete or cancel by compensating
+    for tester_key in decision_movement_dict.keys():
+      if prevision_movement_dict.has_key(tester_key):
+        for movement in decision_movement_dict[tester_key]:
+          quantity = 0
+          for decision_movement in decision_movement_dict[tester_key]:
+            pass
+      else:
+        # The movement is no longer present
+        for movement in decision_movement_dict[tester_key]:
+          




More information about the Erp5-report mailing list