Resource.py 45.8 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 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getDefaultTransformationValue')
    def getDefaultTransformationValue(self, context):
      transformation_list = self.portal_domains.searchPredicateList(context,
                                portal_type="Transformation")

      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
      transformation from the complex transformation returned by
      getDefaultTransformationValue
      """
      method = context._getTypeBasedMethod(\
                        'getDefaultConversionTransformationValue')
      if method is not None:
        return method()

      transformation_list = self.portal_catalog(portal_type="Transformation",
                                            resource_category_uid=self.getUid())
      if len(transformation_list) > 0:
        return transformation_list[0].getObject()


Romain Courteaud's avatar
Romain Courteaud committed
319
    ####################################################
Jean-Paul Smets's avatar
Jean-Paul Smets committed
320
    # Stock Management
Romain Courteaud's avatar
Romain Courteaud committed
321 322 323
    ####################################################
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventory')
324
    def getInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
325
      """
326
      Returns inventory
Jean-Paul Smets's avatar
Jean-Paul Smets committed
327
      """
328 329 330
      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
331

Romain Courteaud's avatar
Romain Courteaud committed
332 333
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventory')
334
    def getCurrentInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
335
      """
336
      Returns current inventory
Jean-Paul Smets's avatar
Jean-Paul Smets committed
337
      """
338 339 340
      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
341

Romain Courteaud's avatar
Romain Courteaud committed
342 343
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getAvailableInventory')
344
    def getAvailableInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
345
      """
346 347
      Returns available inventory
      (current inventory - deliverable)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
348
      """
349 350 351
      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
352

Romain Courteaud's avatar
Romain Courteaud committed
353 354
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventory')
355
    def getFutureInventory(self, **kw):
356 357 358
      """
      Returns inventory at infinite
      """
359 360 361
      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
362

Romain Courteaud's avatar
Romain Courteaud committed
363 364
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryList')
365 366 367 368
    def getInventoryList(self, **kw):
      """
      Returns list of inventory grouped by section or site
      """
369 370 371
      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
372

Romain Courteaud's avatar
Romain Courteaud committed
373 374
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryList')
375
    def getCurrentInventoryList(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
376
      """
377
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
378
      """
379 380 381 382 383 384 385 386 387 388 389 390 391
      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
392

Romain Courteaud's avatar
Romain Courteaud committed
393 394
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryList')
395
    def getFutureInventoryList(self, **kw):
396 397 398
      """
      Returns list of inventory grouped by section or site
      """
399 400 401
      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
402

Romain Courteaud's avatar
Romain Courteaud committed
403 404
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryStat')
405
    def getInventoryStat(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
406
      """
407
      Returns statistics of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
408
      """
409 410 411
      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
412

Romain Courteaud's avatar
Romain Courteaud committed
413 414
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryStat')
415
    def getCurrentInventoryStat(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
416
      """
417
      Returns statistics of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
418
      """
419 420 421 422 423 424 425 426 427 428 429 430 431
      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
432

Romain Courteaud's avatar
Romain Courteaud committed
433 434
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryStat')
435
    def getFutureInventoryStat(self, **kw):
436 437 438
      """
      Returns statistics of inventory grouped by section or site
      """
439 440 441
      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
442

Romain Courteaud's avatar
Romain Courteaud committed
443 444
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryChart')
445
    def getInventoryChart(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
446
      """
447
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
448
      """
449 450 451
      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
452

Romain Courteaud's avatar
Romain Courteaud committed
453 454
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryChart')
455
    def getCurrentInventoryChart(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
456
      """
