Delivery.py 25.1 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2
##############################################################################
#
Romain Courteaud's avatar
Romain Courteaud committed
3
# Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
4
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Romain Courteaud's avatar
Romain Courteaud committed
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 30 31 32 33 34 35
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

from Globals import InitializeClass, PersistentMapping
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.WorkflowCore import WorkflowMethod
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Products.ERP5Type.XMLObject import XMLObject
36
from Products.ERP5Type.Base import Base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
37 38 39
from Products.ERP5.Document.DeliveryCell import DeliveryCell
from Acquisition import Explicit, Implicit
from Products.PythonScripts.Utility import allow_class
40
from DateTime import DateTime
Jean-Paul Smets's avatar
Jean-Paul Smets committed
41 42 43 44

from zLOG import LOG

class Delivery(XMLObject):
45 46 47 48
    """
        Each time delivery is modified, it MUST launch a reindexing of
        inventories which are related to the resources contained in the Delivery
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
49 50 51 52 53
    # CMF Type Definition
    meta_type = 'ERP5 Delivery'
    portal_type = 'Delivery'
    isPortalContent = 1
    isRADContent = 1
54
    isDelivery = 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
55 56 57

    # Declarative security
    security = ClassSecurityInfo()
58
    security.declareObjectProtected(Permissions.AccessContentsInformation)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93

    # Default Properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
                      , PropertySheet.CategoryCore
                      , PropertySheet.DublinCore
                      , PropertySheet.Task
                      , PropertySheet.Arrow
                      , PropertySheet.Movement
                      , PropertySheet.Delivery
                      , PropertySheet.Reference
                      )

    security.declareProtected(Permissions.AccessContentsInformation, 'isAccountable')
    def isAccountable(self):
      """
        Returns 1 if this needs to be accounted
        Only account movements which are not associated to a delivery
        Whenever delivery is there, delivery has priority
      """
      return 1

    # Pricing methods
    def _getTotalPrice(self, context):
      return 2.0

    def _getDefaultTotalPrice(self, context):
      return 3.0

    def _getSourceTotalPrice(self, context):
      return 4.0

    def _getDestinationTotalPrice(self, context):
      return 5.0

Yoshinori Okuji's avatar
Yoshinori Okuji committed
94
    security.declareProtected(Permissions.AccessContentsInformation, 'getDefaultTotalPrice')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    def getDefaultTotalPrice(self, context=None, REQUEST=None, **kw):
      """
      """
      return self._getDefaultTotalPrice(self.asContext(context=context, REQUEST=REQUEST, **kw))

    security.declareProtected(Permissions.AccessContentsInformation, 'getSourceTotalPrice')
    def getSourceTotalPrice(self, context=None, REQUEST=None, **kw):
      """
      """
      return self._getSourceTotalPrice(self.asContext(context=context, REQUEST=REQUEST, **kw))

    security.declareProtected(Permissions.AccessContentsInformation, 'getDestinationTotalPrice')
    def getDestinationTotalPrice(self, context=None, REQUEST=None, **kw):
      """
      """
      return self._getDestinationTotalPrice(self.asContext(context=context, REQUEST=REQUEST, **kw))

    # Pricing
Jean-Paul Smets's avatar
Jean-Paul Smets committed
113 114 115 116 117 118
    security.declareProtected( Permissions.ModifyPortalContent, 'updatePrice' )
    def updatePrice(self):
      for c in self.objectValues():
        if hasattr(aq_base(c), 'updatePrice'):
          c.updatePrice()

119 120 121 122 123 124 125
    security.declareProtected( Permissions.AccessContentsInformation,
                               'getTotalPrice')
    def getTotalPrice(self, fast=1, src__=0, **kw):
      """ Returns the total price for this order
        if the `fast` argument is set to a true value, then it use
        SQLCatalog to compute the price, otherwise it sums the total
        price of objects one by one.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
