NodeBudgetVariation.py 10.9 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
##############################################################################
#
# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
#
# 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 AccessControl.ZopeGuards import guarded_getattr
31
from Products.ERP5Type import Permissions, PropertySheet
32
from Products.ERP5.Document.BudgetVariation import BudgetVariation
33 34 35 36 37 38 39 40 41
from Products.ZSQLCatalog.SQLCatalog import Query, NegatedQuery
from Products.ERP5Type.Message import translateString


class VirtualNode(object):
  """A Virtual Node for all Other Nodes.

  This virtual document can be used in budget variations.
  """
42
  __allow_access_to_unprotected_subobjects__ = 1
43 44 45 46 47 48 49
  def __init__(self, relative_url):
    """The Virtual Node will use the relative URL of the budget line for
    memberships.
    """
    self.relative_url = relative_url

  def getTitle(self):
50
    return str(translateString('All Others'))
51 52 53 54 55 56

  def getRelativeUrl(self):
    return self.relative_url

  def getUid(self):
    return -1L
57 58 59 60


class NodeBudgetVariation(BudgetVariation):
  """ A budget variation for node
61 62

  A script will return the list of possible nodes, or they will be configured
63 64
  explicitly on the budget variation. It is also possible to include a virtual
  node for all others not selected nodes.
65 66 67 68 69 70 71 72
  """
  # Default Properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.XMLObject
                    , PropertySheet.SimpleItem
                    , PropertySheet.SortIndex
                    , PropertySheet.Path
                    , PropertySheet.Predicate
73
                    , PropertySheet.BudgetVariation
74 75 76 77 78 79 80 81 82 83 84
                    )

  # CMF Type Definition
  meta_type = 'ERP5 Node Budget Variation'
  portal_type = 'Node Budget Variation'
  add_permission = Permissions.AddPortalContent

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

85
  # zope.interface.implements(BudgetVariation, )
86 87 88 89 90

  def asBudgetPredicate(self):
    """This budget variation in a predicate
    """

91
  def _getNodeList(self, context):
92 93 94 95
    """Returns the list of possible nodes
    """
    node_select_method_id = self.getProperty('node_select_method_id')
    if node_select_method_id:
96
      return guarded_getattr(context, node_select_method_id)()
97
    # no script defined, used the explicitly selected values
98 99 100
    if self.getProperty('include_virtual_other_node'):
      return self.getAggregateValueList() + [
                    VirtualNode(context.getRelativeUrl()), ]
101
    return self.getAggregateValueList()
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

  def _getNodeTitle(self, node):
    """Returns the title of a node
    """
    node_title_method_id = self.getProperty('node_title_method_id', 'getTitle')
    return guarded_getattr(node, node_title_method_id)()

  def getCellRangeForBudgetLine(self, budget_line, matrixbox=0):
    """The cell range added by this variation
    """
    base_category = self.getProperty('variation_base_category')
    prefix = ''
    if base_category:
      prefix = '%s/' % base_category

    node_item_list = [('%s%s' % (prefix, node.getRelativeUrl()),
                       self._getNodeTitle(node))
119
                           for node in self._getNodeList(budget_line)]
120 121 122 123 124 125 126 127
    variation_category_list = budget_line.getVariationCategoryList()
    if matrixbox:
      return [[i for i in node_item_list if i[0] in variation_category_list]]
    return [[i[0] for i in node_item_list if i[0] in variation_category_list]]

  def getInventoryQueryDict(self, budget_cell):
    """ Query dict to pass to simulation query
    """
128 129
    axis = self.getInventoryAxis()
    if not axis:
130
      return dict()
131 132 133
    base_category = self.getProperty('variation_base_category')
    if not base_category:
      return dict()
134
    budget_line = budget_cell.getParentValue()
135 136 137 138 139 140 141 142 143

    context = budget_cell
    if self.isMemberOf('budget_variation/budget'):
      context = budget_line.getParentValue()
    elif self.isMemberOf('budget_variation/budget_line'):
      context = budget_line

    portal_categories = self.getPortalObject().portal_categories
    for criterion_category in context.getMembershipCriterionCategoryList():
144 145 146 147
      if '/' not in criterion_category: # safe ...
        continue
      criterion_base_category, node_url = criterion_category.split('/', 1)
      if criterion_base_category == base_category:
148 149
        if axis == 'movement':
          axis = 'default_%s' % base_category
150 151 152 153 154
        if axis == 'movement_strict_membership':
          axis = 'default_strict_%s' % base_category
        # TODO: This is not correct if axis is a category such as
        # section_category, because getInventoryList for now does not support
        # parameters such as section_category_uid
155
        axis = '%s_uid' % axis
156 157 158 159 160 161 162 163
        if node_url == budget_line.getRelativeUrl():
          # This is the "All Other" virtual node
          other_uid_list = []
          for node in self._getNodeList(budget_line):
            if '%s/%s' % (base_category, node.getRelativeUrl()) in\
                                    budget_line.getVariationCategoryList():
              other_uid_list.append(node.getUid())
          return {axis: NegatedQuery(Query(**{axis: other_uid_list}))}
164
        return {axis:
165
                portal_categories.getCategoryValue(node_url, base_category=criterion_base_category).getUid()}
166

167 168
    return dict()

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
  def getInventoryListQueryDict(self, budget_line):
    """Returns the query dict to pass to simulation query for a budget line
    """
    axis = self.getInventoryAxis()
    if not axis:
      return dict()
    base_category = self.getProperty('variation_base_category')
    if not base_category:
      return dict()

    context = budget_line
    if self.isMemberOf('budget_variation/budget'):
      context = budget_line.getParentValue()

    portal_categories = self.getPortalObject().portal_categories
    query_dict = dict()
    if axis == 'movement':
      axis = 'default_%s_uid' % base_category
187 188 189 190
      query_dict['select_list'] = [axis]
    if axis == 'movement_strict_membership':
      axis = 'default_strict_%s_uid' % base_category
      query_dict['select_list'] = [axis]
191
    query_dict['group_by_%s' % axis] = True
192

193 194 195 196 197 198 199 200 201
    # TODO: This is not correct if axis is a category (such as
    # section_category)
    axis = '%s_uid' % axis

    # if we have a virtual "all others" node, we don't set a criterion here.
    if self.getProperty('include_virtual_other_node'):
      return query_dict

    for node_url in context.getVariationCategoryList(
202
                          base_category_list=(base_category,)):
203 204 205 206 207
      query_dict.setdefault(axis, []).append(
                portal_categories.getCategoryValue(node_url,
                      base_category=base_category).getUid())
    return query_dict
  
208 209 210 211
  def _getCellKeyFromInventoryListBrain(self, brain, budget_line,
                                         cell_key_cache=None):
    """Compute key from inventory brain, with support for "all others" virtual
    node.
212 213
    """
    key = BudgetVariation._getCellKeyFromInventoryListBrain(
214
                   self, brain, budget_line, cell_key_cache=cell_key_cache)
215 216 217 218 219 220
    if self.getProperty('include_virtual_other_node'):
      if key not in [x[1] for x in
          self.getBudgetVariationRangeCategoryList(budget_line)]:
        key = '%s/%s' % ( self.getProperty('variation_base_category'),
                          budget_line.getRelativeUrl() )
    return key
221

222 223 224 225 226 227 228 229 230
  def getBudgetLineVariationRangeCategoryList(self, budget_line):
    """Returns the Variation Range Category List that can be applied to this
    budget line.
    """
    base_category = self.getProperty('variation_base_category')
    prefix = ''
    if base_category:
      prefix = '%s/' % base_category
    return [(self._getNodeTitle(node), '%s%s' % (prefix, node.getRelativeUrl()))
231
                for node in self._getNodeList(budget_line)]
232

233 234 235 236 237 238 239 240 241 242 243
  def getBudgetVariationRangeCategoryList(self, budget):
    """Returns the Variation Range Category Listhat can be applied to this
    budget.
    """
    base_category = self.getProperty('variation_base_category')
    prefix = ''
    if base_category:
      prefix = '%s/' % base_category
    return [(self._getNodeTitle(node), '%s%s' % (prefix, node.getRelativeUrl()))
                for node in self._getNodeList(budget)]

244 245 246 247 248
  def initializeBudgetLine(self, budget_line):
    """Initialize a budget line
    """
    budget_line_variation_category_list =\
       list(budget_line.getVariationBaseCategoryList() or [])
249 250
    budget_line_membership_criterion_base_category_list =\
       list(budget_line.getMembershipCriterionBaseCategoryList() or [])
251 252 253 254 255
    base_category = self.getProperty('variation_base_category')
    if base_category:
      budget_line_variation_category_list.append(base_category)
      budget_line.setVariationBaseCategoryList(
              budget_line_variation_category_list)
256 257 258 259
    if self.isMemberOf('budget_variation/budget_line'):
      budget_line_membership_criterion_base_category_list.append(base_category)
      budget_line.setMembershipCriterionBaseCategoryList(
          budget_line_membership_criterion_base_category_list)
260

261 262 263
  def initializeBudget(self, budget):
    """Initialize a budget.
    """
264
    budget_variation_base_category_list =\
265
       list(budget.getVariationBaseCategoryList() or [])
266 267
    budget_membership_criterion_base_category_list =\
       list(budget.getMembershipCriterionBaseCategoryList() or [])
268 269
    base_category = self.getProperty('variation_base_category')
    if base_category:
270 271 272 273
      if base_category not in budget_variation_base_category_list:
        budget_variation_base_category_list.append(base_category)
      if base_category not in budget_membership_criterion_base_category_list:
        budget_membership_criterion_base_category_list.append(base_category)
274
      budget.setVariationBaseCategoryList(
275 276 277
              budget_variation_base_category_list)
      budget.setMembershipCriterionBaseCategoryList(
              budget_membership_criterion_base_category_list)
278