Measure.py 11.7 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
##############################################################################
#
# Copyright (c) 2008 Nexedi SARL 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 Products.ERP5Type import PropertySheet
from Products.ERP5Type.Permissions import AccessContentsInformation
from Products.ERP5Type.XMLMatrix import XMLMatrix
from Products.ERP5.Variated import Variated

35 36 37 38 39 40 41

def getQuantity(quantity_unit_value):
  quantity = quantity_unit_value.getProperty('quantity')
  if quantity is not None:
    return float(quantity)


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 138 139 140 141
class Measure(XMLMatrix):
  """
    A Measure
  """

  meta_type = 'ERP5 Measure'
  portal_type = 'Measure'

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

  # Declarative properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.XMLObject
                    , PropertySheet.CategoryCore
                    , PropertySheet.Measure
                    )

  security.declareProtected(AccessContentsInformation, 'getResourceValue')
  def getResourceValue(self):
    """
    Gets the resource object described by this measure.
    """
    return self.getDefaultResourceValue()

  ##
  #  Forms.

  security.declareProtected(AccessContentsInformation, 'getVariationRangeCategoryItemList')
  def getVariationRangeCategoryItemList(self, variation):
    """
    Returns possible variation category values for the selected variation.
    variation is a 0-based index and possible category values is provided
    as a list of tuples (id, title). This is mostly useful for matrixbox.
    """
    mvbc_list = self.getMeasureVariationBaseCategoryList()
    if len(mvbc_list) <= variation:
      return ()
    return self.getResourceValue().getVariationCategoryItemList(
      is_right_display=1,
      base_category_list=(mvbc_list[variation],),
      omit_individual_variation=0,
      display_base_category=0,
      sort_id='id')

  security.declareProtected(AccessContentsInformation, 'getQuantityUnitItemList')
  def getQuantityUnitItemList(self):
    """
    Returns the list of possible quantity units for the current metric type.
    This is mostly useful in ERP5Form instances to generate selection menus.
    """
    metric_type = self.getMetricType()
    if not metric_type:
      return ('', ''),
    portal = self.getPortalObject()
    return getattr(
      portal.portal_categories.getCategoryValue(metric_type.split('/', 1)[0],
                                                'quantity_unit'),
      portal.portal_preferences.getPreference(
        'preferred_category_child_item_list_method_id',
        'getCategoryChildCompactLogicalPathItemList')
    )(recursive=0, local_sort_id='quantity', checked_permission='View')

  security.declareProtected(AccessContentsInformation, 'getLocalQuantityUnit')
  def getLocalQuantityUnit(self):
    """
    Returns the 'quantity_unit' category without acquisition.
    Used in Resource_viewMeasure and Measure_view.
    """
    quantity_unit_list = self.getPortalObject().portal_categories \
      .getSingleCategoryMembershipList(self, 'quantity_unit')
    if quantity_unit_list:
      return quantity_unit_list[0]

  ##
  #  Measures associated to a quantity unit of the resource
  #  have a specific behaviour.

  security.declareProtected(AccessContentsInformation, 'isDefaultMeasure')
  def isDefaultMeasure(self, quantity_unit=None):
    """
    Checks if self is a default measure for the associated resource.
    """
    default = self.getResourceValue().getDefaultMeasure(quantity_unit)
    return default is not None \
       and self.getRelativeUrl() == default.getRelativeUrl()

  ##
  #  Conversion.

  security.declareProtected(AccessContentsInformation, 'getConvertedQuantityUnit')
  def getConvertedQuantityUnit(self):
    """
    Gets the quantity unit ratio, in respect to the base quantity unit.
    """
    quantity_unit = self.getQuantityUnitValue()
    metric_type = self.getMetricType()
    if quantity_unit is not None and metric_type and \
        quantity_unit.getParentId() == metric_type.split('/', 1)[0]:
