Resource.py 30 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
from math import log
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31

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

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

from Products.ERP5.Variated import Variated
from Products.ERP5.Core.Resource import Resource as CoreResource
Sebastien Robin's avatar
Sebastien Robin committed
40
from Products.CMFCore.WorkflowCore import WorkflowMethod
41
from Products.CMFCategory.Renderer import Renderer
42
from Products.CMFCore.utils import getToolByName
Jean-Paul Smets's avatar
Jean-Paul Smets committed
43 44 45

from zLOG import LOG

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

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

    # Declarative security
    security = ClassSecurityInfo()
59
    security.declareObjectProtected(Permissions.AccessContentsInformation)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
60 61 62 63 64 65 66 67 68 69 70 71

    # Declarative interfaces
    __implements__ = ( Interface.Variated, )

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

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

          resource.getVariationRangeCategoryItemList
            => [(display, value)]
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
        
      ## 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
113
        """
114
        result = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
115 116 117 118
        if base_category_list is ():
          base_category_list = self.getVariationBaseCategoryList()
        elif type(base_category_list) is type('a'):
          base_category_list = (base_category_list,)
119

120
        other_base_category_dict = dict([(i,1) for i in base_category_list])
Yoshinori Okuji's avatar
Yoshinori Okuji committed
121
        other_variations = self.searchFolder( \
122 123
                               portal_type=self.getPortalVariationTypeList(),
                               sort_on=[('title','ascending')])
Yoshinori Okuji's avatar
Yoshinori Okuji committed
124 125
        other_variations = [x.getObject() for x in other_variations]
        other_variations = [x for x in other_variations if x is not None]
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140

        for object in other_variations:
          for base_category in object.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([object]))
141

142 143 144 145
        other_base_category_item_list = filter(lambda x: x[1]==1, 
            other_base_category_dict.items())
        other_base_category_list = map(lambda x: x[0],
            other_base_category_item_list)
146 147 148 149
        # Get category variation
        if len(other_base_category_list) != 0:
          result += Variated.getVariationRangeCategoryItemList(
              self, base_category_list=other_base_category_list,
150
              base=base, display_base_category=display_base_category, **kw)
151
        # Return result
152
        return result
153

Jean-Paul Smets's avatar
Jean-Paul Smets committed
154 155
    security.declareProtected(Permissions.AccessContentsInformation,
                                           'getVariationCategoryItemList')
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
    def getVariationCategoryItemList(self, base_category_list=(), 
                                     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, 
                         display_id='getTitle' (default value getTitleOrId)
      """
      result = Variated.getVariationCategoryItemList(self, 
                            base_category_list=base_category_list, 
174 175
                            display_base_category=display_base_category, 
                            base=base, **kw)
176
      if not omit_individual_variation:
177 178
        other_variations = self.searchFolder(
                              portal_type=self.getPortalVariationTypeList())
