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

from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName
31
from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32
from Products.ERP5Type.XMLObject import XMLObject
33
from Products.ERP5Type.PsycoWrapper import psyco
34
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
35
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36 37 38

from zLOG import LOG

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 80
    def tpValues(self) :
      """ show the content in the left pane of the ZMI """
      return self.objectValues()

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

Jean-Paul Smets's avatar
Jean-Paul Smets committed
87
    security.declareProtected(Permissions.ModifyPortalContent, 'expand')
88
    def expand(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
89 90 91 92 93 94 95 96
      """
        Expands the current movement downward.

        -> new status -> expanded

        An applied rule can be expanded only if its parent movement
        is expanded.
      """
97 98 99 100
      expand = UnrestrictedMethod(self._expand)
      return expand(**kw)

    def _expand(self, **kw):
101 102 103 104 105 106 107 108
      tv = getTransactionalVariable(self)
      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:
111
        if self.isRootAppliedRule():
112
          # We should capture here a list of url/uids of deliveires to update
113
          rule._v_notify_dict = {}
Sebastien Robin's avatar
Sebastien Robin committed
114
        rule.expand(self,**kw)
115 116 117
        # XXX This part must be done with a interaction workflow is needed.
#       if self.isRootAppliedRule():
#         self.activate(
118
#                after_method_id=["immediateReindexObject",
119 120
#                                 "recursiveImmediateReindexObject"]).\
#                                   notifySimulationChange(rule._v_notify_dict)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
121

122 123 124 125 126 127 128
      # 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
129 130 131
    security.declareProtected(Permissions.ModifyPortalContent, 'solve')
    def solve(self, solution_list):
      """
132
        Solve inconsistency according to a certain number of solutions
Jean-Paul Smets's avatar
Jean-Paul Smets committed
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
        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
159 160 161
    security.declareProtected(Permissions.AccessContentsInformation,
        'isStable')
    def isStable(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
162
      """
163
      Tells whether the rule is stable or not.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
164
      """
165
      return self.getSpecialiseValue().isStable(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
166

167 168
    security.declareProtected(Permissions.AccessContentsInformation,
        'isDivergent')
169
    def isDivergent(self, sim_mvt):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
170
      """
171
      Tells whether generated sim_mvt is divergent or not.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
172
      """
173
      return self.getSpecialiseValue().isDivergent(sim_mvt)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
174

175 176
    security.declareProtected(Permissions.AccessContentsInformation,
        'getDivergenceList')
177
    def getDivergenceList(self, sim_mvt):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
178
      """
179
      Returns a list Divergence descriptors
Jean-Paul Smets's avatar
Jean-Paul Smets committed
180
      """
181
      return self.getSpecialiseValue().getDivergenceList(sim_mvt)
182 183 184 185 186 187 188 189

    security.declareProtected(Permissions.AccessContentsInformation,
        'getSolverList')
    def getSolverList(self, movement):
      """
      Returns a list Divergence solvers
      """
      return self.getSpecialiseValue().getSolverList(movement)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
190

191
    security.declareProtected(Permissions.AccessContentsInformation,
192
        'isRootAppliedRule')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
193 194 195 196
    def isRootAppliedRule(self):
      """
        Returns 1 is this is a root applied rule
      """
197
      return self.getParentValue().getMetaType() == "ERP5 Simulation Tool"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
198

199
    security.declareProtected(Permissions.AccessContentsInformation,
200
        'getRootAppliedRule')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
201
    def getRootAppliedRule(self):
202 203 204 205
      """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
206
        return self
207
      return self.getParentValue().getRootAppliedRule()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
208

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
    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
        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'))

249
    security.declareProtected(Permissions.AccessContentsInformation,
Łukasz Nowak's avatar
Łukasz Nowak committed
250 251
                             'getBusinessProcessValue')
    def getBusinessProcessValue(self):
252
      """Return the business process model that has been used in this
253
      simulation, or None if none  has been used.
254
      """
255 256
      return self._getExplanationSpecialiseValue(
          ('Business Process',))
257

258 259
    security.declareProtected(Permissions.ModifyPortalContent,
                              'notifySimulationChange')
260 261 262 263
    def notifySimulationChange(self, notify_dict):
      for delivery_url in notify_dict.keys():
        delivery_value = self.getPortalObject().restrictedTraverse(delivery_url)
        if delivery_value is None:
264 265 266
          LOG("ERP5 WARNING", 0,
              'Unable to access object %s to notify simulation change' %
               delivery_url)
267 268
        else:
          delivery_value.notifySimulationChange()
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298

    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
      """
      tv = getTransactionalVariable(self)
      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)