diff --git a/product/ERP5/mixin/movement_generator.py b/product/ERP5/mixin/movement_generator.py deleted file mode 100644 index b15a9edeffa39ccd33ed350cef8100b8dece99e0..0000000000000000000000000000000000000000 --- a/product/ERP5/mixin/movement_generator.py +++ /dev/null @@ -1,201 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright (c) 2010 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. -# -############################################################################## - -from Products.ERP5.MovementCollectionDiff import _getPropertyAndCategoryList - -class MovementGeneratorMixin: - """ - This class provides a generic implementation of IMovementGenerator. - It is used by rules to generate a collection of movements from - the context of an applied rule. - - TODO: - Deliveries ? Movements ? Items ? - Does it depend by default on IAmountGeneratorMixin - """ - # Default values - _applied_rule = None - _rule = None - _trade_phase_list = None - - def __init__(self, applied_rule=None, rule=None, trade_phase_list=None): - self._trade_phase_list = trade_phase_list # XXX - self._applied_rule = applied_rule - if rule is None and applied_rule is not None: - self._rule = applied_rule.getSpecialiseValue() - else: - self._rule = rule # for rule specific stuff - - # Implementation of IMovementGenerator - def getGeneratedMovementList(self, movement_list=None, rounding=False): - """ - Returns an IMovementList generated by a model applied to the context - - context - an IMovementCollection, an IMovementList or an IMovement - - movement_list - optional IMovementList which can be passed explicitely - whenever context is an IMovementCollection and whenever - we want to filter context.getMovementList - - rounding - boolean argument, which controls if rounding shall be applied on - generated movements or not - - NOTE: - - implement rounding appropriately (True or False seems - simplistic) - """ - # Default implementation bellow can be overriden by subclasses - # however it should be generic enough not to be overriden - # by most classes - # Results will be appended to result, objects created inside folder - from Products.ERP5Type.Document import newTempMovement - result = [] - if self._applied_rule is None: - folder = self # Really useful ? - else: - folder = self._applied_rule - # Build a list of movement and business path - id_index = 0 - for input_movement, business_path in self \ - ._getInputMovementAndPathTupleList(movement_list=movement_list, - rounding=rounding): - # Merge movement and business path properties (core implementation) - kw = self._getPropertyAndCategoryDict(input_movement, business_path) - # Update movement properties (class overridable) - kw.update(self._getUpdatePropertyDict(input_movement)) - # And build temp movement of appropriate type - simulation_movement = newTempMovement(folder, - 'generated_movement_%s' % id_index) - simulation_movement._edit(**kw) - result.append(simulation_movement) - id_index += 1 - return result - - def _getUpdatePropertyDict(self, input_movement): - # Default implementation bellow can be overriden by subclasses - return {'delivery': input_movement.getRelativeUrl(), # XXX-JPS empty is better - } - - def _getTradePhaseList(self, input_movement, business_process): - if self._trade_phase_list: - return self._trade_phase_list - if self._rule: - self._rule.getTradePhaseList() - return business_process.getTradePhaseList() - - def _getInputMovementAndPathTupleList(self, movement_list=None, rounding=None): - """ - Returns list of tuples (movement, business_path) - """ - # Init result - result = [] - # First generate a list of movements with any appropriate method - input_movement_list = self._getInputMovementList(movement_list=movement_list, rounding=rounding) - # For each input movement - for input_movement in input_movement_list: - # Find its business process, if any - business_process = input_movement.asComposedDocument() # This produces a business process ideally - # Initialise trade phase list for a movement and business process - trade_phase_list = self._getTradePhaseList(input_movement, business_process) - - if business_process is None or len(trade_phase_list) == 0: - result.append((input_movement, None)) - else: - for business_path in business_process.getPathValueList( - trade_phase_list, - input_movement) or [None]: - result.append((input_movement, business_path)) - - return result - - def _getInputMovementList(self, movement_list=None, rounding=None): - raise NotImplementedError - # Default implementation takes amounts ? - # Use TradeModelRuleMovementGenerator._getInputMovementList as default implementation - # and potentially use trade phase for that.... as a way to filter out - - def _getPropertyAndCategoryDict(self, movement, business_path): - """ - Merge a movement and a business_path and return a dict of - properties and categories whch can be used to create a new movement. - - movement -- an IMovement instance - - business_path -- an IBusinessPath instance - - rule -- optional rule parameter which can be used to - narrow down properties to be copied - """ - rule = self._rule - if rule is None: - property_dict = _getPropertyAndCategoryList(movement) - else: - property_dict = {} - for tester in rule._getUpdatingTesterList(exclude_quantity=False): - property_dict.update(tester.getUpdatablePropertyDict( - movement, None)) - - if business_path is None: - return property_dict - - # Arrow - for base_category, category_url_list in \ - business_path.getArrowCategoryDict(context=movement).iteritems(): - property_dict[base_category] = category_url_list - - # 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() - - movement_start_date = movement.getStartDate() - movement_stop_date = movement.getStopDate() - if movement_start_date == movement_stop_date: - property_dict['start_date'] = business_path.getExpectedStartDate( - movement) - property_dict['stop_date'] = business_path.getExpectedStopDate(movement) - # in case of not fully working BPM get dates from movement - # XXX: as soon as BPM will be fully operational this hack will not be - # needed anymore - if property_dict['start_date'] is None: - property_dict['start_date'] = movement_start_date - if property_dict['stop_date'] is None: - property_dict['stop_date'] = movement_stop_date - else: # XXX shall not be used, but business_path.getExpectedStart/StopDate - # do not works on second path... - property_dict['start_date'] = movement_start_date - property_dict['stop_date'] = movement_stop_date - - # save a relation to business path - property_dict['causality'] = [business_path.getRelativeUrl()] - - return property_dict diff --git a/product/ERP5/mixin/rule.py b/product/ERP5/mixin/rule.py index 0363db9bca3568138e06bde81c788ed83abc094c..7634f118c2a382aec1cd3cc43b9570e01b5be64f 100644 --- a/product/ERP5/mixin/rule.py +++ b/product/ERP5/mixin/rule.py @@ -32,6 +32,9 @@ from Acquisition import aq_base from Products.CMFCore.utils import getToolByName from Products.ERP5Type import Permissions, interfaces from Products.ERP5.Document.Predicate import Predicate +from Products.ERP5.MovementCollectionDiff import _getPropertyAndCategoryList + +from zLOG import LOG def _compare(tester_list, prevision_movement, decision_movement): for tester in tester_list: @@ -39,6 +42,97 @@ def _compare(tester_list, prevision_movement, decision_movement): return False return True +class MovementGeneratorMixin: + """ + This class provides a generic implementation of IMovementGenerator + which can be used together the Rule mixin class bellow. It does not + have any pretention to provide more than that. + """ + # Default values + _applied_rule = None + _rule = None + _trade_phase_list = None + _explanation = None + + def __init__(self, applied_rule, explanation=None, rule=None, trade_phase_list=None): + self._trade_phase_list = trade_phase_list # XXX-JPS Why a list ? + self._applied_rule = applied_rule + if rule is None and applied_rule is not None: + self._rule = applied_rule.getSpecialiseValue() + else: + self._rule = rule # for rule specific stuff + if explanation is None: + self._explanation = applied_rule.getRootExplanationValue() + else: + self._explanation = explanation + # XXX-JPS handle delay_mode + + # Implementation of IMovementGenerator + def getGeneratedMovementList(self, movement_list=None, rounding=False): + """ + Returns an IMovementList generated by a model applied to the context + + context - an IMovementCollection, an IMovementList or an IMovement + + movement_list - optional IMovementList which can be passed explicitely + whenever context is an IMovementCollection and whenever + we want to filter context.getMovementList + + rounding - boolean argument, which controls if rounding shall be applied on + generated movements or not + + NOTE: + - implement rounding appropriately (True or False seems + simplistic) + """ + # Default implementation bellow can be overriden by subclasses + # however it should be generic enough not to be overriden + # by most classes + # Results will be appended to result, objects created inside folder + from Products.ERP5Type.Document import newTempMovement + result = [] + folder = self._applied_rule + # Build a list of movement and business path + for input_amount in self._getInputAmountList(movement_list=movement_list, + rounding=rounding): + # Merge movement and business path properties (core implementation) + LOG('getGeneratedMovementList input_movement', 0, repr(input_movement)) + # Lookup Business Process through composition (NOT UNION) + business_process = input_movement.asComposedDocument() + explanation = self._explanation + LOG('getGeneratedMovementList business_process', 0, repr(business_process)) + trade_phase = self._getTradePhaseList(input_movement, business_process) # XXX-JPS not convenient to handle + result.extend(business_process.getTradePhaseMovementList(explanation, input_amount, + trade_phase=trade_phase, delay_mode=None)) + + # Extend movement properties + for movement in result: + movement._edit(self._getUpdatePropertyDict(movement)) + + # And return list of generated movements + return result + + def _getUpdatePropertyDict(self, input_movement): + # Default implementation bellow can be overriden by subclasses + return {'delivery': input_movement.getRelativeUrl(), # XXX-JPS empty is better + } + + def _getTradePhaseList(self, input_movement, business_process): # XXX-JPS WEIRD + LOG('_getTradePhaseList _trade_phase_list', 0, str(self._trade_phase_list)) + if self._trade_phase_list: + return self._trade_phase_list + if self._rule: + LOG('_getTradePhaseList _trade_phase_list', 0, str(self._rule.getTradePhaseList())) + return self._rule.getTradePhaseList() + return business_process.getTradePhaseList() + + def _getInputMovementList(self, movement_list=None, rounding=None): + raise NotImplementedError + # Default implementation takes amounts ? + # Use TradeModelRuleMovementGenerator._getInputMovementList as default implementation + # and potentially use trade phase for that.... as a way to filter out + + class RuleMixin: """ Provides generic methods and helper methods to implement @@ -161,6 +255,7 @@ class RuleMixin: def _getMovementGeneratorContext(self, applied_rule): """ Return the movement generator context to use for expand + XXX-JPS likely useless """ raise NotImplementedError