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

30
import zope.interface
31
from math import log
Alexandre Boeglin's avatar
Alexandre Boeglin committed
32
from warnings import warn
Jean-Paul Smets's avatar
Jean-Paul Smets committed
33

34
from AccessControl import ClassSecurityInfo
Jean-Paul Smets's avatar
Jean-Paul Smets committed
35

36
from Products.ERP5Type import Permissions, PropertySheet, interfaces
Jean-Paul Smets's avatar
Jean-Paul Smets committed
37
from Products.ERP5Type.XMLMatrix import XMLMatrix
38
from Products.ERP5Type.Base import Base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
39

40 41
from Products.ERP5Type.Utils import cartesianProduct

Jean-Paul Smets's avatar
Jean-Paul Smets committed
42
from Products.ERP5.Variated import Variated
43
from Products.CMFCategory.Renderer import Renderer
44
from Products.CMFCore.utils import getToolByName
Jean-Paul Smets's avatar
Jean-Paul Smets committed
45

Alexandre Boeglin's avatar
Alexandre Boeglin committed
46
from zLOG import LOG, WARNING
Jean-Paul Smets's avatar
Jean-Paul Smets committed
47

48
class Resource(XMLMatrix, Variated):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
49 50 51 52 53 54
    """
      A Resource
    """

    meta_type = 'ERP5 Resource'
    portal_type = 'Resource'
55
    add_permission = Permissions.AddPortalContent
Jean-Paul Smets's avatar
Jean-Paul Smets committed
56 57 58

    # Declarative security
    security = ClassSecurityInfo()
59
    security.declareObjectProtected(Permissions.AccessContentsInformation)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
60 61

    # Declarative interfaces
62
    zope.interface.implements( interfaces.IVariated, )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
63 64 65 66 67 68 69 70 71

    # Declarative properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
                      , PropertySheet.CategoryCore
                      , PropertySheet.DublinCore
                      , PropertySheet.Price
                      , PropertySheet.Resource
                      , PropertySheet.Reference
72
                      , PropertySheet.Comment
73
                      , PropertySheet.FlowCapacity
Sebastien Robin's avatar
Sebastien Robin committed
74
                      , PropertySheet.VariationRange
75
                      , PropertySheet.DefaultSupply
76
                      , PropertySheet.Aggregated
Jean-Paul Smets's avatar
Jean-Paul Smets committed
77 78 79 80 81 82 83
                      )

    # Is it OK now ?
    # The same method is at about 3 different places
    # Some genericity is needed
    security.declareProtected(Permissions.AccessContentsInformation,
                                           'getVariationRangeCategoryItemList')
