[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