[Erp5-report] r28517 - /erp5/trunk/products/ERP5/Document/
nobody at svn.erp5.org
nobody at svn.erp5.org
Thu Aug 20 16:28:00 CEST 2009
Author: luke
Date: Thu Aug 20 16:27:58 2009
New Revision: 28517
URL: http://svn.erp5.org?rev=28517&view=rev
Log:
Rule.py
- update copyrights, fix some typos
- declare encoding
- cleanup imports
- incorporate BPMRule functionality
- define which helpers are override able, which aren't
BPMRule.py
- remove, because Rule.py provide same functionality
BPMInvoiceTransactionRule.py
BPMInvoicingRule.py
BPMOrderRule.py
BPMDeliveryRule.py
- use Rule instead of BPMRule
- remove not needed disclaimer
Removed:
erp5/trunk/products/ERP5/Document/BPMRule.py
Modified:
erp5/trunk/products/ERP5/Document/BPMDeliveryRule.py
erp5/trunk/products/ERP5/Document/BPMInvoiceTransactionRule.py
erp5/trunk/products/ERP5/Document/BPMInvoicingRule.py
erp5/trunk/products/ERP5/Document/BPMOrderRule.py
erp5/trunk/products/ERP5/Document/Rule.py
Modified: erp5/trunk/products/ERP5/Document/BPMDeliveryRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BPMDeliveryRule.py?rev=28517&r1=28516&r2=28517&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/BPMDeliveryRule.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/BPMDeliveryRule.py [utf8] Thu Aug 20 16:27:58 2009
@@ -31,12 +31,10 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
-from Products.ERP5.Document.BPMRule import BPMRule
+from Products.ERP5.Document.Rule import Rule
-class BPMDeliveryRule(BPMRule):
+class BPMDeliveryRule(Rule):
"""
- DISCLAIMER: Refer to BPMRule docstring disclaimer.
-
This is BPM enabled Delivery Rule.
"""
Modified: erp5/trunk/products/ERP5/Document/BPMInvoiceTransactionRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BPMInvoiceTransactionRule.py?rev=28517&r1=28516&r2=28517&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/BPMInvoiceTransactionRule.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/BPMInvoiceTransactionRule.py [utf8] Thu Aug 20 16:27:58 2009
@@ -30,13 +30,11 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
-from Products.ERP5.Document.BPMRule import BPMRule
+from Products.ERP5.Document.Rule import Rule
from Products.ERP5.Document.PredicateMatrix import PredicateMatrix
-class BPMInvoiceTransactionRule(BPMRule, PredicateMatrix):
+class BPMInvoiceTransactionRule(Rule, PredicateMatrix):
"""
- DISCLAIMER: Refer to BPMRule docstring disclaimer.
-
This is BPM enabled Invoice Transaction Rule.
"""
Modified: erp5/trunk/products/ERP5/Document/BPMInvoicingRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BPMInvoicingRule.py?rev=28517&r1=28516&r2=28517&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/BPMInvoicingRule.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/BPMInvoicingRule.py [utf8] Thu Aug 20 16:27:58 2009
@@ -32,12 +32,10 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
-from Products.ERP5.Document.BPMRule import BPMRule
+from Products.ERP5.Document.Rule import Rule
-class BPMInvoicingRule(BPMRule):
+class BPMInvoicingRule(Rule):
"""
- DISCLAIMER: Refer to BPMRule docstring disclaimer.
-
This is BPM enabled Invoicing Rule
"""
Modified: erp5/trunk/products/ERP5/Document/BPMOrderRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BPMOrderRule.py?rev=28517&r1=28516&r2=28517&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/BPMOrderRule.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/BPMOrderRule.py [utf8] Thu Aug 20 16:27:58 2009
@@ -35,8 +35,6 @@
class BPMOrderRule(BPMDeliveryRule):
"""
- DISCLAIMER: Refer to BPMRule docstring disclaimer.
-
This is BPM enabled Order Rule.
"""
# CMF Type Definition
Removed: erp5/trunk/products/ERP5/Document/BPMRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BPMRule.py?rev=28516&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/Document/BPMRule.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/BPMRule.py (removed)
@@ -1,411 +1,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
-# Jean-Paul Smets-Solanes <jp at nexedi.com>
-# Łukasz Nowak <luke at 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.
-#
-##############################################################################
-
-import zope.interface
-from AccessControl import ClassSecurityInfo
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
-from Products.ERP5.Document.Predicate import Predicate
-from Products.ERP5Type.XMLObject import XMLObject
-from Acquisition import aq_base
-from Products.CMFCore.utils import getToolByName
-from zLOG import LOG
-
-class BPMRule(Predicate, XMLObject):
- """
- DISCLAIMER: Do not use this in any production system.
- This is only proof of concept and evaluation of new system
- design. Implementation and API can change without any
- further warning.
-
- *DO NOT USE IN PRODUCTION SYSTEM*
-
- This is BPM enabled Rule system Base class.
- """
-
- # CMF Type Definition
- meta_type = 'ERP5 BPM Rule'
- portal_type = 'BPM Rule'
- add_permission = Permissions.AddPortalContent
- isPortalContent = 1
- isRADContent = 1
- isPredicate = 1
-
- # Declarative security
- security = ClassSecurityInfo()
- security.declareObjectProtected(Permissions.AccessContentsInformation)
-
- zope.interface.implements( interfaces.IPredicate,
- interfaces.IRule )
-
- # Default Properties
- property_sheets = ( PropertySheet.Base
- , PropertySheet.XMLObject
- , PropertySheet.CategoryCore
- , PropertySheet.DublinCore
- , PropertySheet.Task
- , PropertySheet.Predicate
- , PropertySheet.Reference
- , PropertySheet.Version
- , PropertySheet.Rule
- )
-
- movement_type = 'Simulation Movement'
-
- security.declareProtected(Permissions.AccessContentsInformation,
- 'isAccountable')
- def isAccountable(self, movement):
- """Tells wether generated movement needs to be accounted or not.
-
- Only account movements which are not associated to a delivery;
- Whenever delivery is there, delivery has priority
- """
- return movement.getDeliveryValue() is None
-
- security.declareProtected(Permissions.ModifyPortalContent,
- 'constructNewAppliedRule')
- def constructNewAppliedRule(self, context, id=None, activate_kw=None):
- """
- Creates a new applied rule which points to self
- """
- 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)
-
- # Simulation workflow
- def test(self, *args, **kw):
- """
- If no test method is defined, return False, to prevent infinite loop
- """
- if not self.getTestMethodId():
- return False
- return Predicate.test(self, *args, **kw)
-
- # Solvers
- security.declareProtected( Permissions.AccessContentsInformation,
- 'isDivergent')
- def isDivergent(self, simulation_movement, ignore_list=[]):
- """
- Returns true if the Simulation Movement is divergent comparing to
- the delivery value
- """
- delivery = simulation_movement.getDeliveryValue()
- if delivery is None:
- return 0
-
- if self.getDivergenceList(simulation_movement) == []:
- return 0
- else:
- return 1
-
- security.declareProtected(Permissions.View, 'getDivergenceList')
- def getDivergenceList(self, simulation_movement):
- """
- Return a list of messages that contains the divergences.
- """
- result_list = []
- for divergence_tester in self.contentValues(
- portal_type=self.getPortalDivergenceTesterTypeList()):
- result = divergence_tester.explain(simulation_movement)
- result_list.extend(result)
- return result_list
-
- # Deliverability / orderability
- def isOrderable(self, movement):
- return 0
-
- def isDeliverable(self, movement):
- return 0
-
- def isStable(self, applied_rule, **kw):
- """
- - generate a list of previsions
- - compare the prevision with existing children
- - return 1 if they match, 0 else
- """
- list = self._getCompensatedMovementList(applied_rule, **kw)
- for e in list:
- if len(e) > 0:
- return 0
- return 1
-
-#### Helpers to overload
- def _getExpandablePropertyUpdateDict(self, applied_rule, movement,
- business_path, current_property_dict):
- """Rule specific dictionary used to update _getExpandablePropertyDict
- This method might be overloaded.
- """
- return {}
-
- def _getInputMovementList(self, applied_rule):
- """Return list of movements for applied rule.
- This method might be overloaded"""
- return [applied_rule.getParentValue()]
-
- def _generatePrevisionList(self, applied_rule, **kw):
- """
- Generate a list of dictionaries, that contain calculated content of
- current Simulation Movements in applied rule.
- based on its context (parent movement, delivery, configuration ...)
-
- These previsions are returned as dictionaries.
- """
- prevision_dict_list = []
- for input_movement, business_path in self \
- ._getInputMovementAndPathTupleList(applied_rule):
- prevision_dict_list.append(self._getExpandablePropertyDict(applied_rule,
- input_movement, business_path))
- return prevision_dict_list
-
-#### Helpers NOT to overload
- def _getCurrentMovementList(self, applied_rule, **kw):
- """
- Returns the list of current children of the applied rule, sorted in 3
- groups : immutable/mutable/deletable
-
- * immutable is frozen
- * mutable is not frozen, but delivered
- * deletable is not frozen and not delivered
-
- Delivered means movement is delivered or any of its children is delivered.
- """
- immutable_movement_list = []
- mutable_movement_list = []
- deletable_movement_list = []
-
- for movement in applied_rule.contentValues(
- portal_type=self.movement_type):
- if movement.isFrozen():
- immutable_movement_list.append(movement)
- else:
- if movement._isTreeDelivered():
- mutable_movement_list.append(movement)
- else:
- deletable_movement_list.append(movement)
-
- return (immutable_movement_list, mutable_movement_list,
- deletable_movement_list)
-
- def _getInputMovementAndPathTupleList(self, applied_rule):
- """Returns list of tuples (movement, business_path)"""
- input_movement_list = self._getInputMovementList(applied_rule)
- business_process = applied_rule.getBusinessProcessValue()
-
- input_movement_and_path_list = []
- business_path_list = []
- for input_movement in input_movement_list:
- for business_path in business_process.getPathValueList(
- self.getTradePhaseList(),
- input_movement):
- input_movement_and_path_list.append((input_movement, business_path))
- business_path not in business_path_list and business_path_list \
- .append(business_path)
-
- if len(business_path_list) > 1:
- raise NotImplementedError
-
- return input_movement_and_path_list
-
- def _getCompensatedMovementList(self, applied_rule, **kw):
- """Compute the difference between prevision and existing movements
-
- Immutable movements need compensation, mutable ones needs to be modified
-
- XXX For now, this implementation is too simple. It could be improved by
- using MovementGroups
- """
- add_list = [] # list of movements to be added
- modify_dict = {} # dict of movements to be modified
- delete_list = [] # list of movements to be deleted
-
- prevision_list = self._generatePrevisionList(applied_rule, **kw)
- immutable_movement_list, mutable_movement_list, \
- deletable_movement_list = self._getCurrentMovementList(applied_rule,
- **kw)
- movement_list = immutable_movement_list + mutable_movement_list \
- + deletable_movement_list
- non_matched_list = movement_list[:] # list of remaining movements
-
- for prevision in prevision_list:
- p_matched_list = []
- for movement in non_matched_list:
- for prop in self.getMatchingPropertyList():
- if prevision.get(prop) != movement.getProperty(prop):
- break
- else:
- p_matched_list.append(movement)
-
- # Movements exist, we'll try to make them match the prevision
- if p_matched_list != []:
- # Check the quantity
- m_quantity = 0.0
- for movement in p_matched_list:
- m_quantity += movement.getQuantity()#getCorrectedQuantity()
- if m_quantity != prevision.get('quantity'):
- # special case - quantity
- if movement.isPropertyForced('quantity'):
- # TODO: support compensation if not prevent_compensation
- LOG('%s:%s' % (self.getRelativeUrl(), movement.getRelativeUrl()), 100,
- 'Quantity forced to stay as %s, even if wanted %s' % (m_quantity, prevision.get('quantity')))
- # DivergenceDecision mangle
- pass
- else:
- q_diff = prevision.get('quantity') - m_quantity
- # try to find a movement that can be edited
- for movement in p_matched_list:
- if movement in (mutable_movement_list \
- + deletable_movement_list):
- # mark as requiring modification
- prop_dict = modify_dict.setdefault(movement.getId(), {})
- #prop_dict['quantity'] = movement.getCorrectedQuantity() + \
- prop_dict['quantity'] = movement.getQuantity() + \
- q_diff
- break
- else:
- # no modifiable movement was found, need to compensate by quantity
- raise NotImplementedError('Need to generate quantity compensation')
-
- for movement in p_matched_list:
- if movement in (mutable_movement_list \
- + deletable_movement_list):
- prop_dict = modify_dict.setdefault(movement.getId(), {})
- for k, v in prevision.items():
- if k not in ('quantity',) and v != movement.getProperty(k):
- # TODO: acceptance range
- if movement.isPropertyForced(k):
- # support compensation if not prevent_compensation
- LOG('%s:%s' % (self.getRelativeUrl(), movement.getRelativeUrl()), 100,
- 'Property %s forced to stay as %r, even if wanted %r' % (k, movement.getProperty(k), v))
- # DivergenceDecision mangle
- continue
- prop_dict.setdefault(k, v)
-
- # update movement lists
- for movement in p_matched_list:
- non_matched_list.remove(movement)
-
- # No movement matched, we need to create one
- else:
- add_list.append(prevision)
-
- # delete non matched movements
- for movement in non_matched_list:
- if movement in deletable_movement_list:
- # delete movement
- delete_list.append(movement.getId())
- elif movement in mutable_movement_list:
- # set movement quantity to 0 to make it "void"
- prop_dict = modify_dict.setdefault(movement.getId(), {})
- prop_dict['quantity'] = 0.0
- else:
- # movement not modifiable, we can decide to create a compensation
- # with negative quantity
- raise NotImplementedError("Tried to delete immutable movement %s" % \
- movement.getRelativeUrl())
- return (add_list, modify_dict, delete_list)
-
- def _getExpandablePropertyDict(self, applied_rule, movement, business_path,
- **kw):
- """
- Return a Dictionary with the Properties used to edit the simulation
- Do NOT overload this method, use _getExpandablePropertyUpdateDict instead
- """
- property_dict = {}
-
- default_property_list = self.getExpandablePropertyList()
- for prop in default_property_list:
- property_dict[prop] = movement.getProperty(prop)
-
- # Arrow
- for base_category in \
- business_path.getSourceBaseCategoryList() +\
- business_path.getDestinationBaseCategoryList():
- # XXX: we need to use _list for categories *always*
- category_url = business_path.getDefaultAcquiredCategoryMembership(
- base_category, context=movement)
- if category_url not in ['', None]:
- property_dict['%s_list' % base_category] = [category_url]
- else:
- property_dict['%s_list' % base_category] = []
- # Amount
- if business_path.getQuantity():
- property_dict['quantity'] = business_path.getQuantity()
- elif business_path.getEfficiency():
- property_dict['quantity'] = movement.getQuantity() *\
- business_path.getEfficiency()
- else:
- property_dict['quantity'] = movement.getQuantity()
-
- if movement.getStartDate() == movement.getStopDate():
- property_dict['start_date'] = business_path.getExpectedStartDate(
- movement)
- property_dict['stop_date'] = business_path.getExpectedStopDate(movement)
- else: # XXX shall not be used, but business_path.getExpectedStart/StopDate
- # do not works on second path...
- property_dict['start_date'] = movement.getStartDate()
- property_dict['stop_date'] = movement.getStopDate()
-
- # rule specific
- property_dict.update(**self._getExpandablePropertyUpdateDict(applied_rule,
- movement, business_path, property_dict))
-
- # save a relation to business path
- property_dict['causality_list'] = [business_path.getRelativeUrl()]
-
- return property_dict
-
- security.declareProtected(Permissions.ModifyPortalContent, 'expand')
- def expand(self, applied_rule, force=0, **kw):
- """Generic expand with helpers.
- Do NOT overload, use helpers."""
- add_list, modify_dict, \
- delete_list = self._getCompensatedMovementList(applied_rule, **kw)
-
- # delete not needed movements
- for movement_id in delete_list:
- applied_rule._delObject(movement_id)
-
- # update existing
- for movement, property_dict in modify_dict.items():
- applied_rule[movement].edit(**property_dict)
-
- # add new ones
- for movement_dict in add_list:
- movement_id = applied_rule._get_id(movement_dict.pop('id', None))
- new_movement = applied_rule.newContent(id=movement_id,
- portal_type=self.movement_type, **movement_dict)
-
- for o in applied_rule.objectValues():
- o.expand(**kw)
Modified: erp5/trunk/products/ERP5/Document/Rule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/Rule.py?rev=28517&r1=28516&r2=28517&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/Rule.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/Rule.py [utf8] Thu Aug 20 16:27:58 2009
@@ -1,13 +1,15 @@
+# -*- coding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
+# Copyright (c) 2002-2009 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp at nexedi.com>
+# Łukasz Nowak <luke at nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
+# 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
-# garantees and support are strongly adviced to contract a Free Software
+# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
@@ -29,10 +31,10 @@
import zope.interface
from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName
-from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces
+from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5.Document.Predicate import Predicate
-from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
+from Acquisition import aq_base
from zLOG import LOG, WARNING
class Rule(Predicate, XMLObject):
@@ -120,6 +122,10 @@
activate_kw=activate_kw)
return context.get(id)
+ def _isBPM(self):
+ """Checks if rule is used in BPM"""
+ return bool(self.getTradePhaseList())
+
# Simulation workflow
def test(self, *args, **kw):
"""
@@ -129,6 +135,29 @@
return False
return Predicate.test(self, *args, **kw)
+ def _expandBPM(self, applied_rule, force=0, **kw):
+ """Generic expand with helpers.
+ Do NOT overload, use helpers."""
+ add_list, modify_dict, \
+ delete_list = self._getCompensatedMovementList(applied_rule, **kw)
+
+ # delete not needed movements
+ for movement_id in delete_list:
+ applied_rule._delObject(movement_id)
+
+ # update existing
+ for movement, property_dict in modify_dict.items():
+ applied_rule[movement].edit(**property_dict)
+
+ # add new ones
+ for movement_dict in add_list:
+ movement_id = applied_rule._get_id(movement_dict.pop('id', None))
+ new_movement = applied_rule.newContent(id=movement_id,
+ portal_type=self.movement_type, **movement_dict)
+
+ for o in applied_rule.objectValues():
+ o.expand(**kw)
+
security.declareProtected(Permissions.ModifyPortalContent, 'expand')
def expand(self, applied_rule, **kw):
"""
@@ -137,6 +166,8 @@
An applied rule can be expanded only if its parent movement
is expanded.
"""
+ if self._isBPM():
+ return self._expandBPM(applied_rule, **kw)
for o in applied_rule.objectValues():
o.expand(**kw)
@@ -221,7 +252,35 @@
return 0
return 1
-#### Helpers
+#### Helpers to overload
+ def _getExpandablePropertyUpdateDict(self, applied_rule, movement,
+ business_path, current_property_dict):
+ """Rule specific dictionary used to update _getExpandablePropertyDict
+ This method might be overloaded.
+ """
+ return {}
+
+ def _getInputMovementList(self, applied_rule):
+ """Return list of movements for applied rule.
+ This method might be overloaded"""
+ return [applied_rule.getParentValue()]
+
+ def _generatePrevisionList(self, applied_rule, **kw):
+ """
+ Generate a list of dictionaries, that contain calculated content of
+ current Simulation Movements in applied rule.
+ based on its context (parent movement, delivery, configuration ...)
+
+ These previsions are returned as dictionaries.
+ """
+ prevision_dict_list = []
+ for input_movement, business_path in self \
+ ._getInputMovementAndPathTupleList(applied_rule):
+ prevision_dict_list.append(self._getExpandablePropertyDict(applied_rule,
+ input_movement, business_path))
+ return prevision_dict_list
+
+#### Helpers NOT to overload
def _getCurrentMovementList(self, applied_rule, **kw):
"""
Returns the list of current children of the applied rule, sorted in 3
@@ -258,6 +317,121 @@
return (immutable_movement_list, mutable_movement_list,
deletable_movement_list)
+ def _getInputMovementAndPathTupleList(self, applied_rule):
+ """Returns list of tuples (movement, business_path)"""
+ input_movement_list = self._getInputMovementList(applied_rule)
+ business_process = applied_rule.getBusinessProcessValue()
+
+ input_movement_and_path_list = []
+ business_path_list = []
+ for input_movement in input_movement_list:
+ for business_path in business_process.getPathValueList(
+ self.getTradePhaseList(),
+ input_movement):
+ input_movement_and_path_list.append((input_movement, business_path))
+ business_path not in business_path_list and business_path_list \
+ .append(business_path)
+
+ if len(business_path_list) > 1:
+ raise NotImplementedError
+
+ return input_movement_and_path_list
+
+ def _getCompensatedMovementListBPM(self, applied_rule, **kw):
+ """Compute the difference between prevision and existing movements
+
+ Immutable movements need compensation, mutable ones needs to be modified
+ """
+ add_list = [] # list of movements to be added
+ modify_dict = {} # dict of movements to be modified
+ delete_list = [] # list of movements to be deleted
+
+ prevision_list = self._generatePrevisionList(applied_rule, **kw)
+ immutable_movement_list, mutable_movement_list, \
+ deletable_movement_list = self._getCurrentMovementList(applied_rule,
+ **kw)
+ movement_list = immutable_movement_list + mutable_movement_list \
+ + deletable_movement_list
+ non_matched_list = movement_list[:] # list of remaining movements
+
+ for prevision in prevision_list:
+ p_matched_list = []
+ for movement in non_matched_list:
+ for prop in self.getMatchingPropertyList():
+ if prevision.get(prop) != movement.getProperty(prop):
+ break
+ else:
+ p_matched_list.append(movement)
+
+ # Movements exist, we'll try to make them match the prevision
+ if p_matched_list != []:
+ # Check the quantity
+ m_quantity = 0.0
+ for movement in p_matched_list:
+ m_quantity += movement.getQuantity()#getCorrectedQuantity()
+ if m_quantity != prevision.get('quantity'):
+ # special case - quantity
+ if movement.isPropertyForced('quantity'):
+ # TODO: support compensation if not prevent_compensation
+ LOG('%s:%s' % (self.getRelativeUrl(), movement.getRelativeUrl()), WARNING,
+ 'Quantity forced to stay as %s, even if wanted %s' % (m_quantity, prevision.get('quantity')))
+ # DivergenceDecision mangle
+ pass
+ else:
+ q_diff = prevision.get('quantity') - m_quantity
+ # try to find a movement that can be edited
+ for movement in p_matched_list:
+ if movement in (mutable_movement_list \
+ + deletable_movement_list):
+ # mark as requiring modification
+ prop_dict = modify_dict.setdefault(movement.getId(), {})
+ #prop_dict['quantity'] = movement.getCorrectedQuantity() + \
+ prop_dict['quantity'] = movement.getQuantity() + \
+ q_diff
+ break
+ else:
+ # no modifiable movement was found, need to compensate by quantity
+ raise NotImplementedError('Need to generate quantity compensation')
+
+ for movement in p_matched_list:
+ if movement in (mutable_movement_list \
+ + deletable_movement_list):
+ prop_dict = modify_dict.setdefault(movement.getId(), {})
+ for k, v in prevision.items():
+ if k not in ('quantity',) and v != movement.getProperty(k):
+ # TODO: acceptance range
+ if movement.isPropertyForced(k):
+ # support compensation if not prevent_compensation
+ LOG('%s:%s' % (self.getRelativeUrl(), movement.getRelativeUrl()), WARNING,
+ 'Property %s forced to stay as %r, even if wanted %r' % (k, movement.getProperty(k), v))
+ # DivergenceDecision mangle
+ continue
+ prop_dict.setdefault(k, v)
+
+ # update movement lists
+ for movement in p_matched_list:
+ non_matched_list.remove(movement)
+
+ # No movement matched, we need to create one
+ else:
+ add_list.append(prevision)
+
+ # delete non matched movements
+ for movement in non_matched_list:
+ if movement in deletable_movement_list:
+ # delete movement
+ delete_list.append(movement.getId())
+ elif movement in mutable_movement_list:
+ # set movement quantity to 0 to make it "void"
+ prop_dict = modify_dict.setdefault(movement.getId(), {})
+ prop_dict['quantity'] = 0.0
+ else:
+ # movement not modifiable, we can decide to create a compensation
+ # with negative quantity
+ raise NotImplementedError("Tried to delete immutable movement %s" % \
+ movement.getRelativeUrl())
+ return (add_list, modify_dict, delete_list)
+
def _getCompensatedMovementList(self, applied_rule,
matching_property_list=(
'resource',
@@ -271,6 +445,9 @@
XXX For now, this implementation is too simple. It could be improved by
using MovementGroups
"""
+ if self._isBPM():
+ return self._getCompensatedMovementListBPM(applied_rule,
+ matching_property_list=matching_property_list, **kw)
add_list = [] # list of movements to be added
modify_dict = {} # dict of movements to be modified
delete_list = [] # list of movements to be deleted
@@ -358,3 +535,55 @@
"Can not create a compensation movement for %s" % \
movement.getRelativeUrl())
return (add_list, modify_dict, delete_list)
+
+ def _getExpandablePropertyDict(self, applied_rule, movement, business_path,
+ **kw):
+ """
+ Return a Dictionary with the Properties used to edit the simulation
+ Do NOT overload this method, use _getExpandablePropertyUpdateDict instead
+ """
+ property_dict = {}
+
+ default_property_list = self.getExpandablePropertyList()
+ for prop in default_property_list:
+ property_dict[prop] = movement.getProperty(prop)
+
+ # Arrow
+ for base_category in \
+ business_path.getSourceBaseCategoryList() +\
+ business_path.getDestinationBaseCategoryList():
+ # XXX: we need to use _list for categories *always*
+ category_url = business_path.getDefaultAcquiredCategoryMembership(
+ base_category, context=movement)
+ if category_url not in ['', None]:
+ property_dict['%s_list' % base_category] = [category_url]
+ else:
+ property_dict['%s_list' % base_category] = []
+ # Amount
+ if business_path.getQuantity():
+ property_dict['quantity'] = business_path.getQuantity()
+ elif business_path.getEfficiency():
+ property_dict['quantity'] = movement.getQuantity() *\
+ business_path.getEfficiency()
+ else:
+ property_dict['quantity'] = movement.getQuantity()
+
+ if movement.getStartDate() == movement.getStopDate():
+ property_dict['start_date'] = business_path.getExpectedStartDate(
+ movement)
+ property_dict['stop_date'] = business_path.getExpectedStopDate(movement)
+ else: # XXX shall not be used, but business_path.getExpectedStart/StopDate
+ # do not works on second path...
+ property_dict['start_date'] = movement.getStartDate()
+ property_dict['stop_date'] = movement.getStopDate()
+
+ # rule specific
+ property_dict.update(**self._getExpandablePropertyUpdateDict(applied_rule,
+ movement, business_path, property_dict))
+
+ # save a relation to business path
+ property_dict['causality_list'] = [business_path.getRelativeUrl()]
+
+ return property_dict
+
+
More information about the Erp5-report
mailing list