457
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
458
      """
459 460 461
      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
462

Romain Courteaud's avatar
Romain Courteaud committed
463 464
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryChart')
465
    def getFutureInventoryChart(self, **kw):
466 467 468
      """
      Returns list of inventory grouped by section or site
      """
469 470 471
      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
472

Romain Courteaud's avatar
Romain Courteaud committed
473 474
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryHistoryList')
475
    def getInventoryHistoryList(self, **kw):
476 477 478
      """
      Returns list of inventory grouped by section or site
      """
479 480 481
      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
482

Romain Courteaud's avatar
Romain Courteaud committed
483 484
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryHistoryChart')
485
    def getInventoryHistoryChart(self, **kw):
486 487 488
      """
      Returns list of inventory grouped by section or site
      """
489 490 491
      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
492

493 494 495 496
    # XXX FIXME
    # Method getCurrentMovementHistoryList, 
    # getAvailableMovementHistoryList, getFutureMovementHistoryList
    # can be added
Romain Courteaud's avatar
Romain Courteaud committed
497 498
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getMovementHistoryList')
499
    def getMovementHistoryList(self, **kw):
500 501 502
      """
      Returns list of inventory grouped by section or site
      """
503 504 505
      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
506

Romain Courteaud's avatar
Romain Courteaud committed
507 508
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getMovementHistoryStat')
509
    def getMovementHistoryStat(self, **kw):
510 511 512
      """
      Returns list of inventory grouped by section or site
      """
513 514 515
      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
516

Romain Courteaud's avatar
Romain Courteaud committed
517 518
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getNextNegativeInventoryDate')
519
    def getNextNegativeInventoryDate(self, **kw):
520 521 522
      """
      Returns list of inventory grouped by section or site
      """
523 524 525
      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
526 527


528
    # Asset Price API
529 530
    security.declareProtected(Permissions.AccessContentsInformation,
        'getInventoryAssetPrice')
531
    def getInventoryAssetPrice(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
532
      """
533
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
534
      """
535 536 537
      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
538

539 540
    security.declareProtected(Permissions.AccessContentsInformation,
        'getCurrentInventoryAssetPrice')
541
    def getCurrentInventoryAssetPrice(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
542
      """
543
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
544
      """
545 546 547
      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
548

549 550
    security.declareProtected(Permissions.AccessContentsInformation,
        'getAvailableInventoryAssetPrice')
551
    def getAvailableInventoryAssetPrice(self, **kw):
552 553 554
      """
      Returns list of inventory grouped by section or site
      """
555 556 557
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getAvailableInventoryAssetPrice(**kw)
558

559 560
    security.declareProtected(Permissions.AccessContentsInformation,
        'getFutureInventoryAssetPrice')
561
    def getFutureInventoryAssetPrice(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
562
      """
563
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
564
      """
565 566 567
      kw['resource_uid'] = self.getUid()
      portal_simulation = getToolByName(self, 'portal_simulation')
      return portal_simulation.getFutureInventoryAssetPrice(**kw)
568

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

570
    # Industrial price API
571 572
    security.declareProtected(Permissions.AccessContentsInformation,
        'getIndustrialPrice')
573 574 575 576 577 578 579 580 581 582 583 584
    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

585
    # Predicate handling
586 587
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'asPredicate')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
588 589 590 591 592 593 594
    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',))
595 596
      p.setMembershipCriterionCategoryList(('resource/%s'
          % self.getRelativeUrl(),))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
597
      return p
598

599
    def _pricingSortMethod(self, a, b):
600 601 602 603
      # 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
604

605
    security.declareProtected(Permissions.AccessContentsInformation, 
606
                              'getPriceParameterDict')
607
    def getPriceParameterDict(self, context=None, REQUEST=None, **kw):
608
      """
609
      Get all pricing parameters from Predicate.
