AppliedRule.py 9.81 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
    @UnrestrictedMethod
89
    def expand(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
90 91 92 93 94 95 96 97
      """
        Expands the current movement downward.

        -> new status -> expanded

        An applied rule can be expanded only if its parent movement
        is expanded.
      """
98 99 100 101 102 103 104 105
      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
106 107
      rule = self.getSpecialiseValue()
      if rule is not None:
Sebastien Robin's avatar
Sebastien Robin committed
108
        rule.expand(self,**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
109

110 111 112 113 114 115 116
      # 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
117 118 119
    security.declareProtected(Permissions.ModifyPortalContent, 'solve')
    def solve(self, solution_list):
      """
120
        Solve inconsistency according to a certain number of solutions
Jean-Paul Smets's avatar
Jean-Paul Smets committed
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
        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
147 148 149
    security.declareProtected(Permissions.AccessContentsInformation,
        'isStable')
    def isStable(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
150
      """
151
      Tells whether the rule is stable or not.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
152
      """
153
      return self.getSpecialiseValue().isStable(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
154

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

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

    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
178

179
    security.declareProtected(Permissions.AccessContentsInformation,
180
        'isRootAppliedRule')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
181 182 183 184
    def isRootAppliedRule(self):
      """
        Returns 1 is this is a root applied rule
      """
185
      return self.getParentValue().getMetaType() == "ERP5 Simulation Tool"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
186

187
    security.declareProtected(Permissions.AccessContentsInformation,
188
        'getRootAppliedRule')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
189
    def getRootAppliedRule(self):
190 191 192 193
      """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
194
        return self
195
      return self.getParentValue().getRootAppliedRule()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
196

197 198 199 200 201 202 203 204 205 206 207 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
    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'))

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

246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
    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)