126
      """
127 128 129 130 131
      if not fast :
        kw.setdefault( 'portal_type',
                       self.getPortalDeliveryMovementTypeList())
        return sum([ line.getTotalPrice(fast=0) for line in
                        self.objectValues(**kw) ])
132
      kw['explanation_uid'] = self.getUid()
133 134
      kw.update(self.portal_catalog.buildSQLQuery(**kw))
      if src__:
135 136
        return self.Delivery_zGetTotal(src__=1, **kw)
      aggregate = self.Delivery_zGetTotal(**kw)[0]
137
      return aggregate.total_price or 0
Jean-Paul Smets's avatar
Jean-Paul Smets committed
138

Romain Courteaud's avatar
Romain Courteaud committed
139 140
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getTotalQuantity')
141 142 143 144 145
    def getTotalQuantity(self, fast=1, src__=0, **kw):
      """ Returns the total quantity of this order.
        if the `fast` argument is set to a true value, then it use
        SQLCatalog to compute the quantity, otherwise it sums the total
        quantity of objects one by one.
146
      """
147
      if not fast :
Romain Courteaud's avatar
Romain Courteaud committed
148 149
        kw.setdefault('portal_type',
                      self.getPortalDeliveryMovementTypeList())
150 151
        return sum([ line.getTotalQuantity(fast=0) for line in
                        self.objectValues(**kw) ])
152
      kw['explanation_uid'] = self.getUid()
153 154
      kw.update(self.portal_catalog.buildSQLQuery(**kw))
      if src__:
155 156
        return self.Delivery_zGetTotal(src__=1, **kw)
      aggregate = self.Delivery_zGetTotal(**kw)[0]
157
      return aggregate.total_quantity or 0
Jean-Paul Smets's avatar
Jean-Paul Smets committed
158

Jérome Perrin's avatar
Jérome Perrin committed
159 160
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getDeliveryUid')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
161 162 163
    def getDeliveryUid(self):
      return self.getUid()

Jérome Perrin's avatar
Jérome Perrin committed
164 165
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getDeliveryValue')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
166
    def getDeliveryValue(self):
167 168 169 170 171
      """
      Deprecated, we should use getRootDeliveryValue instead
      """
      return self

Jérome Perrin's avatar
Jérome Perrin committed
172 173
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getRootDeliveryValue')
174 175 176 177 178
    def getRootDeliveryValue(self):
      """
      This method returns the delivery, it is usefull to retrieve the delivery
      from a line or a cell
      """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
179 180
      return self

Jérome Perrin's avatar
Jérome Perrin committed
181 182
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getDelivery')
183 184 185
    def getDelivery(self):
      return self.getRelativeUrl()

186
    security.declareProtected(Permissions.AccessContentsInformation,
Romain Courteaud's avatar
Romain Courteaud committed
187 188
                             'getMovementList')
    def getMovementList(self, portal_type=None, **kw):
189 190 191
      """
        Return a list of movements.
      """
192 193
      if portal_type is None:
        portal_type = self.getPortalMovementTypeList()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
194
      movement_list = []
195
      for m in self.contentValues(filter={'portal_type': portal_type}):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
196
        if m.hasCellContent():
197
          for c in m.contentValues(filter={'portal_type': portal_type}):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
198 199 200 201 202
            movement_list.append(c)
        else:
          movement_list.append(m)
      return movement_list

Jérome Perrin's avatar
Jérome Perrin committed
203 204
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getSimulatedMovementList')
205 206 207 208 209
    def getSimulatedMovementList(self):
      """
        Return a list of simulated movements.
        This does not contain Container Line or Container Cell.
      """
Jérome Perrin's avatar
Jérome Perrin committed
210 211
      return self.getMovementList(portal_type=
                          self.getPortalSimulatedMovementTypeList())
212

Jérome Perrin's avatar
Jérome Perrin committed
213 214
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getInvoiceMovementList')
215 216 217 218 219
    def getInvoiceMovementList(self):
      """
        Return a list of simulated movements.
        This does not contain Container Line or Container Cell.
      """
Jérome Perrin's avatar
Jérome Perrin committed
220 221
      return self.getMovementList(portal_type=
                            self.getPortalInvoiceMovementTypeList())
222

Jérome Perrin's avatar
Jérome Perrin committed
223 224
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getContainerList')
225 226 227 228 229 230
    def getContainerList(self):
      """
        Return a list of root containers.
        This does not contain sub-containers.
      """
      container_list = []
Jérome Perrin's avatar
Jérome Perrin committed
231 232
      for m in self.contentValues(filter={'portal_type':
                                  self.getPortalContainerTypeList()}):
233 234 235
        container_list.append(m)
      return container_list

236 237
    def applyToDeliveryRelatedMovement(self, portal_type='Simulation Movement',
        method_id = 'expand',**kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
238
      for my_simulation_movement in self.getDeliveryRelatedValueList(
Jérome Perrin's avatar
Jérome Perrin committed
239
                                      portal_type = 'Simulation Movement'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
240
          # And apply
241
          getattr(my_simulation_movement.getObject(), method_id)(**kw)
Jérome Perrin's avatar
Jérome Perrin committed
242 243
      for m in self.contentValues(filter={'portal_type':
                                      self.getPortalMovementTypeList()}):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
244 245
        # Find related in simulation
        for my_simulation_movement in m.getDeliveryRelatedValueList(
Jérome Perrin's avatar
Jérome Perrin committed
246
                                  portal_type = 'Simulation Movement'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
247
          # And apply
248
          getattr(my_simulation_movement.getObject(), method_id)(**kw)
Jérome Perrin's avatar
Jérome Perrin committed
249 250
        for c in m.contentValues(filter={'portal_type':
                                        self.getPortalMovementTypeList()}):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
251
          for my_simulation_movement in c.getDeliveryRelatedValueList(
Jérome Perrin's avatar
Jérome Perrin committed
252
                                  portal_type = 'Simulation Movement'):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
253
            # And apply
254
            getattr(my_simulation_movement.getObject(), method_id)(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
255 256 257 258 259


    #######################################################
    # Causality computation
    security.declareProtected(Permissions.View, 'isConvergent')
260
    def isConvergent(self,**kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
261 262 263
      """
        Returns 0 if the target is not met
      """
264
      return int(not self.isDivergent(**kw))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
265

Jean-Paul Smets's avatar
Jean-Paul Smets committed
266 267
    security.declareProtected(Permissions.View, 'isSimulated')
    def isSimulated(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
268 269 270 271
      """
        Returns 1 if all movements have a delivery or order counterpart
        in the simulation
      """
272
      #LOG('Delivery.isSimulated getMovementList',0,self.getMovementList())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
273
      for m in self.getMovementList():
274 275
        #LOG('Delivery.isSimulated m',0,m.getPhysicalPath())
        #LOG('Delivery.isSimulated m.isSimulated',0,m.isSimulated())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
276
        if not m.isSimulated():
277 278
          #LOG('Delivery.isSimulated m.getQuantity',0,m.getQuantity())
          #LOG('Delivery.isSimulated m.getSimulationQuantity',0,m.getSimulationQuantity())
279
          if m.getQuantity() != 0.0 or m.getSimulationQuantity() != 0:
280 281
            return 0
          # else Do we need to create a simulation movement ? XXX probably not
Jean-Paul Smets's avatar
Jean-Paul Smets committed
282 283
      return 1

284
    security.declareProtected(Permissions.View, 'isDivergent')
285
    def isDivergent(self,fast=0,**kw):
286 287 288 289
      """
        Returns 1 if the target is not met according to the current information
        After and edit, the isOutOfTarget will be checked. If it is 1,
        a message is emitted
