Commit 95837573 authored by Jean-Paul Smets's avatar Jean-Paul Smets

Initial upload with some draft pseudo code. (incomplete) which show how...

Initial upload with some draft pseudo code. (incomplete) which show how _buildMovementCollectionDiff will become.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@30631 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 175087e5
# -*- 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]:
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment