AppliedRule.py 12.3 KB
Newer Older
1
# -*- coding: utf-8 -*-
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2 3 4
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
5
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
#
# 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.
#
##############################################################################

30 31
import zope.interface

Jean-Paul Smets's avatar
Jean-Paul Smets committed
32
from AccessControl import ClassSecurityInfo
33
from Products.ERP5Type import Permissions, PropertySheet, interfaces
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34
from Products.ERP5Type.XMLObject import XMLObject
35
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
36
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
37
from Products.ERP5Legacy.Document.Rule import Rule
Jean-Paul Smets's avatar
Jean-Paul Smets committed
38

39 40 41
TREE_DELIVERED_CACHE_KEY = 'AppliedRule._isTreeDelivered_cache'
TREE_DELIVERED_CACHE_ENABLED = 'TREE_DELIVERED_CACHE_ENABLED'

Jean-Paul Smets's avatar
Jean-Paul Smets committed
42 43
class AppliedRule(XMLObject):
    """
44
      An applied rule holds a list of simulation movements.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
45

46 47
      An applied rule points to an instance of Rule (which defines the actual
      rule to apply with its parameters) through the specialise relation.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
48

49 50
      An applied rule can expand itself (look at its direct parent and take
      conclusions on what should be inside).
Jean-Paul Smets's avatar
Jean-Paul Smets committed
51

52 53
      An applied rule can tell if it is stable (if its children are consistent
      with what would be expanded from its direct parent).
Jean-Paul Smets's avatar
Jean-Paul Smets committed
54

55 56 57 58
      An applied rule can tell if any of his direct children is divergent (not
      consistent with the delivery).

      All algorithms are implemented by the rule.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
59 60 61 62 63 64 65 66
    """

    # CMF Type Definition
    meta_type = 'ERP5 Applied Rule'
    portal_type = 'Applied Rule'

    # Declarative security
    security = ClassSecurityInfo()
67
    security.declareObjectProtected(Permissions.AccessContentsInformation)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
68 69 70 71 72 73 74 75 76

    # Default Properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
                      , PropertySheet.SimpleItem
                      , PropertySheet.CategoryCore
                      , PropertySheet.AppliedRule
                      )

77 78 79
    # Declarative interfaces
    zope.interface.implements(interfaces.IMovementCollection,)

80 81 82 83
    def tpValues(self) :
      """ show the content in the left pane of the ZMI """
      return self.objectValues()

84
    security.declareProtected(Permissions.AccessContentsInformation,
85
        'isAccountable')
86
    def isAccountable(self, movement):
87
      """Tells whether generated movement needs to be accounted or not."""
88
      return self.getSpecialiseValue().isAccountable(movement)
89

Jean-Paul Smets's avatar
Jean-Paul Smets committed
90
    security.declareProtected(Permissions.ModifyPortalContent, 'expand')
91
    @UnrestrictedMethod