142
      return getQuantity(quantity_unit)
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 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

  security.declareProtected(AccessContentsInformation, 'getConvertedQuantity')
  def getConvertedQuantity(self, variation_list=()):
    """
    Gets the measure value for a specified variation,
    in respected to the base quantity unit.

    Should it be reimplemented using predicates?
    If so, 'variation_list' parameter is deprecated.
    """
    quantity_unit = self.getConvertedQuantityUnit()
    if not quantity_unit:
      return
    quantity = self.getQuantity()
    if variation_list:
      variation_set = set(variation_list)
      for cell in self.objectValues():
        # if cell.test(context): # XXX
        if variation_set.issuperset(
            cell.getMembershipCriterionCategoryList()):
          quantity = cell.getQuantity()
          break
    return quantity * quantity_unit

  ##
  #  Cataloging.

  security.declareProtected(AccessContentsInformation, 'asCatalogRowList')
  def asCatalogRowList(self):
    """
    Returns the list of rows to insert in the measure table of the catalog.
    Called by Resource.getMeasureRowList.
    """
    quantity_unit = self.getConvertedQuantityUnit()
    if not quantity_unit:
      return ()
    uid = self.getUid()
    resource = self.getResourceValue()
    resource_uid = resource.getUid()
    metric_type_uid = self.getMetricTypeUid()
    quantity = self.getQuantity()

    # The only purpose of the defining a default measure explicitly is to
    # set a specific metric_type for the management unit.
    # Therefore, the measure mustn't be variated and the described quantity
    # (quantity * quantity_unit) must match the management unit.
    # If the conditions aren't met, return an empty list.
    default = self.isDefaultMeasure()

    measure_variation_base_category_list = \
      self.getMeasureVariationBaseCategoryList()
    if not measure_variation_base_category_list:
      # Easy case: there is no variation axe for this metric_type,
      # so we simply return 1 row.
      if quantity is not None:
        quantity *= quantity_unit
        if (not default or quantity ==
200
            getQuantity(resource.getQuantityUnitValue())):
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 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
          return (uid, resource_uid, '^', metric_type_uid, quantity),
      return ()

    if default:
      return ()

    # 1st step: Build a list of possible variation combinations.
    # Each element of the list (variation_list) is a pair of lists, where:
    #  * first list's elements are regex tokens:
    #    they'll be used to build the 'variation' values (in the result).
    #  * the second list is a combination of categories, used to find
    #    the measure cells containing the 'quantity' values.
    #
    # This step is done by starting from a 1-element list (variation_list).
    # For each variation axe (if variation_base_category in
    # measure_variation_base_category_list), we rebuild variation_list entirely
    # and its size is multiplied by the number of categories in this axe.
    # For other varation base categories (variation_base_category not in
    # measure_variation_base_category_list), we simply
    # update variation_list to add 1 regex token to each regex list.
    #
    # Example:
    #  * variation base categories: colour, logo, size
    #  * variation axes: colour, size
    #
    #  variation_list (tokens are simplified for readability):
    # 0. []
    # 1. [([colour/red],   ['colour/red']),
    #     ([colour/green], ['colour/green']),
    #     ([colour/blue],  ['colour/blue'])]
    # 2. [([colour/red,   logo/*], ['colour/red']),
    #     ([colour/green, logo/*], ['colour/green']),
    #     ([colour/blue,  logo/*], ['colour/blue'])]
    # 3. [([colour/red,   logo/*, size/small], ['colour/red',   'size/small']),
    #     ([colour/green, logo/*, size/small], ['colour/green', 'size/small']),
    #     ([colour/blue,  logo/*, size/small], ['colour/blue',  'size/small']),
    #     ([colour/red,   logo/*, size/medium], ['colour/red',   'size/medium']),
    #     ([colour/green, logo/*, size/medium], ['colour/green', 'size/medium']),
    #     ([colour/blue,  logo/*, size/medium], ['colour/blue',  'size/medium']),
    #     ([colour/red,   logo/*, size/big], ['colour/red',   'size/big']),
    #     ([colour/green, logo/*, size/big], ['colour/green', 'size/big']),
    #     ([colour/blue,  logo/*, size/big], ['colour/blue',  'size/big'])]

    # Note that from this point, we always work with sorted lists of
    # categories (or regex tokens).

    variation_list = ([], []),
    optional_variation_base_category_set = \
      set(resource.getOptionalVariationBaseCategoryList())
    for variation_base_category in sorted(
        resource.getVariationBaseCategoryList(omit_optional_variation=0)):
      if variation_base_category in measure_variation_base_category_list:
        # This is where we rebuild variation_list entirely. Size of
        # variation_list is multiplied by len(getVariationCategoryList).
        # The lists of each pairs in variation_list get one more element:
        # variation_category.
        variation_list = [(regex_list + [variation_category + '\n'],
                           variation_base_category_list + [variation_category])
          for variation_category in resource.getVariationCategoryList(
            base_category_list=(variation_base_category,),
            omit_individual_variation=0)
          for regex_list, variation_base_category_list in variation_list]
      else:
        variation_base_category_regex = variation_base_category + '/[^\n]+\n'
        if variation_base_category in optional_variation_base_category_set:
          variation_base_category_regex = \
            '(%s)*' % (variation_base_category_regex, )
        for regex_list, variation_base_category_list in variation_list:
          regex_list.append(variation_base_category_regex)

    # 2nd step: Retrieve all measure cells in a dictionary for fast lookup.
    cell_map = {}
    for cell in self.objectValues():
      cell_map[tuple(sorted(cell.getMembershipCriterionCategoryList()))] \
        = cell.getQuantity()

    # 3rd step: Build the list of rows to return,
    # by merging variation_list (1st step) and cell_map (2nd step).
    row_list = []
    for regex_list, variation_base_category_list in variation_list:
      cell = cell_map.get(tuple(variation_base_category_list))
      if cell is None:
        if quantity is None:
          continue
        cell = quantity
      row_list.append((uid,
                       resource_uid,
                       '^%s$' % ''.join(regex_list),
                       metric_type_uid,
                       cell * quantity_unit))

    return row_list