Jean-Paul Smets's avatar
Jean-Paul Smets committed
290

291 292
        emit targetUnreachable !
      """
293 294 295 296
      # Delivery_zIsDivergent only works when object and simulation is
      # reindexed, so if an user change the delivery, he must wait
      # until everything is indexed, this is not acceptable for users
      # so we should not use it by default (and may be we should remove)
297
      if fast==1 and len(self.Delivery_zIsDivergent(uid=self.getUid())) > 0:
298 299
        return 1
      # Check if the total quantity equals the total of each simulation movement quantity
300 301 302
      for movement in self.getMovementList():
        if movement.isDivergent():
          return 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
303 304
      return 0

305 306 307 308 309 310
    def updateCausalityState(self,**kw):
      """
      This is often called as an activity, it will check if the
      deliver is convergent, and if so it will put the delivery
      in a solved state, if not convergent in a diverged state
      """
311
      if hasattr(self,'diverge') and hasattr(self,'converge'):
312
        if self.isDivergent(**kw):
313 314 315
          self.diverge()
        else:
          self.converge()
316

Jean-Paul Smets's avatar
Jean-Paul Smets committed
317 318 319 320 321 322
    #######################################################
    # Defer indexing process
    def reindexObject(self, *k, **kw):
      """
        Reindex children and simulation
      """
323
      self.recursiveReindexObject(*k, **kw)
324 325 326
      # NEW: we never rexpand simulation - This is a task for DSolver / TSolver
      # Make sure expanded simulation is still OK (expand and reindex)
      # self.activate().applyToDeliveryRelatedMovement(method_id = 'expand')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
327 328 329 330 331

    #######################################################
    # Stock Management
    def _getMovementResourceList(self):
      resource_dict = {}
Romain Courteaud's avatar
Romain Courteaud committed
332 333
      for m in self.contentValues(filter={
                      'portal_type': self.getPortalMovementTypeList()}):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
334 335 336 337 338
        r = m.getResource()
        if r is not None:
          resource_dict[r] = 1
      return resource_dict.keys()

Romain Courteaud's avatar
Romain Courteaud committed
339 340
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventory')
341 342 343 344 345 346
    def getInventory(self, **kw):
      """
      Returns inventory
      """
      kw['resource'] = self._getMovementResourceList()
      return self.portal_simulation.getInventory(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
347

Romain Courteaud's avatar
Romain Courteaud committed
348 349
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventory')
350
    def getCurrentInventory(self, **kw):
351 352 353
      """
      Returns current inventory
      """
Romain Courteaud's avatar
Romain Courteaud committed
354
      kw['resource'] = self._getMovementResourceList()
355
      return self.portal_simulation.getCurrentInventory(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
356

Romain Courteaud's avatar
Romain Courteaud committed
357 358
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getAvailableInventory')
359
    def getAvailableInventory(self, **kw):
360 361 362 363
      """
      Returns available inventory
      (current inventory - deliverable)
      """
Romain Courteaud's avatar
Romain Courteaud committed
364
      kw['resource'] = self._getMovementResourceList()
365 366
      return self.portal_simulation.getAvailableInventory(**kw)

Romain Courteaud's avatar
Romain Courteaud committed
367 368
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventory')
369
    def getFutureInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
370
      """