610
      """
611
      # Search all categories context
612
      new_category_list = []
613
      if context is not None:
614
        new_category_list += context.getCategoryList()
615 616
      #XXX This should be 'category_list' instead of 'categories' to respect
      # the naming convention. Must take care of side effects when fixing
617 618 619
      if kw.has_key('categories'):
        new_category_list.extend(kw['categories'])
        del kw['categories']
620 621
      resource_category = 'resource/' + self.getRelativeUrl()
      if not resource_category in new_category_list:
622 623 624
        new_category_list += (resource_category, )
      # Generate the predicate mapped value
      # to get some price values.
625
      domain_tool = getToolByName(self,'portal_domains')
626
      portal_type_list = self.getPortalSupplyPathTypeList()
627

Alexandre Boeglin's avatar
Alexandre Boeglin committed
628 629 630 631 632 633 634 635 636 637 638
      # 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)
639 640 641
      # Get price parameters
      price_parameter_dict = {
        'base_price': None,
642 643 644
        'additional_price': [],
        'surcharge_ratio': [],
        'discount_ratio': [],
645
        'exclusive_discount_ratio': None,
646 647
        'variable_additional_price': [],
        'non_discountable_additional_price': [],
648
        'priced_quantity': None,
649
        'base_unit_price': None,
650
      }
Alexandre Boeglin's avatar
Alexandre Boeglin committed
651 652 653 654
      if mapped_value is None:
        return price_parameter_dict
      for price_parameter_name in price_parameter_dict.keys():
        price_parameter_value = \
655 656 657
          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
658 659 660 661 662 663
          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':
664
                price_parameter_dict[price_parameter_name] = \
Alexandre Boeglin's avatar
Alexandre Boeglin committed
665 666 667 668
                    max(price_parameter_value)
              else:
                price_parameter_dict[price_parameter_name] = \
                    price_parameter_value[0]
669 670
      return price_parameter_dict
      
671
    security.declareProtected(Permissions.AccessContentsInformation,
672 673
        'getPricingVariable')
    def getPricingVariable(self, context=None):
674 675 676 677
      """
      Return the value of the property used to calculate variable pricing
      This basically calls a script like Product_getPricingVariable
      """
678 679 680
      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)
681
      method = None
682
      if context is not None:
683
        method = context._getTypeBasedMethod('getPricingVariable')
684
      if method is None or context is None:
685
        method = self._getTypeBasedMethod('getPricingVariable')
686

687 688 689 690
      if method is None:
        return 0.0
      return float(method())

691
    security.declareProtected(Permissions.AccessContentsInformation, 
692 693 694 695 696 697 698 699
                              '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.
700 701
      # Note that this is based on self (i.e. a resource) instead of context
      # (i.e. a movement).
702
      method = self._getTypeBasedMethod('getPriceCalculationOperandDict')
703
      if method is not None:
704
        return method(default=default, movement=context, REQUEST=REQUEST, **kw)
705

706 707 708 709 710 711 712 713 714
      # 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
715 716 717 718 719
      # 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.

720
      price_parameter_dict = self.getPriceParameterDict(
721 722 723
                                     context=context, REQUEST=REQUEST, **kw)
      # Calculate the unit price
      unit_base_price = None
724
      # Calculate
725 726 727 728 729
#     ((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))
730 731 732 733 734
      # Or, as (nearly) one single line :
#     ((bp + S(ap) + v * S(vap))
#       * (1 - m(1, M(S(dr), edr)))
#       + S(ndap))
#     * (1 + S(sr))
735 736 737
      # 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
738 739
      # depends on discrete variations, but also on a continuous property
      # of the object
740

741
      base_price = price_parameter_dict['base_price']
742
      if base_price in [None, '']:
743 744
        # XXX Compatibility
        # base_price must not be defined on resource
745 746
        base_price = self.getBasePrice()
      if base_price not in [None, '']:
747 748
        unit_base_price = base_price
        # Sum additional price
749
        for additional_price in price_parameter_dict['additional_price']:
750
          unit_base_price += additional_price
751
        # Sum variable additional price
752
        variable_value = self.getPricingVariable(context=context)
753 754
        for variable_additional_price in \
            price_parameter_dict['variable_additional_price']:
755
          unit_base_price += variable_additional_price * variable_value
756
        # Discount
757 758 759
        sum_discount_ratio = 0
        for discount_ratio in price_parameter_dict['discount_ratio']:
          sum_discount_ratio += discount_ratio
760 761 762
        exclusive_discount_ratio = \
            price_parameter_dict['exclusive_discount_ratio']
        d_ratio = 0
763
        d_ratio = max(d_ratio, sum_discount_ratio)
764 765 766 767 768
        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
769 770 771 772 773 774 775 776 777
        # 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
778 779 780
      # Divide by the priced quantity if not (None, 0)
      if unit_base_price is not None\
          and price_parameter_dict['priced_quantity']:
781
        priced_quantity = price_parameter_dict['priced_quantity']
782
        unit_base_price = unit_base_price / priced_quantity
783
      # Return result
784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
      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
809 810 811 812 813 814

    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getQuantityPrecision')
    def getQuantityPrecision(self):
      """Return the floating point precision of a quantity.
      """
815 816 817
      try:
        return int(round(- log(self.getBaseUnitQuantity(), 10),0))
      except TypeError:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
818
        return 0
819
      return 0
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835


    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
836
      quantity = self.getQuantityUnitDefinitionRatio(traverse(quantity_unit))
837 838 839 840
      if quantity_unit.split('/', 1)[0] != management_unit.split('/', 1)[0]:
        measure = self.getDefaultMeasure(quantity_unit)
        quantity /= measure.getConvertedQuantity(variation_list)
      else:
841
        quantity /= self.getQuantityUnitDefinitionRatio(traverse(management_unit))
842 843 844 845 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 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
      return quantity

    # Unit conversion
    security.declareProtected(Permissions.AccessContentsInformation, 'convertQuantity')
    def convertQuantity(self, quantity, from_unit, to_unit, variation_list=()):
      # 'variation_list' parameter may be deprecated:
      # cf Measure.getConvertedQuantity
      try:
        return quantity * self._getConversionRatio(from_unit, variation_list) \
                        / 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))

    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]

894 895 896 897 898 899 900 901 902 903 904 905 906 907
    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.
      """