92
    def expand(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
93 94 95 96 97 98 99 100
      """
        Expands the current movement downward.

        -> new status -> expanded

        An applied rule can be expanded only if its parent movement
        is expanded.
      """
101
      tv = getTransactionalVariable()
102 103 104 105 106 107 108
      cache = tv.setdefault(TREE_DELIVERED_CACHE_KEY, {})
      cache_enabled = cache.get(TREE_DELIVERED_CACHE_ENABLED, 0)

      # enable cache
      if not cache_enabled:
        cache[TREE_DELIVERED_CACHE_ENABLED] = 1

Jean-Paul Smets's avatar
Jean-Paul Smets committed
109 110
      rule = self.getSpecialiseValue()
      if rule is not None:
Sebastien Robin's avatar
Sebastien Robin committed
111
        rule.expand(self,**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
112

113 114 115 116 117 118 119
      # disable and clear cache
      if not cache_enabled:
        try:
          del tv[TREE_DELIVERED_CACHE_KEY]
        except KeyError:
          pass

Jean-Paul Smets's avatar
Jean-Paul Smets committed
120 121 122
    security.declareProtected(Permissions.ModifyPortalContent, 'solve')
    def solve(self, solution_list):
      """
123
        Solve inconsistency according to a certain number of solutions
Jean-Paul Smets's avatar
Jean-Paul Smets committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
        templates. This updates the

        -> new status -> solved

        This applies a solution to an applied rule. Once
        the solution is applied, the parent movement is checked.
        If it does not diverge, the rule is reexpanded. If not,
        diverge is called on the parent movement.
      """
      rule = self.getSpecialiseValue()
      if rule is not None:
        rule.solve(self)

    security.declareProtected(Permissions.ModifyPortalContent, 'diverge')
    def diverge(self):
      """
        -> new status -> diverged

        This basically sets the rule to "diverged"
        and blocks expansion process
      """
      rule = self.getSpecialiseValue()
      if rule is not None:
        rule.diverge(self)

    # Solvers
150 151 152
    security.declareProtected(Permissions.AccessContentsInformation,
        'isStable')
    def isStable(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
153
      """
154
      Tells whether the rule is stable or not.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
155
      """
156
      return self.getSpecialiseValue().isStable(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
157

158 159
    security.declareProtected(Permissions.AccessContentsInformation,
        'isDivergent')
160
    def isDivergent(self, sim_mvt):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
161
      """
162
      Tells whether generated sim_mvt is divergent or not.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
163
      """
164
      return self.getSpecialiseValue().isDivergent(sim_mvt)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
165

166 167
    security.declareProtected(Permissions.AccessContentsInformation,
        'getDivergenceList')
168
    def getDivergenceList(self, sim_mvt):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
169
      """
170
      Returns a list Divergence descriptors
Jean-Paul Smets's avatar
Jean-Paul Smets committed
171
      """
172
      return self.getSpecialiseValue().getDivergenceList(sim_mvt)
173

174
    security.declareProtected(Permissions.AccessContentsInformation,
175
        'isRootAppliedRule')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
176 177 178 179
    def isRootAppliedRule(self):
      """
        Returns 1 is this is a root applied rule
      """
180
      return self.getParentValue().getMetaType() == "ERP5 Simulation Tool"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
181

182
    security.declareProtected(Permissions.AccessContentsInformation,
183
        'getRootAppliedRule')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
184
    def getRootAppliedRule(self):
185 186 187 188
      """Return the root applied rule.
      useful if some reindexing is needed from inside
      """
      if self.getParentValue().getMetaType() == "ERP5 Simulation Tool":
Jean-Paul Smets's avatar
Jean-Paul Smets committed
189
        return self
190
      return self.getParentValue().getRootAppliedRule()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
191

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
    def _getExplanationSpecialiseValue(self, portal_type_list):
      """Returns first found specialise value of delivery or order
      In case if self is root Applied Rule uses causality
      Otherwise uses delivery, than order of parent movements
      Recurses to parents"""
      def findSpecialiseValueBySimulation(movement):
        specialise_value = None
        if movement.getPortalType() != 'Simulation Movement':
          return None
        delivery, order = movement.getDeliveryValue(), movement.getOrderValue()

        if delivery is not None:
          specialise_value = delivery.getExplanationValue() \
              .getRootSpecialiseValue(portal_type_list)
          if specialise_value is not None:
            return specialise_value
208
        # 'order' category is deprecated. it is kept for compatibility.
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
        if order is not None:
          specialise_value = order.getExplanationValue() \
              .getRootSpecialiseValue(portal_type_list)
          if specialise_value is not None:
            return specialise_value
        return findSpecialiseValueBySimulation(movement.getParentValue() \
            .getParentValue())

      if self.getRootAppliedRule() == self:
        return self.getCausalityValue() \
            .getRootSpecialiseValue(portal_type_list)
      movement = self.getParentValue()
      return findSpecialiseValueBySimulation(movement)


    security.declareProtected(Permissions.AccessContentsInformation,
                             'getTradeConditionValue')
    def getTradeConditionValue(self):
      """Return the trade condition that has been used in this
      simulation, or None if none has been used.
      """
      return self._getExplanationSpecialiseValue(
          ('Purchase Trade Condition', 'Sale Trade Condition'))

233
    security.declareProtected(Permissions.AccessContentsInformation,
Łukasz Nowak's avatar
Łukasz Nowak committed
234 235
                             'getBusinessProcessValue')
    def getBusinessProcessValue(self):
236
      """Return the business process model that has been used in this
237
      simulation, or None if none  has been used.
238
      """
239 240
      return self._getExplanationSpecialiseValue(
          ('Business Process',))
241

242 243 244 245 246 247 248 249
    def _isTreeDelivered(self):
      """
      Checks if submovements of this applied rule (going down the complete
      simulation tree) have a delivery relation.
      Returns True if at least one is delivered, False if none of them are.

      see SimulationMovement._isTreeDelivered
      """
250
      tv = getTransactionalVariable()
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
      cache = tv.setdefault(TREE_DELIVERED_CACHE_KEY, {})
      cache_enabled = cache.get(TREE_DELIVERED_CACHE_ENABLED, 0)

      def getTreeDelivered(applied_rule):
        for movement in applied_rule.objectValues():
          if movement._isTreeDelivered():
            return True
        return False

      rule_key = self.getRelativeUrl()
      if cache_enabled:
        try:
          return cache[rule_key]
        except:
          result = getTreeDelivered(self)
          cache[rule_key] = result
          return result
      else:
        return getTreeDelivered(self)

271 272 273 274 275 276 277
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getMovementList')
    def getMovementList(self, portal_type=None, **kw):
      """
       Return a list of movements.
      """
      return self.objectValues(portal_type=Rule.movement_type)
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330

    security.declareProtected(Permissions.AccessContentsInformation,
            'getIndexableChildSimulationMovementValueList')
    def getIndexableChildSimulationMovementValueList(self):
      return [x for x in self.getIndexableChildValueList() 
              if x.getPortalType() == 'Simulation Movement']

    security.declarePublic('recursiveImmediateReindexSimulationMovement')
    def recursiveImmediateReindexSimulationMovement(self, **kw):
      """
        Applies immediateReindexObject recursively to Simulation Movements
      """
      # Reindex direct children
      root_indexable = int(getattr(self.getPortalObject(), 'isIndexable', 1))
      for movement in self.objectValues():
        if movement.isIndexable and root_indexable:
          movement.immediateReindexObject(**kw)
      # Go recursively
      for movement in self.objectValues():
        for applied_rule in movement.objectValues():
          applied_rule.recursiveImmediateReindexSimulationMovement(**kw)

    security.declarePublic('recursiveReindexObject')
    def recursiveReindexSimulationMovement(self, activate_kw=None, **kw):
      if self.isIndexable:
        if activate_kw is None:
          activate_kw = {}

      reindex_kw = self.getDefaultReindexParameterDict()
      if reindex_kw is not None:
        reindex_activate_kw = reindex_kw.pop('activate_kw', None)
        if reindex_activate_kw is not None:
          reindex_activate_kw = reindex_activate_kw.copy()
          if activate_kw is not None:
            # activate_kw parameter takes precedence
            reindex_activate_kw.update(activate_kw)
          activate_kw = reindex_activate_kw
        kw.update(reindex_kw)

      group_id_list  = []
      if kw.get("group_id", "") not in ('', None):
        group_id_list.append(kw.get("group_id", ""))
      if kw.get("sql_catalog_id", "") not in ('', None):
        group_id_list.append(kw.get("sql_catalog_id", ""))
      group_id = ' '.join(group_id_list)

      self.activate(group_method_id='portal_catalog/catalogObjectList',
                    expand_method_id='getIndexableChildSimulationMovementValueList',
                    alternate_method_id='alternateReindexObject',
                    group_id=group_id,
                    serialization_tag=self.getRootDocumentPath(),
                    **activate_kw).recursiveImmediateReindexSimulationMovement(**kw)