371
      Returns inventory at infinite
Jean-Paul Smets's avatar
Jean-Paul Smets committed
372
      """
Romain Courteaud's avatar
Romain Courteaud committed
373
      kw['resource'] = self._getMovementResourceList()
374
      return self.portal_simulation.getFutureInventory(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
375

Romain Courteaud's avatar
Romain Courteaud committed
376 377
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryList')
378
    def getInventoryList(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
379
      """
380
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
381
      """
Romain Courteaud's avatar
Romain Courteaud committed
382
      kw['resource'] = self._getMovementResourceList()
383
      return self.portal_simulation.getInventoryList(**kw)
384

Romain Courteaud's avatar
Romain Courteaud committed
385 386
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryList')
387
    def getCurrentInventoryList(self, **kw):
388 389 390
      """
      Returns list of inventory grouped by section or site
      """
Romain Courteaud's avatar
Romain Courteaud committed
391
      kw['resource'] = self._getMovementResourceList()
392
      return self.portal_simulation.getCurrentInventoryList(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
393

Romain Courteaud's avatar
Romain Courteaud committed
394 395
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryList')
396
    def getFutureInventoryList(self, **kw):
397 398 399
      """
      Returns list of inventory grouped by section or site
      """
Romain Courteaud's avatar
Romain Courteaud committed
400
      kw['resource'] = self._getMovementResourceList()
401
      return self.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
      """
Romain Courteaud's avatar
Romain Courteaud committed
409
      kw['resource'] = self._getMovementResourceList()