179 180 181 182 183 184 185 186 187 188 189 190

        other_variations = map(lambda x: x.getObject(), other_variations)
        other_variations = filter(lambda x: x is not None, other_variations)

        for object in other_variations:
          for base_category in object.getVariationBaseCategoryList():
            if (base_category_list is ()) or \
               (base_category in base_category_list):
              # XXX append object, relative_url ?
              # XXX now, call Renderer a lot of time.
              # Better implementation needed
              result.extend(Renderer(
191 192 193 194 195 196
                             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([object]))
197 198 199
      return result

    security.declareProtected(Permissions.AccessContentsInformation,
200
                              'getVariationCategoryList')
201
    def getVariationCategoryList(self, base_category_list=(),
202
                                 omit_individual_variation=1, **kw):
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
      """
        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(
232 233
                    base_category_list=base_category_list,
                    omit_individual_variation=omit_individual_variation,**kw)
234
      return map(lambda x: x[1], vcil)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
235 236 237 238 239 240 241 242 243 244 245 246

    # Unit conversion
    security.declareProtected(Permissions.AccessContentsInformation, 'convertQuantity')
    def convertQuantity(self, quantity, from_unit, to_unit):
      return quantity

# 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
247
      except AttributeError:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
248 249 250 251 252 253 254 255
        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
256
      except AttributeError:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
257 258 259 260 261 262 263 264 265 266
        return None


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


Romain Courteaud's avatar
Romain Courteaud committed
267
    ####################################################
Jean-Paul Smets's avatar
Jean-Paul Smets committed
268
    # Stock Management
Romain Courteaud's avatar
Romain Courteaud committed
269 270 271
    ####################################################
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventory')
272
    def getInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
273
      """
274
      Returns inventory
Jean-Paul Smets's avatar
Jean-Paul Smets committed
275
      """
276 277 278
      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
279

Romain Courteaud's avatar
Romain Courteaud committed
280 281
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventory')
282
    def getCurrentInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
283
      """
284
      Returns current inventory
Jean-Paul Smets's avatar
Jean-Paul Smets committed
285
      """
286 287 288
      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
289

Romain Courteaud's avatar
Romain Courteaud committed
290 291
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getAvailableInventory')
292
    def getAvailableInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
293
      """
294 295
      Returns available inventory
      (current inventory - deliverable)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
296
      """
297 298 299
      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
300

Romain Courteaud's avatar
Romain Courteaud committed
301 302
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventory')
303
    def getFutureInventory(self, **kw):
304 305 306
      """
      Returns inventory at infinite
      """
307 308 309
      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
310

Romain Courteaud's avatar
Romain Courteaud committed
311 312
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryList')
313 314 315 316
    def getInventoryList(self, **kw):
      """
      Returns list of inventory grouped by section or site
      """
317 318 319
      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
320

Romain Courteaud's avatar
Romain Courteaud committed
321 322
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryList')
323
    def getCurrentInventoryList(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
324
      """
325
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
326
      """
327 328 329 330 331 332 333 334 335 336 337 338 339
      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
340

Romain Courteaud's avatar
Romain Courteaud committed
341 342
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryList')
343
    def getFutureInventoryList(self, **kw):
344 345 346
      """
      Returns list of inventory grouped by section or site
      """
347 348 349
      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
350

Romain Courteaud's avatar
Romain Courteaud committed
351 352
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryStat')
353
    def getInventoryStat(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
354
      """
355
      Returns statistics of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
356
      """
357 358 359
      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
360

Romain Courteaud's avatar
Romain Courteaud committed
361 362
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryStat')
363
    def getCurrentInventoryStat(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
364
      """
365
      Returns statistics of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
366
      """
367 368 369 370 371 372 373 374 375 376 377 378 379
      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
380

Romain Courteaud's avatar
Romain Courteaud committed
381 382
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryStat')
383
    def getFutureInventoryStat(self, **kw):
384 385 386
      """
      Returns statistics of inventory grouped by section or site
      """
387 388 389
      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
390

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

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

Romain Courteaud's avatar
Romain Courteaud committed
411 412
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryChart')
413
    def getFutureInventoryChart(self, **kw):
414 415 416
      """
      Returns list of inventory grouped by section or site
      """
417 418 419
      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
420

Romain Courteaud's avatar
Romain Courteaud committed
421 422
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryHistoryList')
423
    def getInventoryHistoryList(self, **kw):
424 425 426
      """
      Returns list of inventory grouped by section or site
      """
427 428 429
      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
430

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

441 442 443 444
    # XXX FIXME
    # Method getCurrentMovementHistoryList, 
    # getAvailableMovementHistoryList, getFutureMovementHistoryList
    # can be added
Romain Courteaud's avatar
Romain Courteaud committed
445 446
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getMovementHistoryList')
447
    def getMovementHistoryList(self, **kw):
448 449 450
      """
      Returns list of inventory grouped by section or site
      """
451 452 453
      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
454

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

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


476
    # Asset Price API
477 478
    security.declareProtected(Permissions.AccessContentsInformation,
        'getInventoryAssetPrice')
479
    def getInventoryAssetPrice(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
480
      """
481
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
482
      """
483 484 485
      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
486

487 488
    security.declareProtected(Permissions.AccessContentsInformation,
        'getCurrentInventoryAssetPrice')
489
    def getCurrentInventoryAssetPrice(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
490
      """
491
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
492
      """
493 494 495
      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
496

497 498
    security.declareProtected(Permissions.AccessContentsInformation,
        'getAvailableInventoryAssetPrice')
499
    def getAvailableInventoryAssetPrice(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.getAvailableInventoryAssetPrice(**kw)
506

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

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

518
    # Industrial price API
519 520
    security.declareProtected(Permissions.AccessContentsInformation,
        'getIndustrialPrice')
521 522 523 524 525 526 527 528 529 530 531 532
    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

533
    # Predicate handling
534 535
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'asPredicate')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
536 537 538 539 540 541 542
    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',))
543 544
      p.setMembershipCriterionCategoryList(('resource/%s'
          % self.getRelativeUrl(),))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
545
      return p
546

547
    def _pricingSortMethod(self, a, b):
Alexandre Boeglin's avatar
Alexandre Boeglin committed
548 549 550 551 552 553 554
      # Simple method : the one that defines a destination wins
      parent = a.getParentValue()
      if parent.getPortalType().endswith(' Line'):
        parent = parent.getParentValue()
      if parent.getDestination():
        return -1 # a has a destination and wins
      return 1 # a has no destination ans loses
555

556
    security.declareProtected(Permissions.AccessContentsInformation, 
557 558
                              '_getPriceParameterDict')
    def _getPriceParameterDict(self, context=None, REQUEST=None, **kw):
559
      """
560
      Get all pricing parameters from Predicate.
561
      """
562
      # Search all categories context
563
      new_category_list = []
564
      if context is not None:
565
        new_category_list += context.getCategoryList()
566 567
      #XXX This should be 'category_list' instead of 'categories' to respect
      # the naming convention. Must take care of side effects when fixing
568 569 570
      if kw.has_key('categories'):
        new_category_list.extend(kw['categories'])
        del kw['categories']
571 572
      resource_category = 'resource/' + self.getRelativeUrl()
      if not resource_category in new_category_list:
573 574 575
        new_category_list += (resource_category, )
      # Generate the predicate mapped value
      # to get some price values.
576
      domain_tool = getToolByName(self,'portal_domains')
577
      portal_type_list = self.getPortalSupplyPathTypeList()
578

Alexandre Boeglin's avatar
Alexandre Boeglin committed
579 580 581 582 583 584 585 586 587 588 589
      # 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)
590 591 592
      # Get price parameters
      price_parameter_dict = {
        'base_price': None,
593 594 595
        'additional_price': [],
        'surcharge_ratio': [],
        'discount_ratio': [],
596
        'exclusive_discount_ratio': None,
597 598
        'variable_additional_price': [],
        'non_discountable_additional_price': [],
599
      }
Alexandre Boeglin's avatar
Alexandre Boeglin committed
600 601 602 603 604 605 606 607 608 609 610 611
      if mapped_value is None:
        return price_parameter_dict
      for price_parameter_name in price_parameter_dict.keys():
        price_parameter_value = \
          mapped_value.getProperty(price_parameter_name)
        if price_parameter_value not in [None, '']:
          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':
612
                price_parameter_dict[price_parameter_name] = \
Alexandre Boeglin's avatar
Alexandre Boeglin committed
613 614 615 616
                    max(price_parameter_value)
              else:
                price_parameter_dict[price_parameter_name] = \
                    price_parameter_value[0]
617 618
      return price_parameter_dict
      
619 620
    security.declareProtected(Permissions.AccessContentsInformation,
        '_getPricingVariable')
621
    def _getPricingVariable(self, context=None):
622 623 624 625
      """
      Return the value of the property used to calculate variable pricing
      This basically calls a script like Product_getPricingVariable
      """
626
      method = None
627
      if context is not None:
628
        method = context._getTypeBasedMethod('getPricingVariable')
629
      if method is None or context is None:
630
        method = self._getTypeBasedMethod('getPricingVariable')
631

632 633 634 635
      if method is None:
        return 0.0
      return float(method())

636 637
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getPrice')
638
    def getPrice(self, default=None, context=None, REQUEST=None, **kw):
639 640 641
      """
      Return the unit price of a resource in a specific context.
      """
642 643 644 645 646 647 648 649 650 651
      # 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
      
652 653 654 655
      price_parameter_dict = self._getPriceParameterDict(
                                     context=context, REQUEST=REQUEST, **kw)
      # Calculate the unit price
      unit_base_price = None
656
      # Calculate
657 658 659 660 661
#     ((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))
662 663 664 665 666
      # Or, as (nearly) one single line :
#     ((bp + S(ap) + v * S(vap))
#       * (1 - m(1, M(S(dr), edr)))
#       + S(ndap))
#     * (1 + S(sr))
667 668 669
      # 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
670 671
      # depends on discrete variations, but also on a continuous property
      # of the object
672

673
      base_price = price_parameter_dict['base_price']
674
      if base_price in [None, '']:
675 676
        # XXX Compatibility
        # base_price must not be defined on resource
677 678
        base_price = self.getBasePrice()
      if base_price not in [None, '']:
679 680
        unit_base_price = base_price
        # Sum additional price
681
        for additional_price in price_parameter_dict['additional_price']:
682
          unit_base_price += additional_price
683
        # Sum variable additional price
684
        variable_value = self._getPricingVariable(context=context)
685 686
        for variable_additional_price in \
            price_parameter_dict['variable_additional_price']:
687
          unit_base_price += variable_additional_price * variable_value
688
        # Discount
689 690 691
        sum_discount_ratio = 0
        for discount_ratio in price_parameter_dict['discount_ratio']:
          sum_discount_ratio += discount_ratio
692 693 694
        exclusive_discount_ratio = \
            price_parameter_dict['exclusive_discount_ratio']
        d_ratio = 0
695
        d_ratio = max(d_ratio, sum_discount_ratio)
696 697 698 699 700
        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
701 702 703 704 705 706 707 708 709
        # 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
710 711
      # Divide by the priced quantity
      if unit_base_price is not None:
712
        priced_quantity = self.getPricedQuantity()
713
        unit_base_price = unit_base_price / priced_quantity
714 715
      # Return result
      return unit_base_price
Yoshinori Okuji's avatar
Yoshinori Okuji committed
716 717 718 719 720 721

    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getQuantityPrecision')
    def getQuantityPrecision(self):
      """Return the floating point precision of a quantity.
      """
722 723 724
      try:
        return int(round(- log(self.getBaseUnitQuantity(), 10),0))
      except TypeError:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
725
        return 0
726 727 728
      return 0


729