84 85 86
    def getVariationRangeCategoryItemList(self, base_category_list=(), base=1, 
                                          root=1, display_id='title', 
                                          display_base_category=1,
87 88
                                          current_category=None,
                                          omit_individual_variation=0, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
89 90
        """
          Returns possible variations
91 92 93

          resource.getVariationRangeCategoryItemList
            => [(display, value)]
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
        
      ## Variation API (exemple) ##
        Base categories defined:
          - colour
          - morphology
          - size
        Categories defined:
          - colour/blue
          - colour/red
          - size/Man
          - size/Woman
        Resource 'resource' created with variation_base_category_list:
            (colour, morphology, size)

        resource.getVariationRangeCategoryList
        variation   | individual variation | result
        ____________________________________________________________________________________
                    |                      | (colour/blue, colour/red, size/Man, size/Woman)
        size/Man    |                      | (colour/blue, colour/red, size/Man, size/Woman)
        colour/blue |                      | (colour/blue, colour/red, size/Man, size/Woman)
                    |  colour/1            | (colour/1, size/Man, size/Woman)
                    |  morphology/2        | (colour/blue, colour/red, size/Man, size/Woman, morphology/2)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
116
        """
117
        result = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
118
        if base_category_list is ():
119 120 121
          base_category_list = self.getVariationBaseCategoryList(
              omit_individual_variation=omit_individual_variation)
        elif isinstance(base_category_list, str):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
122
          base_category_list = (base_category_list,)
123

124 125 126 127 128
        individual_variation_list = self.searchFolder(
            portal_type=self.getPortalVariationTypeList(),
            sort_on=[('title','ascending')])
        individual_variation_list = [x.getObject() for x in
            individual_variation_list]
129
        other_base_category_dict = dict([(i,1) for i in base_category_list])
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
 
        if not omit_individual_variation:              
          for variation in individual_variation_list:
            for base_category in variation.getVariationBaseCategoryList():
              if base_category_list is ()\
                  or base_category in base_category_list:
                other_base_category_dict[base_category] = 0
                # XXX now, call Renderer a lot of time.
                # Better implementation needed
                result.extend(Renderer(
                    base_category=base_category, 
                    display_base_category=display_base_category,
                    display_none_category=0, base=base,
                    current_category=current_category,
                    display_id=display_id).render([variation]))

        other_base_category_list = [x for x, y in
            other_base_category_dict.iteritems() if y == 1]
148
        # Get category variation
149 150
        if other_base_category_list:
          result.extend(Variated.getVariationRangeCategoryItemList(
151
              self, base_category_list=other_base_category_list,
152
              base=base, display_base_category=display_base_category, **kw))
153
        # Return result
154
        return result
155

Jean-Paul Smets's avatar
Jean-Paul Smets committed
156 157
    security.declareProtected(Permissions.AccessContentsInformation,
                                           'getVariationCategoryItemList')
158
    def getVariationCategoryItemList(self, base_category_list=(), 
159
                                     omit_optional_variation=0,
160 161 162 163 164 165 166 167 168 169 170 171 172
                                     omit_individual_variation=1, base=1,
                                     current_category=None,
                                     display_base_category=1,
                                     display_id='title', **kw):
      """
        Returns variations of the resource.
        If omit_individual_variation==1, does not return individual 
        variation.
        Else, returns them.
        Display is on left.
            => [(display, value)]

        *old parameters: base=1, current_category=None, 
173
                         display_id='title' (default value title)
174
      """
175 176 177 178
      base_category_list = base_category_list or \
          self.getVariationBaseCategoryList()
      
      individual_bc_list = self.getIndividualVariationBaseCategoryList()
179 180 181 182 183 184 185 186 187 188
      other_bc_list = [x for x in base_category_list
          if not x in individual_bc_list]

      if omit_optional_variation:
        optional_bc_list = self.getOptionalVariationBaseCategoryList()\
            or self.getPortalOptionBaseCategoryList()
        if optional_bc_list:
          other_bc_list = [x for x in other_bc_list
              if not x in optional_bc_list]
              
189
      
190
      result = Variated.getVariationCategoryItemList(self, 
191
                            base_category_list=other_bc_list, 
192
                            display_base_category=display_base_category, 
193
                            display_id=display_id, base=base, **kw)
194
      
195
      if not omit_individual_variation:
196 197 198 199
        individual_variation_list = self.searchFolder(
            portal_type=self.getPortalVariationTypeList())
        individual_variation_list = [x.getObject() for x in
            individual_variation_list]
200

201 202
        for variation in individual_variation_list:
          for base_category in variation.getVariationBaseCategoryList():
203 204 205 206 207 208
            # backwards compatbility: if individual_bc_list is empty, allow
            # all individual variation base categories.
            if (base_category_list is ()
                or base_category in base_category_list)\
               and (not len(individual_bc_list)
                    or base_category in individual_bc_list):
209 210 211 212
              # XXX append object, relative_url ?
              # XXX now, call Renderer a lot of time.
              # Better implementation needed
              result.extend(Renderer(
213 214 215 216 217
                  base_category=base_category,
                  display_base_category=display_base_category,
                  display_none_category=0, base=base,
                  current_category=current_category, display_id=display_id,
                  **kw).render([variation]))
218 219 220
      return result

    security.declareProtected(Permissions.AccessContentsInformation,
221
                              'getVariationCategoryList')
222
    def getVariationCategoryList(self, default=[], base_category_list=(),
223
                                 omit_individual_variation=1, **kw):
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
      """
        Returns variations of the resource.
        If omit_individual_variation==1, does not return individual 
        variation.
        Else, returns them.

        ## Variation API (exemple) ##
        Base categories defined:
          - colour
          - morphology
          - size
        Categories defined:
          - colour/blue
          - colour/red
          - size/Man
          - size/Woman
        Resource 'resource' created with variation_base_category_list:
            (colour, morphology, size)

        resource.getVariationCategoryList
        variation   | individual variation | result
        _____________________________________________________
                    |                      | ()
        size/Man    |                      | (size/Man, )
        colour/blue |                      | (colour/blue, )
                    |  colour/1            | (colour/1, )
                    |  morphology/2        | (morphology/2, )
      """
      vcil = self.getVariationCategoryItemList(
253 254
                    base_category_list=base_category_list,
                    omit_individual_variation=omit_individual_variation,**kw)
255
      return [x[1] for x in vcil]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
256 257 258 259 260 261 262

# This patch is temporary and allows to circumvent name conflict in ZSQLCatalog process for Coramy
    security.declareProtected(Permissions.AccessContentsInformation,
                                              'getDefaultDestinationAmountBis')
    def getDefaultDestinationAmountBis(self, unit=None, variation=None, REQUEST=None):
      try:
        return self.getDestinationReference()
Yoshinori Okuji's avatar
Yoshinori Okuji committed
263
      except AttributeError:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
264 265 266 267 268 269 270 271
        return None

# This patch is temporary and allows to circumvent name conflict in ZSQLCatalog process for Coramy
    security.declareProtected(Permissions.AccessContentsInformation,
                                              'getDefaultSourceAmountBis')
    def getDefaultSourceAmountBis(self, unit=None, variation=None, REQUEST=None):
      try:
        return self.getSourceReference()
Yoshinori Okuji's avatar
Yoshinori Okuji committed
272
      except AttributeError:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
273 274 275 276 277 278 279 280 281 282
        return None


    # This patch allows variations to find a resource
    security.declareProtected(Permissions.AccessContentsInformation,
                                              'getDefaultResourceValue')
    def getDefaultResourceValue(self):
      return self


283 284
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getDefaultTransformationValue')
285 286 287
    def getDefaultTransformationValue(self, context=None):
      """
      If context is None, returns the first available transformation that
288 289
      use self as a Resource. If there are several candidates, return the
      Transformation that has the latest version.
290 291 292 293 294

      Otherwise, context is used as a Predicate to match Transformations.
      If the search returns several candidates due to a relaxed Predicate,
      the first item is returned arbitrarily.
      """
295 296 297 298
      method = self._getTypeBasedMethod('getDefaultTransformationValue')
      if method is not None:
        return method(context)

299
      if context is None:
Nicolas Dumazet's avatar
Nicolas Dumazet committed
300 301 302 303 304 305
        transformation_list = self.portal_catalog(
            portal_type="Transformation",
            resource_relative_url=self.getRelativeUrl(),
            sort_on=[('version', 'descending')],
            limit=1
        )
306 307
        if len(transformation_list) > 0:
          return transformation_list[0].getObject()
308
        return None
309

310
      method = context._getTypeBasedMethod('getDefaultTransformationValue')
311 312 313
      if method is not None:
        return method(context)

314
      transformation_list = self.portal_domains.searchPredicateList(context,
315 316
                                portal_type="Transformation",
                                limit=1)
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333

      if len(transformation_list) > 0:
        return transformation_list[0]

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getDefaultConversionTransformationValue')
    def getDefaultConversionTransformationValue(self):
      """
      Return a Transformation object that should be used to compute
      converted inventories.
      This should be overriden in subclasses, or in the Type Based Method
      of the same name.

      The method can return an existing Transformation object, or a
      temporary Transformation: one might want for example, for conversion
      purposes, to ignore some (packaging, wrapping, labelling) components
      in conversion reports. This method can be used to create a simplified
334
      transformation from a complex real-world transformation.
335
      """
336
      method = self._getTypeBasedMethod(\
337 338 339 340
                        'getDefaultConversionTransformationValue')
      if method is not None:
        return method()

341
      return self.getDefaultTransformationValue(context=None)
342 343


344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
    security.declareProtected(Permissions.AccessContentsInformation,
                           'getTransformationVariationCategoryCartesianProduct')
    def getTransformationVariationCategoryCartesianProduct(self):
      """
      Defines which variations are of interest when indexing
      Transformations related to this resource.

      By default, this returns the cartesian Product of all
      possible categories using all variation axes.

      Override this to reduce the number of indexed rows, and/or
      if some variation axes do not matter when displaying
      Transformed inventories.

      XXX This should use variated_range mixin when available
      """
      method = self._getTypeBasedMethod(\
          'getTransformationVariationCategoryCartesianProduct')
      if method is not None:
        return method()

      variation_list_list = []
      for base_variation in self.getVariationBaseCategoryList():
        variation_list = self.getVariationCategoryList( \
            base_category_list=(base_variation,))
        if len(variation_list) > 0:
          variation_list_list.append(variation_list)

      return cartesianProduct(variation_list_list)


Romain Courteaud's avatar
Romain Courteaud committed
375
    ####################################################
Jean-Paul Smets's avatar
Jean-Paul Smets committed
376
    # Stock Management
Romain Courteaud's avatar
Romain Courteaud committed
377 378 379
    ####################################################
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventory')
380
    def getInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
381
      """
382
      Returns inventory
Jean-Paul Smets's avatar
Jean-Paul Smets committed
383
      """
384 385 386
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getInventory(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
387

Romain Courteaud's avatar
Romain Courteaud committed
388 389
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventory')
390
    def getCurrentInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
391
      """
392
      Returns current inventory
Jean-Paul Smets's avatar
Jean-Paul Smets committed
393
      """
394 395 396
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getCurrentInventory(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
397

Romain Courteaud's avatar
Romain Courteaud committed
398 399
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getAvailableInventory')
400
    def getAvailableInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
401
      """
402 403
      Returns available inventory
      (current inventory - deliverable)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
404
      """
405 406 407
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getAvailableInventory(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
408

Romain Courteaud's avatar
Romain Courteaud committed
409 410
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventory')
411
    def getFutureInventory(self, **kw):
412 413 414
      """
      Returns inventory at infinite
      """
415 416 417
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getFutureInventory(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
418

Romain Courteaud's avatar
Romain Courteaud committed
419 420
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryList')
421 422 423 424
    def getInventoryList(self, **kw):
      """
      Returns list of inventory grouped by section or site
      """
425 426 427
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getInventoryList(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
428

Romain Courteaud's avatar
Romain Courteaud committed
429 430
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryList')
431
    def getCurrentInventoryList(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
432
      """
433
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
434
      """
435 436 437 438 439 440 441 442 443 444 445 446 447
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getCurrentInventoryList(**kw)

    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getAvailableInventoryList')
    def getAvailableInventoryList(self, **kw):
      """
      Returns list of inventory grouped by section or site
      """
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getAvailableInventoryList(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
448

Romain Courteaud's avatar
Romain Courteaud committed
449 450
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryList')
451
    def getFutureInventoryList(self, **kw):
452 453 454
      """
      Returns list of inventory grouped by section or site
      """
455 456 457
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getFutureInventoryList(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
458

Romain Courteaud's avatar
Romain Courteaud committed
459 460
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryStat')
461
    def getInventoryStat(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
462
      """
463
      Returns statistics of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
464
      """
465 466 467
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getInventoryStat(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
468

Romain Courteaud's avatar
Romain Courteaud committed
469 470
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryStat')
471
    def getCurrentInventoryStat(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
472
      """
473
      Returns statistics of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
474
      """
475 476 477 478 479 480 481 482 483 484 485 486 487
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getCurrentInventoryStat(**kw)

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getAvailableInventoryStat')
    def getAvailableInventoryStat(self, **kw):
      """
      Returns statistics of inventory grouped by section or site
      """
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getAvailableInventoryStat(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
488

Romain Courteaud's avatar
Romain Courteaud committed
489 490
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryStat')
491
    def getFutureInventoryStat(self, **kw):
492 493 494
      """
      Returns statistics of inventory grouped by section or site
      """
495 496 497
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getFutureInventoryStat(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
498

Romain Courteaud's avatar
Romain Courteaud committed
499 500
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryChart')
501
    def getInventoryChart(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
502
      """
503
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
504
      """
505 506 507
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getInventoryChart(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
508

Romain Courteaud's avatar
Romain Courteaud committed
509 510
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryChart')
511
    def getCurrentInventoryChart(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
512
      """
513
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
514
      """
515 516 517
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getCurrentInventoryChart(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
518

Romain Courteaud's avatar
Romain Courteaud committed
519 520
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryChart')
521
    def getFutureInventoryChart(self, **kw):
522 523 524
      """
      Returns list of inventory grouped by section or site
      """
525 526 527
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getFutureInventoryChart(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
528

Romain Courteaud's avatar
Romain Courteaud committed
529 530
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryHistoryList')
531
    def getInventoryHistoryList(self, **kw):
532 533 534
      """
      Returns list of inventory grouped by section or site
      """
535 536 537
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getInventoryHistoryList(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
538

Romain Courteaud's avatar
Romain Courteaud committed
539 540
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryHistoryChart')
541
    def getInventoryHistoryChart(self, **kw):
542 543 544
      """
      Returns list of inventory grouped by section or site
      """
545 546 547
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getInventoryHistoryChart(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
548

549 550 551 552
    # XXX FIXME
    # Method getCurrentMovementHistoryList, 
    # getAvailableMovementHistoryList, getFutureMovementHistoryList
    # can be added
Romain Courteaud's avatar
Romain Courteaud committed
553 554
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getMovementHistoryList')
555
    def getMovementHistoryList(self, **kw):
556 557 558
      """
      Returns list of inventory grouped by section or site
      """
559 560 561
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getMovementHistoryList(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
562

Romain Courteaud's avatar
Romain Courteaud committed
563 564
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getMovementHistoryStat')
565
    def getMovementHistoryStat(self, **kw):
566 567 568
      """
      Returns list of inventory grouped by section or site
      """
569 570 571
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getMovementHistoryStat(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
572

Romain Courteaud's avatar
Romain Courteaud committed
573 574
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getNextNegativeInventoryDate')
575
    def getNextNegativeInventoryDate(self, **kw):
576 577 578
      """
      Returns list of inventory grouped by section or site
      """
579 580 581
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getNextNegativeInventoryDate(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
582 583


584
    # Asset Price API
585 586
    security.declareProtected(Permissions.AccessContentsInformation,
        'getInventoryAssetPrice')
587
    def getInventoryAssetPrice(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
588
      """
589
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
590
      """
591 592 593
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getInventoryAssetPrice(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
594

595 596
    security.declareProtected(Permissions.AccessContentsInformation,
        'getCurrentInventoryAssetPrice')
597
    def getCurrentInventoryAssetPrice(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
598
      """
599
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
600
      """
601 602 603
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getCurrentInventoryAssetPrice(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
604

605 606
    security.declareProtected(Permissions.AccessContentsInformation,
        'getAvailableInventoryAssetPrice')
607
    def getAvailableInventoryAssetPrice(self, **kw):
608 609 610
      """
      Returns list of inventory grouped by section or site
      """
611 612 613
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getAvailableInventoryAssetPrice(**kw)
614

615 616
    security.declareProtected(Permissions.AccessContentsInformation,
        'getFutureInventoryAssetPrice')
617
    def getFutureInventoryAssetPrice(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
618
      """
619
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
620
      """
621 622 623
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getFutureInventoryAssetPrice(**kw)
624

Jean-Paul Smets's avatar
Jean-Paul Smets committed
625

626
    # Industrial price API
627 628
    security.declareProtected(Permissions.AccessContentsInformation,
        'getIndustrialPrice')
629 630 631 632 633 634 635 636 637 638 639 640
    def getIndustrialPrice(self, context=None, REQUEST=None, **kw):
      """
        Returns industrial price
      """
      context = self.asContext(context=context, REQUEST=REQUEST, **kw)
      result = self._getIndustrialPrice(context)
      return result

    def _getIndustrialPrice(self, context):
      # Default value is None
      return None

641
    # Predicate handling
642 643
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'asPredicate')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
644 645 646 647 648 649 650
    def asPredicate(self):
      """
      Returns a temporary Predicate based on the Resource properties
      """
      from Products.ERP5 import newTempPredicateGroup as newTempPredicate
      p = newTempPredicate(self.getId(), uid = self.getUid())
      p.setMembershipCriterionBaseCategoryList(('resource',))
651 652
      p.setMembershipCriterionCategoryList(('resource/%s'
          % self.getRelativeUrl(),))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
653
      return p
654

655
    def _pricingSortMethod(self, a, b):
656 657 658 659
      # Simple method : the one that defines a destination section wins
      if a.getDestinationSection():
        return -1 # a defines a destination section and wins
      return 1 # a defines no destination section and loses
660

661
    security.declareProtected(Permissions.AccessContentsInformation, 
662
                              'getPriceParameterDict')
663 664
    def getPriceParameterDict(self, context=None, REQUEST=None,
                              supply_path_type=None, **kw):
665
      """
666
      Get all pricing parameters from Predicate.
667
      """
668
      # Search all categories context
669
      new_category_list = []
670
      if context is not None:
671
        new_category_list += context.getCategoryList()
672 673
      #XXX This should be 'category_list' instead of 'categories' to respect
      # the naming convention. Must take care of side effects when fixing
674 675 676
      if kw.has_key('categories'):
        new_category_list.extend(kw['categories'])
        del kw['categories']
677 678
      resource_category = 'resource/' + self.getRelativeUrl()
      if not resource_category in new_category_list:
679 680 681
        new_category_list += (resource_category, )
      # Generate the predicate mapped value
      # to get some price values.
682
      domain_tool = getToolByName(self,'portal_domains')
683 684 685 686 687 688
      if supply_path_type is None:
        portal_type_list = self.getPortalSupplyPathTypeList()
      elif isinstance(supply_path_type, (list, tuple)):
        portal_type_list = supply_path_type
      else:
        portal_type_list = (supply_path_type,)
689

Alexandre Boeglin's avatar
Alexandre Boeglin committed
690 691 692 693 694 695 696 697 698 699 700
      # Generate the fake context
      tmp_context = self.asContext(context=context, 
                                   categories=new_category_list,
                                   REQUEST=REQUEST, **kw)
      tmp_kw = kw.copy()
      if 'sort_method' not in tmp_kw:
        tmp_kw['sort_method'] = self._pricingSortMethod
      mapped_value = domain_tool.generateMultivaluedMappedValue(
                                             tmp_context,
                                             portal_type=portal_type_list,
                                             has_cell_content=0, **tmp_kw)
701 702 703
      # Get price parameters
      price_parameter_dict = {
        'base_price': None,
704 705 706
        'additional_price': [],
        'surcharge_ratio': [],
        'discount_ratio': [],
707
        'exclusive_discount_ratio': None,
708 709
        'variable_additional_price': [],
        'non_discountable_additional_price': [],
710
        'priced_quantity': None,
711
        'base_unit_price': None,
712
      }
Alexandre Boeglin's avatar
Alexandre Boeglin committed
713 714 715 716
      if mapped_value is None:
        return price_parameter_dict
      for price_parameter_name in price_parameter_dict.keys():
        price_parameter_value = \
717 718 719
          mapped_value.getProperty(price_parameter_name,
              d=price_parameter_dict[price_parameter_name])
        if price_parameter_value not in [None, [], '']:
Alexandre Boeglin's avatar
Alexandre Boeglin committed
720 721 722 723 724 725
          try:
            price_parameter_dict[price_parameter_name].extend(
                                            price_parameter_value)
          except AttributeError:
            if price_parameter_dict[price_parameter_name] is None:
              if price_parameter_name == 'exclusive_discount_ratio':
726
                price_parameter_dict[price_parameter_name] = \
Alexandre Boeglin's avatar
Alexandre Boeglin committed
727 728 729 730
                    max(price_parameter_value)
              else:
                price_parameter_dict[price_parameter_name] = \
                    price_parameter_value[0]
731 732
      return price_parameter_dict
      
733
    security.declareProtected(Permissions.AccessContentsInformation,
734 735
        'getPricingVariable')
    def getPricingVariable(self, context=None):
736 737 738 739
      """
      Return the value of the property used to calculate variable pricing
      This basically calls a script like Product_getPricingVariable
      """
740 741 742
      warn('Resource.getPricingVariable is deprecated; Please use' \
           ' a type-based method for getPrice, and call whatever scripts' \
           ' you need to invoke from that method.', DeprecationWarning)
743
      method = None
744
      if context is not None:
745
        method = context._getTypeBasedMethod('getPricingVariable')
746
      if method is None or context is None:
747
        method = self._getTypeBasedMethod('getPricingVariable')
748

749 750 751 752
      if method is None:
        return 0.0
      return float(method())

753
    security.declareProtected(Permissions.AccessContentsInformation, 
754 755 756 757 758 759 760 761
                              'getPriceCalculationOperandDict')
    def getPriceCalculationOperandDict(self, default=None, context=None,
            REQUEST=None, **kw):
      """Return a dictionary which contains operands for price calculation.
      Consult the doc string in Movement.getPriceCalculationOperandDict
      for more details.
      """
      # First, try to use a new type-based method for the calculation.
762 763
      # Note that this is based on self (i.e. a resource) instead of context
      # (i.e. a movement).
764
      method = self._getTypeBasedMethod('getPriceCalculationOperandDict')
765
      if method is not None:
766
        return method(default=default, movement=context, REQUEST=REQUEST, **kw)
767

768 769 770 771 772 773 774 775 776
      # Next, try an old type-based method which returns only a final result.
      method = self._getTypeBasedMethod('getPrice')
      if method is not None:
        price = method(default=default, movement=context, REQUEST=REQUEST, **kw)
        if price is not None:
          return {'price': price}
        return default

      # This below is used only if any type-based method is not
777 778 779 780 781
      # available at all. We should provide the default implementation
      # in a Business Template as Resource_getPrice, thus this will not
      # be used in the future. Kept only for backward compatibility in
      # case where the user still uses an older Business Template.

782
      price_parameter_dict = self.getPriceParameterDict(
783 784 785
                                     context=context, REQUEST=REQUEST, **kw)
      # Calculate the unit price
      unit_base_price = None
786
      # Calculate
787 788 789 790 791
#     ((base_price + SUM(additional_price) +
#     variable_value * SUM(variable_additional_price)) *
#     (1 - MIN(1, MAX(SUM(discount_ratio) , exclusive_discount_ratio ))) +
#     SUM(non_discountable_additional_price)) *
#     (1 + SUM(surcharge_ratio))
792 793 794 795 796
      # Or, as (nearly) one single line :
#     ((bp + S(ap) + v * S(vap))
#       * (1 - m(1, M(S(dr), edr)))
#       + S(ndap))
#     * (1 + S(sr))
797 798 799
      # Variable value is dynamically configurable through a python script.
      # It can be anything, depending on business requirements.
      # It can be seen as a way to define a pricing model that not only
800 801
      # depends on discrete variations, but also on a continuous property
      # of the object
802

803
      base_price = price_parameter_dict['base_price']
804
      if base_price in [None, '']:
805 806
        # XXX Compatibility
        # base_price must not be defined on resource
807 808
        base_price = self.getBasePrice()
      if base_price not in [None, '']:
809 810
        unit_base_price = base_price
        # Sum additional price
811
        for additional_price in price_parameter_dict['additional_price']:
812
          unit_base_price += additional_price
813
        # Sum variable additional price
814
        variable_value = self.getPricingVariable(context=context)
815 816
        for variable_additional_price in \
            price_parameter_dict['variable_additional_price']:
817
          unit_base_price += variable_additional_price * variable_value
818
        # Discount
819 820 821
        sum_discount_ratio = 0
        for discount_ratio in price_parameter_dict['discount_ratio']:
          sum_discount_ratio += discount_ratio
822 823 824
        exclusive_discount_ratio = \
            price_parameter_dict['exclusive_discount_ratio']
        d_ratio = 0
825
        d_ratio = max(d_ratio, sum_discount_ratio)
826 827 828 829 830
        if exclusive_discount_ratio not in [None, '']:
          d_ratio = max(d_ratio, exclusive_discount_ratio)
        if d_ratio != 0:
          d_ratio = 1 - min(1, d_ratio)
          unit_base_price = unit_base_price * d_ratio
831 832 833 834 835 836 837 838 839
        # Sum non discountable additional price
        for non_discountable_additional_price in\
            price_parameter_dict['non_discountable_additional_price']:
          unit_base_price += non_discountable_additional_price
        # Surcharge ratio
        sum_surcharge_ratio = 1
        for surcharge_ratio in price_parameter_dict['surcharge_ratio']:
          sum_surcharge_ratio += surcharge_ratio
        unit_base_price = unit_base_price * sum_surcharge_ratio
840 841 842
      # Divide by the priced quantity if not (None, 0)
      if unit_base_price is not None\
          and price_parameter_dict['priced_quantity']:
843
        priced_quantity = price_parameter_dict['priced_quantity']
844
        unit_base_price = unit_base_price / priced_quantity
845
      # Return result
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870
      if unit_base_price is not None:
        return {'price': unit_base_price}
      return default

    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getPrice')
    def getPrice(self, default=None, context=None, REQUEST=None, **kw):
      """
      Return the unit price of a resource in a specific context.
      """
      # see Movement.getPrice
      if isinstance(default, Base) and context is None:
        msg = 'getPrice first argument is supposed to be the default value'\
              ' accessor, the context should be passed as with the context='\
              ' keyword argument'
        warn(msg, DeprecationWarning)
        LOG('ERP5', WARNING, msg)
        context = default
        default = None
      
      operand_dict = self.getPriceCalculationOperandDict(default=default, 
              context=context, REQUEST=REQUEST, **kw)
      if operand_dict is not None:
        return operand_dict['price']
      return default
Yoshinori Okuji's avatar
Yoshinori Okuji committed
871 872 873 874 875 876

    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getQuantityPrecision')
    def getQuantityPrecision(self):
      """Return the floating point precision of a quantity.
      """
877 878 879
      try:
        return int(round(- log(self.getBaseUnitQuantity(), 10),0))
      except TypeError:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
880
        return 0
881
      return 0
882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897


    def _getConversionRatio(self, quantity_unit, variation_list):
      """
      Converts a quantity unit into a ratio in respect to the resource's
      management unit, for the specified variation.
      A quantity can be multiplied by the returned value in order to convert it
      in the management unit.

      'variation_list' parameter may be deprecated:
      cf Measure.getConvertedQuantity
      """
      management_unit = self.getDefaultQuantityUnit()
      if management_unit == quantity_unit:
        return 1.0
      traverse = self.portal_categories['quantity_unit'].unrestrictedTraverse
898
      quantity = self.getQuantityUnitDefinitionRatio(traverse(quantity_unit))
899 900 901 902
      if quantity_unit.split('/', 1)[0] != management_unit.split('/', 1)[0]:
        measure = self.getDefaultMeasure(quantity_unit)
        quantity /= measure.getConvertedQuantity(variation_list)
      else:
903
        quantity /= self.getQuantityUnitDefinitionRatio(traverse(management_unit))
904 905 906 907
      return quantity

    # Unit conversion
    security.declareProtected(Permissions.AccessContentsInformation, 'convertQuantity')
908 909
    def convertQuantity(self, quantity, from_unit, to_unit, variation_list=(),
      transformed_resource=None, transformed_variation_list=()):
910 911 912
      # 'variation_list' parameter may be deprecated:
      # cf Measure.getConvertedQuantity
      try:
913
        result = quantity * self._getConversionRatio(from_unit, variation_list)\
914 915 916 917 918 919 920
                        / self._getConversionRatio(to_unit, variation_list)
      except (ArithmeticError, AttributeError, LookupError, TypeError), error:
        # For compatibility, we only log the error and return None.
        # No exception for the moment.
        LOG('Resource.convertQuantity', WARNING,
            'could not convert quantity for %s (%r)'
            % (self.getRelativeUrl(), error))
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942
        return None

      if transformed_resource is not None:
        variation_text = '\n'.join(variation_list)
        transformed_variation_text = '\n'.join(transformed_variation_list)
        transformed_uid = transformed_resource.getUid()

        query = self.zGetTransformedResourceConversionRatio(\
                    ui = self.getUid(),
                    variation_text = variation_text,
                    transformed_uid = transformed_uid,
                    transformed_variation_text=transformed_variation_text,
                  )
        if len(query) == 0:
          LOG('Resource.convertQuantity', WARNING,
              'could not get Transformation associated to %s -> %s'
              % (transformed_resource.getRelativeUrl(),
                self.getRelativeUrl()))
          return None
        result *= query[0].quantity

      return result
943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getMeasureList')
    def getMeasureList(self):
      """
      Gets the list of Measure objects describing this resource.
      """
      return self.objectValues(portal_type='Measure')

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getDefaultMeasure')
    def getDefaultMeasure(self, quantity_unit=None):
      """
      Returns the measure object associated to quantity_unit.
      If no quantity_unit is specified, the quantity_unit of the resource is used.
      None is returned if the number of found measures differs from 1.
      """
      if quantity_unit is None:
        quantity_unit = self.getQuantityUnit()
      if quantity_unit:
        top = lambda relative_url: relative_url.split('/', 1)[0]

        quantity = top(quantity_unit)
        generic = []
        default = []
        for measure in self.getMeasureList():
          metric_type = measure.getMetricType()
          if metric_type and quantity == top(metric_type) and \
             measure.getDefaultMetricType():
            default.append(measure)
          if quantity == metric_type:
            generic.append(measure)
        result = default or generic
        if len(result) == 1:
          return result[0]

979 980 981 982 983 984 985 986 987 988 989 990 991 992
    def _getQuantityUnitDefinitionDict(self):
      """
      Returns a dictionary representing the Unit Definitions that hold
      for the current resource.
        Keys: quantity_unit categories uids.
        Values: tuple (unit_definition_uid, quantity)
          * unit_definition_uid can be None if the quantity_unit is defined
            as a standard_quantity_unit (no Unit Conversion Definition defines
            it, its definition comes from a Unit Conversion Group)
          * quantity is a float, an amount, expressed in the
            standard_quantity_unit for the base category of the quantity_unit.
            For example, if mass/g is the global standard quantity_unit, all
            definitions for mass/* will be expressed in grams.
      """
993 994
      global_definition_dict = self.\
          QuantityUnitConversionModule_getUniversalDefinitionDict()
995

996 997
      # _getUniversalDefinitionDict is a cached function. Copy the object to
      # avoid modifying it
998
      result = global_definition_dict.copy()
999
      for definition_group in self.objectValues(portal_type= \
1000
          'Quantity Unit Conversion Group'):
1001 1002 1003
        if definition_group.getValidationState() != "validated":
          continue

1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
        standard_quantity_unit_value = definition_group.getQuantityUnitValue()
        if standard_quantity_unit_value is None:
          continue

        uid = standard_quantity_unit_value.getUid()
        try:
          reference_ratio = global_definition_dict[uid][1]
        except KeyError:
          LOG("Resource", WARNING,
              "could not find a global Unit Definition for '%s' while " \
              "indexing local Definition Group '%s'" % \
                  (standard_quantity_unit_value.getRelativeUrl(),
                   definition_group.getRelativeUrl()))
1017 1018
          continue

1019
        for definition in definition_group.objectValues(portal_type= \
1020
            'Quantity Unit Conversion Definition'):
1021 1022 1023
          if definition.getValidationState() != "validated":
            continue

1024 1025 1026
          unit_uid = definition.getQuantityUnitUid()
          if unit_uid is None:
            continue
1027

1028 1029
          definition_ratio = definition.getConversionRatio()
          if not definition_ratio:
1030
            continue
1031

1032 1033
          result[unit_uid] = (definition.getUid(),
                              definition_ratio*reference_ratio)
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055

      return result

    security.declareProtected(Permissions.AccessContentsInformation,
                              'getQuantityUnitConversionDefinitionRowList')
    def getQuantityUnitConversionDefinitionRowList(self):
      """
      Returns a list rows to insert in the quantity_unit_conversion table.
      Used by z_catalog_quantity_unit_conversion_list.
      """
      # XXX If one wanted to add variation-specific Unit Conversion Definitions
      #  he could use an approach very similar to the one used for Measure.
      #  Just add a variation VARCHAR column in quantity_unit_conversion table
      #  (defaulting as "^"). The column would contain the REGEX describing the
      #  variation, exactly as Measure.
      #  Resource_zGetInventoryList would then need expansion to match the
      #  product variation vs the quantity_unit_conversion REGEX.

      uid = self.getUid()
      row_list = []
      for unit_uid, value in self._getQuantityUnitDefinitionDict().iteritems():
        definition_uid, quantity = value
1056 1057 1058 1059
        row_list.append(dict(uid=definition_uid,
                             resource_uid=uid,
                             quantity_unit_uid=unit_uid,
                             quantity=quantity))
1060 1061 1062

      return row_list

1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getMeasureRowList')
    def getMeasureRowList(self):
      """
      Returns a list rows to insert in the measure table.
      Used by z_catalog_measure_list.
      """
      quantity_unit_value = self.getQuantityUnitValue()
      if quantity_unit_value is None:
        return ()

1074 1075
      quantity_unit_definition_dict = self._getQuantityUnitDefinitionDict()

1076 1077 1078 1079 1080
      metric_type_map = {} # duplicate metric_type are not valid

      for measure in self.getMeasureList():
        metric_type = measure.getMetricType()
        if metric_type in metric_type_map:
1081
          metric_type_map[metric_type] = None
1082
        else:
1083
          metric_type_map[metric_type] = measure
1084 1085

      insert_list = []
1086 1087
      for measure in metric_type_map.itervalues():
        if measure is not None:
1088
          insert_list += measure.asCatalogRowList(quantity_unit_definition_dict)
1089

1090 1091 1092 1093 1094 1095 1096
      quantity_unit = quantity_unit_value.getCategoryRelativeUrl()
      if self.getDefaultMeasure(quantity_unit) is None:
          metric_type = quantity_unit.split('/', 1)[0]
          if metric_type and metric_type not in metric_type_map:
            # At this point, we know there is no default measure and we must add
            # a row for the management unit, with the resource's uid as uid, and
            # a generic metric_type.
1097
            quantity_unit_uid = quantity_unit_value.getUid()
1098 1099 1100 1101 1102 1103 1104 1105 1106
            try:
              quantity = quantity_unit_definition_dict[quantity_unit_uid][1]
            except KeyError:
              LOG("Resource", WARNING,
                  "could not find an Unit Definition for '%s' while " \
                  "indexing Resource '%s'" % \
                     (quantity_unit_value.getRelativeUrl(),
                      self.getRelativeUrl()))
              quantity = None
1107

1108 1109 1110 1111
            metric_type_uid = self.getPortalObject().portal_categories \
                                  .getCategoryUid(metric_type, 'metric_type')
            if quantity and metric_type_uid:
              uid = self.getUid()
1112 1113 1114
              insert_list.append(dict(uid=uid, resource_uid=uid, variation='^',
                                  metric_type_uid=metric_type_uid,
                                  quantity=float(quantity)))
1115 1116

      return insert_list
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133

    def getQuantityUnitDefinitionRatio(self, quantity_unit_value):
      """
      get the ratio used to define the quantity unit quantity_unit_value.
      If the Resource has a local Quantity Unit conversion Definition,
      return the ratio from that Definition.
      If not, fetch a Definition in the Global Module.
      """
      portal = self.getPortalObject()
      quantity_unit_uid = quantity_unit_value.getUid()

      deprecated_quantity = quantity_unit_value.getProperty('quantity')
      if deprecated_quantity is not None:
        warn('quantity field of quantity_unit categories is deprecated.' \
           ' Please use Quantity Unit Conversion Definitions instead and' \
           ' reset the value of this field.', DeprecationWarning)

1134
        return float(deprecated_quantity)
1135 1136 1137 1138 1139

      query = self.ResourceModule_zGetQuantityUnitDefinitionRatio(
                            quantity_unit_uid=quantity_unit_uid,
                            resource_uid=self.getUid())
      return query[0].quantity