410
      return self.portal_simulation.getInventoryStat(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
411

Romain Courteaud's avatar
Romain Courteaud committed
412 413
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryStat')
414
    def getCurrentInventoryStat(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
415
      """
416
      Returns statistics of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
417
      """
Romain Courteaud's avatar
Romain Courteaud committed
418
      kw['resource'] = self._getMovementResourceList()
419
      return self.portal_simulation.getCurrentInventoryStat(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
420

Romain Courteaud's avatar
Romain Courteaud committed
421 422
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventoryStat')
423
    def getFutureInventoryStat(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
424
      """
425
      Returns statistics of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
426
      """
Romain Courteaud's avatar
Romain Courteaud committed
427
      kw['resource'] = self._getMovementResourceList()
428
      return self.portal_simulation.getFutureInventoryStat(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
429

Romain Courteaud's avatar
Romain Courteaud committed
430 431
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryChart')
432 433 434 435
    def getInventoryChart(self, **kw):
      """
      Returns list of inventory grouped by section or site
      """
Romain Courteaud's avatar
Romain Courteaud committed
436
      kw['resource'] = self._getMovementResourceList()
437 438
      return self.portal_simulation.getInventoryChart(**kw)

Romain Courteaud's avatar
Romain Courteaud committed
439 440
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryChart')
441
    def getCurrentInventoryChart(self, **kw):
442 443 444
      """
      Returns list of inventory grouped by section or site
      """
Romain Courteaud's avatar
Romain Courteaud committed
445
      kw['resource'] = self._getMovementResourceList()
446
      return self.portal_simulation.getCurrentInventoryChart(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
447

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

Romain Courteaud's avatar
Romain Courteaud committed
457 458
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryHistoryList')
459
    def getInventoryHistoryList(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
460
      """
461
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
462
      """
Romain Courteaud's avatar
Romain Courteaud committed
463
      kw['resource'] = self._getMovementResourceList()
464
      return self.portal_simulation.getInventoryHistoryList(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
465

Romain Courteaud's avatar
Romain Courteaud committed
466 467
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryHistoryChart')
468
    def getInventoryHistoryChart(self, **kw):
469 470 471
      """
      Returns list of inventory grouped by section or site
      """
Romain Courteaud's avatar
Romain Courteaud committed
472
      kw['resource'] = self._getMovementResourceList()
473
      return self.portal_simulation.getInventoryHistoryChart(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
474

Romain Courteaud's avatar
Romain Courteaud committed
475 476
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getMovementHistoryList')
477
    def getMovementHistoryList(self, **kw):
478 479 480
      """
      Returns list of inventory grouped by section or site
      """
Romain Courteaud's avatar
Romain Courteaud committed
481
      kw['resource'] = self._getMovementResourceList()
482
      return self.portal_simulation.getMovementHistoryList(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
483

Romain Courteaud's avatar
Romain Courteaud committed
484 485
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getMovementHistoryStat')
486
    def getMovementHistoryStat(self, **kw):
487 488 489
      """
      Returns list of inventory grouped by section or site
      """
Romain Courteaud's avatar
Romain Courteaud committed
490
      kw['resource'] = self._getMovementResourceList()
491
      return self.portal_simulation.getMovementHistoryStat(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
492

Romain Courteaud's avatar
Romain Courteaud committed
493 494 495 496




497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
# JPS: We must still decide if getInventoryAssetPrice is part of the Delivery API

#     security.declareProtected(Permissions.AccessContentsInformation, 'getInventoryAssetPrice')
#     def getInventoryAssetPrice(self, **kw):
#       """
#         Returns asset at infinite
#       """
#       kw['category'] = self._getMovementResourceList()
#       return self.portal_simulation.getInventoryAssetPrice(**kw)
# 
#     security.declareProtected(Permissions.AccessContentsInformation, 'getFutureInventoryAssetPrice')
#     def getFutureInventoryAssetPrice(self, **kw):
#       """
#         Returns asset at infinite
#       """
#       kw['category'] = self._getMovementResourceList()
#       return self.portal_simulation.getFutureInventoryAssetPrice(**kw)
# 
#     security.declareProtected(Permissions.AccessContentsInformation, 'getCurrentInventoryAssetPrice')
#     def getCurrentInventoryAssetPrice(self, **kw):
#       """
#         Returns asset at infinite
#       """
#       kw['category'] = self._getMovementResourceList()
#       return self.portal_simulation.getCurrentInventoryAssetPrice(**kw)
# 
#     security.declareProtected(Permissions.AccessContentsInformation, 'getAvailableInventoryAssetPrice')
#     def getAvailableInventoryAssetPrice(self, **kw):
#       """
#         Returns asset at infinite
#       """
#       kw['category'] = self._getMovementResourceList()
#       return self.portal_simulation.getAvailableInventoryAssetPrice(**kw)

531 532 533 534 535 536 537 538 539 540 541
    security.declarePrivate( '_edit' )
    def _edit(self, REQUEST=None, force_update = 0, **kw):
      """
      call propagateArrowToSimulation
      """
      XMLObject._edit(self,REQUEST=REQUEST,force_update=force_update,**kw)
      #self.propagateArrowToSimulation()
      # We must expand our applied rule only if not confirmed
      #if self.getSimulationState() in planned_order_state:
      #  self.updateAppliedRule() # This should be implemented with the interaction tool rather than with this hard coding

542 543 544 545 546 547 548 549 550
    security.declareProtected(Permissions.ModifyPortalContent, 'notifySimulationChange')
    def notifySimulationChange(self):
      """
        WorkflowMethod used to notify the causality workflow that the simulation
        has changed, so we have to check if the delivery is divergent or not
      """
      pass
    notifySimulationChange = WorkflowMethod(notifySimulationChange)

551 552
    ##########################################################################
    # Applied Rule stuff
553
    def updateAppliedRule(self, rule_id,force=0,**kw):
554 555 556 557 558 559 560 561
      """
        Create a new Applied Rule is none is related, or call expand
        on the existing one.
      """
      if (rule_id is not None) and\
         (self.getSimulationState() not in \
                                       self.getPortalDraftOrderStateList()):
        # Nothing to do if we are already simulated
562
        self._createAppliedRule(rule_id,force=force,**kw)
563

564
    def _createAppliedRule(self, rule_id,force=0,activate_kw=None,**kw):
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
      """
        Create a new Applied Rule is none is related, or call expand
        on the existing one.
      """
      # Return if draft or cancelled simulation_state
      if self.getSimulationState() in ('cancelled',):
        # The applied rule should be cleaned up 
        # ie. empty all movements which have no confirmed children
        return
      # Otherwise, expand
      # Look up if existing applied rule
      my_applied_rule_list = self.getCausalityRelatedValueList(\
                                            portal_type='Applied Rule')
      if len(my_applied_rule_list) == 0:
        if self.isSimulated(): 
          # No need to create a DeliveryRule 
          # if we are already in the simulation process
          return 
        # Create a new applied order rule (portal_rules.order_rule)
        portal_rules = getToolByName(self, 'portal_rules')
        portal_simulation = getToolByName(self, 'portal_simulation')
        my_applied_rule = portal_rules[rule_id].\
                                    constructNewAppliedRule(portal_simulation)
        # Set causality
        my_applied_rule.setCausalityValue(self)
        # We must make sure this rule is indexed
        # now in order not to create another one later
592
        my_applied_rule.reindexObject(**kw)
593 594 595 596
      elif len(my_applied_rule_list) == 1:
        # Re expand the rule if possible
        my_applied_rule = my_applied_rule_list[0]
      else:
Jérome Perrin's avatar
Jérome Perrin committed
597 598
        raise "SimulationError", 'Delivery %s has more than one applied'\
                                 ' rule.' % self.getRelativeUrl()
599 600 601

      # We are now certain we have a single applied rule
      # It is time to expand it
602
      self.activate(
603 604
        after_path_and_method_id=(
                my_applied_rule.getPath(),
605
               ['immediateReindexObject', 'recursiveImmediateReindexObject']),
606 607
               activate_kw=activate_kw
        ).expand(my_applied_rule.getId(),force=force,activate_kw=activate_kw,**kw)
608 609

    security.declareProtected(Permissions.ModifyPortalContent, 'expand')
610
    def expand(self, applied_rule_id, force=0, activate_kw=None,**kw):
611 612 613 614 615
      """
        Reexpand applied rule
      """
      my_applied_rule = self.portal_simulation.get(applied_rule_id, None)
      if my_applied_rule is not None:
616
        my_applied_rule.expand(force=force, activate_kw=activate_kw,**kw)
617 618 619
        # once expanded, the applied_rule must be reindexed
        # because some simulation_movement may change even
        # if there are not edited (acquisition)
620
        my_applied_rule.recursiveReindexObject(activate_kw=activate_kw)
621
      else:
Jérome Perrin's avatar
Jérome Perrin committed
622
        LOG("ERP5 Error:", 100,
623 624
            "Could not expand applied rule %s for delivery %s" %\
                (applied_rule_id, self.getId()))
625