BusinessPath.py 9.87 KB
Newer Older
1 2 3 4 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
#                    Jean-Paul Smets-Solanes <jp@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.
#
##############################################################################

from Globals import InitializeClass, PersistentMapping
from AccessControl import ClassSecurityInfo

from Products.CMFCore.PortalFolder import ContentFilter
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Products.ERP5.Document.Path import Path

import zope.interface

class BusinessPath(Path):
  """
    The BusinessPath class embeds all information related to 
    lead times and parties involved at a give phase of a business
    process.

    BusinessPath are also used as helper to build buildable movements.
    Here is the typical code of an alarm:
   
    Approach 1: explanation per explanation
      builder = portal_deliveries.default_order_builder
      for path in builder.getSpecialiseRelatedValueList() # or wharever category
        for explanation in portal_catalog(buildable=1, portal_type='Order'):
          path.build(explanation)

      Pros: easy explanation based approach
      Cons: buildable column added in delivery table
            reexpand of a finished order might generate remaining buildable

    Approach 2: isBuildable is indexed for SimulationMovements
      isBuildable() method is added to SimulationMovement

      Pros: global select is possible
      Cons: reindex of simulation is required
            slow indexing

    Approach 3: isBuildable is invoked during build "after select" process
      builder = portal_deliveries.default_order_builder
      for path in builder.getSpecialiseRelatedValueList() # or wharever category
        builder.build(causality_uid=path.getUid(),) # Select movemenents

      Pros: global select is possible
      Cons: global select retrieves long lists
            slow build

     Method 3 is best
  """
  meta_type = 'ERP5 Business Path'
  portal_type = 'Business Path'
  isPredicate = 1

  # Declarative security
  security = ClassSecurityInfo()
  security.declareObjectProtected(Permissions.AccessContentsInformation)

  # Declarative properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.XMLObject
                    , PropertySheet.CategoryCore
                    , PropertySheet.DublinCore
                    , PropertySheet.Folder
                    , PropertySheet.Comment
                    , PropertySheet.Arrow
                    , PropertySheet.Chain
                    , PropertySheet.BusinessPath
                    )

  # Declarative interfaces
  zope.interface.implements(Interface.ICategoryAccessProvider,
                            Interface.IArrow)

  # IBusinessPath Interface
  security.declareProtected(Permissions.AccessContentsInformation, 'getSourceBaseCategoryList')
  def getSourceBaseCategoryList(self):
    """
      Returns all categories which are used to define the source
      of this Arrow
    """
    # Naive implementation - we must use category groups instead
    return ('source', 'source_section', 'source_payment', 'source_project', )

  security.declareProtected(Permissions.AccessContentsInformation, 'getDestinationBaseCategoryList')
  def getDestinationBaseCategoryList(self):
    """
      Returns all categories which are used to define the destination
      of this Arrow
    """
    # Naive implementation - we must use category groups instead
    return ('destination', 'destination_section', 'destination_payment', 'destination_project', )

  # ICategoryAccessProvider overriden methods
  def _getCategoryMembershipList(self, category, **kw):
    """
      Overriden in order to take into account dynamic arrow
      categories
    """
    context = kw.get('context')
    result = Path._getCategoryMembershipList(self, category, **kw)
    if context is not None:
      dynamic_category_list = self._getDynamicCategoryList(context)
      dynamic_category_list= self._filterCategoryList(dynamic_category_list, category, **kw)
      # TODO: static categories should have priority over dynamic categories
      result = dynamic_category_list + result
    return result

  def _getAcquiredCategoryMembershipList(self, category, **kw):
    """
      Overriden in order to take into account dynamic arrow
      categories
    """
138
    context = kw.pop('context', None)
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
    result = Path._getAcquiredCategoryMembershipList(self, category, **kw)
    if context is not None:
      dynamic_category_list = self._getDynamicCategoryList(context)
      dynamic_category_list= self._filterCategoryList(dynamic_category_list, category, **kw)
      # TODO: static categories should have priority over dynamic categories
      result = dynamic_category_list + result
    return result

  def _filterCategoryList(self, category_list, category, spec=(), filter=None, portal_type=(), base=0, 
                         keep_default=1, checked_permission=None):
    """
      XXX - implementation missing
      TBD - look at CategoryTool._buildFilter for inspiration
    """
    return category_list

  # Dynamic context based categories
  def _getDynamicCategoryList(self, context):
157 158
    return self._getDynamicSourceCategoryList(context) \
         + self._getDynamicDestinationCategoryList(context)
159 160 161 162 163 164

  def _getDynamicSourceCategoryList(self, context):
    method_id = self.getSourceMethodId()
    if method_id:
      method = getattr(self, method_id)
      return method(context)
165
    return []
166 167 168 169 170 171

  def _getDynamicDestinationCategoryList(self, context):
    method_id = self.getDestinationMethodId()
    if method_id:
      method = getattr(self, method_id)
      return method(context)
172
    return []
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 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 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267

  # Core API
  def isBuildable(self, explanation):
    """
    """
    if self.isCompleted(explanation):
      return False # No need to build what was already built
    if self.isFrozen(explanation):
      return False # No need to build what is frozen
    predecessor = self.getPredecessorValue()
    if predecessor is None:
      return True # No predecessor, let's build
    if predecessor.isCompleted(explanation):
      return True
    return False

  def isPartiallyBuildable(self, explanation):
    """
      Not sure if this will exist some day XXX
    """

  def _getRelatedSimulationMovementList(explanation):
    """
      
    """
    return self.getCausalityRelatedValueList(portal_type='Simulation Movement',
                                             explanation_uid=explanation.getUid())

  def isCompleted(self, explanation):
    """
      Looks at all simulation related movements
      and checks the simulation_state of the delivery
    """
    acceptable_state_list = self.getCompletedStateIdList() # XXX Missing method / name
    for movement in self._getRelatedSimulationMovementList(explanation):
      if movement.getSimulationState() not in acceptable_state_list:
        return False
    return True

  def isPartiallyCompleted(self, explanation):
    """
      Looks at all simulation related movements
      and checks the simulation_state of the delivery
    """
    acceptable_state_list = self.getCompletedStateList() # XXX Missing method / name
    for movement in self._getRelatedSimulationMovementList(explanation):
      if movement.getSimulationState() in acceptable_state_list:
        return True
    return False

  def isFrozen(self, explanation):
    """
      Looks at all simulation related movements
      and checks if frozen
    """
    movement_list = self._getRelatedSimulationMovementList(explanation)
    if len(movement_list) == 0:
      return False # Nothing to be considered as Frozen
    for movement in movement_list:
      if not movement.isFrozen():
        return False
    return True

  def build(self, explanation):
    """
      Build
    """
    builder_list = self.getBuilderList() # Missing method
    for builder in builder_list:
      builder.build(causality_uid=self.getUid()) # This is one way of doing
      builder.build(movement_relative_url_list=
        self._getRelatedSimulationMovementList(explanation)) # Another way

  # Date calculation
  def getExpectedStartDate(self, explanation, predecessor_date=None):
    """
      Returns the expected start date for this
      path based on the explanation.

      predecessor_date -- if provided, computes the date base on the
                          date value provided
    """
    if predecessor_date is None:
      predecessor_date = self.getPredecessorValue().getExpectedCompletionDate()
    return predecessor_date + wait_time

  def getExpectedStopDate(self, explanation, predecessor_date=None):
    """
      Returns the expected stop date for this
      path based on the explanation.

      predecessor_date -- if provided, computes the date base on the
                          date value provided
    """
    return context.getExpectedStartDate(explanation, predecessor_date=predecessor_date) + lead_time