908 909
      global_definition_dict = self.\
          QuantityUnitConversionModule_getUniversalDefinitionDict()
910

911 912
      # _getUniversalDefinitionDict is a cached function. Copy the object to
      # avoid modifying it
913
      result = global_definition_dict.copy()
914
      for definition_group in self.objectValues(portal_type= \
915
          'Quantity Unit Conversion Group'):
916 917 918
        if definition_group.getValidationState() != "validated":
          continue

919 920 921 922 923 924 925 926 927 928 929 930 931
        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()))
932 933
          continue

934
        for definition in definition_group.objectValues(portal_type= \
935
            'Quantity Unit Conversion Definition'):
936 937 938
          if definition.getValidationState() != "validated":
            continue

939 940 941
          unit_uid = definition.getQuantityUnitUid()
          if unit_uid is None:
            continue
942

943 944 945
          quantity = definition.getQuantity()
          if not quantity:
            continue
946

947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969
          result[unit_uid] = (definition.getUid(), quantity*reference_ratio)

      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
970 971 972 973
        row_list.append(dict(uid=definition_uid,
                             resource_uid=uid,
                             quantity_unit_uid=unit_uid,
                             quantity=quantity))
974 975 976

      return row_list

977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getTransformationRowList')
    def getTransformationRowList(self):
      """
      Returns a list of rows to insert in the transformation table.
      Used by z_catalog_transformation_list
      """
      from Products.ERP5Type.Document import newTempMovement
      resource_uid = self.getUid()

      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)

      transformation = self.getDefaultConversionTransformationValue()

      kw = dict(resource=self.getRelativeUrl(), quantity=1.0)
      row_list = []
      for i, variation_list in enumerate(cartesianProduct(variation_list_list)):
        # We must have an unique movement ID for each movement, or we might
        # hit the cache due to similar physical paths
        movement = newTempMovement(self, 'temp_%s' % i,
                                    variation_category_list=variation_list,
                                    **kw)

        base_row = dict(uid=resource_uid,
                        variation_text=movement.getVariationText())
        for amount in transformation.getAggregatedAmountList(movement):
          transformed_resource_uid = amount.getResourceUid()
          quantity = amount.getQuantity()
          if transformed_resource_uid is not None and quantity is not None:
            row = base_row.copy()
            row.update(transformed_uid=transformed_resource_uid,
                       transformed_variation_text=amount.getVariationText(),
                       quantity=quantity)
            row_list.append(row)
      return row_list


1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
    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 ()

1030 1031
      quantity_unit_definition_dict = self._getQuantityUnitDefinitionDict()

1032 1033 1034 1035 1036
      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:
1037
          metric_type_map[metric_type] = None
1038
        else:
1039
          metric_type_map[metric_type] = measure
1040 1041

      insert_list = []
1042 1043
      for measure in metric_type_map.itervalues():
        if measure is not None:
1044
          insert_list += measure.asCatalogRowList(quantity_unit_definition_dict)
1045

1046 1047 1048 1049 1050 1051 1052
      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.
1053
            quantity_unit_uid = quantity_unit_value.getUid()
1054 1055 1056 1057 1058 1059 1060 1061 1062
            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
1063

1064 1065 1066 1067
            metric_type_uid = self.getPortalObject().portal_categories \
                                  .getCategoryUid(metric_type, 'metric_type')
            if quantity and metric_type_uid:
              uid = self.getUid()
1068 1069 1070
              insert_list.append(dict(uid=uid, resource_uid=uid, variation='^',
                                  metric_type_uid=metric_type_uid,
                                  quantity=float(quantity)))
1071 1072

      return insert_list
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089

    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)

1090
        return float(deprecated_quantity)
1091 1092 1093 1094 1095

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