Delivery.py 29.8 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
#
# 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 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
35
from Products.ERP5.Document.Movement import Movement
36
from Products.ERP5.Document.ImmobilisationDelivery import ImmobilisationDelivery
Jean-Paul Smets's avatar
Jean-Paul Smets committed
37 38

from zLOG import LOG
39
from zLOG import PROBLEM
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40

41
class Delivery(XMLObject, ImmobilisationDelivery):
42 43 44 45
    """
        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
46 47 48 49 50
    # CMF Type Definition
    meta_type = 'ERP5 Delivery'
    portal_type = 'Delivery'
    isPortalContent = 1
    isRADContent = 1
51
    isDelivery = 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
52 53 54

    # Declarative security
    security = ClassSecurityInfo()
55
    security.declareObjectProtected(Permissions.AccessContentsInformation)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
56 57 58 59 60 61 62 63 64 65 66

    # Default Properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
                      , PropertySheet.CategoryCore
                      , PropertySheet.DublinCore
                      , PropertySheet.Task
                      , PropertySheet.Arrow
                      , PropertySheet.Movement
                      , PropertySheet.Delivery
                      , PropertySheet.Reference
67
                      , PropertySheet.Price
Jean-Paul Smets's avatar
Jean-Paul Smets committed
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
                      )

    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
92
    security.declareProtected(Permissions.AccessContentsInformation, 'getDefaultTotalPrice')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
    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
111 112 113 114 115 116
    security.declareProtected( Permissions.ModifyPortalContent, 'updatePrice' )
    def updatePrice(self):
      for c in self.objectValues():
        if hasattr(aq_base(c), 'updatePrice'):
          c.updatePrice()

117 118 119 120 121 122 123
    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.
Sebastien Robin's avatar
Sebastien Robin committed
124 125 126

        So if the order is not in the catalog, getTotalPrice(fast=1)
        will return 0, this is not a bug.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
127
      """
128 129 130 131 132
      if not fast :
        kw.setdefault( 'portal_type',
                       self.getPortalDeliveryMovementTypeList())
        return sum([ line.getTotalPrice(fast=0) for line in
                        self.objectValues(**kw) ])
133
      kw['explanation_uid'] = self.getUid()
134 135
      kw.update(self.portal_catalog.buildSQLQuery(**kw))
      if src__:
136 137
        return self.Delivery_zGetTotal(src__=1, **kw)
      aggregate = self.Delivery_zGetTotal(**kw)[0]
138
      return aggregate.total_price or 0
Jean-Paul Smets's avatar
Jean-Paul Smets committed
139

Romain Courteaud's avatar
Romain Courteaud committed
140 141
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getTotalQuantity')
142 143 144 145 146
    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.
Sebastien Robin's avatar
Sebastien Robin committed
147 148 149

        So if the order is not in the catalog, getTotalQuantity(fast=1)
        will return 0, this is not a bug.
150
      """
151
      if not fast :
Romain Courteaud's avatar
Romain Courteaud committed
152 153
        kw.setdefault('portal_type',
                      self.getPortalDeliveryMovementTypeList())
154 155
        return sum([ line.getTotalQuantity(fast=0) for line in
                        self.objectValues(**kw) ])
156
      kw['explanation_uid'] = self.getUid()
157 158
      kw.update(self.portal_catalog.buildSQLQuery(**kw))
      if src__:
159 160
        return self.Delivery_zGetTotal(src__=1, **kw)
      aggregate = self.Delivery_zGetTotal(**kw)[0]
161
      return aggregate.total_quantity or 0
Jean-Paul Smets's avatar
Jean-Paul Smets committed
162

Jérome Perrin's avatar
Jérome Perrin committed
163 164
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getDeliveryUid')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
165 166 167
    def getDeliveryUid(self):
      return self.getUid()

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

Jérome Perrin's avatar
Jérome Perrin committed
176 177
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getRootDeliveryValue')
178 179 180 181 182
    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
183 184
      return self

Jérome Perrin's avatar
Jérome Perrin committed
185 186
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getDelivery')
187 188 189
    def getDelivery(self):
      return self.getRelativeUrl()

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

Jérome Perrin's avatar
Jérome Perrin committed
208 209
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getSimulatedMovementList')
210 211 212 213 214
    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
215 216
      return self.getMovementList(portal_type=
                          self.getPortalSimulatedMovementTypeList())
217

Jérome Perrin's avatar
Jérome Perrin committed
218 219
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getInvoiceMovementList')
220 221 222 223 224
    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
225 226
      return self.getMovementList(portal_type=
                            self.getPortalInvoiceMovementTypeList())
227

Jérome Perrin's avatar
Jérome Perrin committed
228 229
    security.declareProtected(Permissions.AccessContentsInformation,
                              'getContainerList')
230 231 232 233 234 235
    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
236 237
      for m in self.contentValues(filter={'portal_type':
                                  self.getPortalContainerTypeList()}):
238 239 240
        container_list.append(m)
      return container_list

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


    #######################################################
    # Causality computation
    security.declareProtected(Permissions.View, 'isConvergent')
265
    def isConvergent(self,**kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
266 267 268
      """
        Returns 0 if the target is not met
      """
269
      return int(not self.isDivergent(**kw))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
270

Jean-Paul Smets's avatar
Jean-Paul Smets committed
271 272
    security.declareProtected(Permissions.View, 'isSimulated')
    def isSimulated(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
273 274 275 276
      """
        Returns 1 if all movements have a delivery or order counterpart
        in the simulation
      """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
277
      for m in self.getMovementList():
278 279
        #LOG('Delivery.isSimulated m',0,m.getPhysicalPath())
        #LOG('Delivery.isSimulated m.isSimulated',0,m.isSimulated())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
280
        if not m.isSimulated():
281 282
          #LOG('Delivery.isSimulated m.getQuantity',0,m.getQuantity())
          #LOG('Delivery.isSimulated m.getSimulationQuantity',0,m.getSimulationQuantity())
283
          if m.getQuantity() != 0.0 or m.getSimulationQuantity() != 0:
284 285
            return 0
          # else Do we need to create a simulation movement ? XXX probably not
Jean-Paul Smets's avatar
Jean-Paul Smets committed
286
      return 1
287
    
288
    security.declareProtected(Permissions.View, 'isDivergent')
289
    def isDivergent(self, fast=0, **kw):
290 291 292 293
      """
        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
294

295 296
        emit targetUnreachable !
      """
297 298 299 300
      # 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)
301
      if fast==1 and len(self.Delivery_zIsDivergent(uid=self.getUid())) > 0:
302 303
        return 1
      # Check if the total quantity equals the total of each simulation movement quantity
304 305 306
      for movement in self.getMovementList():
        if movement.isDivergent():
          return 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
307 308
      return 0

309
    security.declareProtected(Permissions.View, 'getDivergenceList')
310
    def getDivergenceList(self, **kw):
311 312 313 314 315 316 317 318
      """
      Return a list of messages that contains the divergences
      """
      divergence_list = []
      for movement in self.getMovementList():
         divergence_list.extend(movement.getDivergenceList())
      return divergence_list

319 320 321 322 323 324
    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
      """
325 326
      if getattr(self, 'diverge', None) is not None \
            and getattr(self, 'converge', None) is not None:
327
        if self.isDivergent(**kw):
328 329 330
          self.diverge()
        else:
          self.converge()
331

Jean-Paul Smets's avatar
Jean-Paul Smets committed
332 333 334 335 336 337
    #######################################################
    # Defer indexing process
    def reindexObject(self, *k, **kw):
      """
        Reindex children and simulation
      """
338
      self.recursiveReindexObject(*k, **kw)
339 340 341
      # 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
342 343 344 345 346

    #######################################################
    # Stock Management
    def _getMovementResourceList(self):
      resource_dict = {}
Romain Courteaud's avatar
Romain Courteaud committed
347 348
      for m in self.contentValues(filter={
                      'portal_type': self.getPortalMovementTypeList()}):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
349 350 351 352 353
        r = m.getResource()
        if r is not None:
          resource_dict[r] = 1
      return resource_dict.keys()

Romain Courteaud's avatar
Romain Courteaud committed
354 355
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventory')
356 357 358 359 360 361
    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
362

Romain Courteaud's avatar
Romain Courteaud committed
363 364
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventory')
365
    def getCurrentInventory(self, **kw):
366 367 368
      """
      Returns current inventory
      """
Romain Courteaud's avatar
Romain Courteaud committed
369
      kw['resource'] = self._getMovementResourceList()
370
      return self.portal_simulation.getCurrentInventory(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
371

Romain Courteaud's avatar
Romain Courteaud committed
372 373
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getAvailableInventory')
374
    def getAvailableInventory(self, **kw):
375 376 377 378
      """
      Returns available inventory
      (current inventory - deliverable)
      """
Romain Courteaud's avatar
Romain Courteaud committed
379
      kw['resource'] = self._getMovementResourceList()
380 381
      return self.portal_simulation.getAvailableInventory(**kw)

Romain Courteaud's avatar
Romain Courteaud committed
382 383
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getFutureInventory')
384
    def getFutureInventory(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
385
      """
386
      Returns inventory at infinite
Jean-Paul Smets's avatar
Jean-Paul Smets committed
387
      """
Romain Courteaud's avatar
Romain Courteaud committed
388
      kw['resource'] = self._getMovementResourceList()
389
      return self.portal_simulation.getFutureInventory(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
390

Romain Courteaud's avatar
Romain Courteaud committed
391 392
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryList')
393
    def getInventoryList(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
      """
Romain Courteaud's avatar
Romain Courteaud committed
397
      kw['resource'] = self._getMovementResourceList()
398
      return self.portal_simulation.getInventoryList(**kw)
399

Romain Courteaud's avatar
Romain Courteaud committed
400 401
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryList')
402
    def getCurrentInventoryList(self, **kw):
403 404 405
      """
      Returns list of inventory grouped by section or site
      """
Romain Courteaud's avatar
Romain Courteaud committed
406
      kw['resource'] = self._getMovementResourceList()
407
      return self.portal_simulation.getCurrentInventoryList(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
408

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

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

Romain Courteaud's avatar
Romain Courteaud committed
427 428
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryStat')
429
    def getCurrentInventoryStat(self, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
430
      """
431
      Returns statistics of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
432
      """
Romain Courteaud's avatar
Romain Courteaud committed
433
      kw['resource'] = self._getMovementResourceList()
434
      return self.portal_simulation.getCurrentInventoryStat(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
435

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

Romain Courteaud's avatar
Romain Courteaud committed
445 446
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getInventoryChart')
447 448 449 450
    def getInventoryChart(self, **kw):
      """
      Returns list of inventory grouped by section or site
      """
Romain Courteaud's avatar
Romain Courteaud committed
451
      kw['resource'] = self._getMovementResourceList()
452 453
      return self.portal_simulation.getInventoryChart(**kw)

Romain Courteaud's avatar
Romain Courteaud committed
454 455
    security.declareProtected(Permissions.AccessContentsInformation, 
                              'getCurrentInventoryChart')
456
    def getCurrentInventoryChart(self, **kw):
457 458 459
      """
      Returns list of inventory grouped by section or site
      """
Romain Courteaud's avatar
Romain Courteaud committed
460
      kw['resource'] = self._getMovementResourceList()
461
      return self.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):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
466
      """
467
      Returns list of inventory grouped by section or site
Jean-Paul Smets's avatar
Jean-Paul Smets committed
468
      """
Romain Courteaud's avatar
Romain Courteaud committed
469
      kw['resource'] = self._getMovementResourceList()
470
      return self.portal_simulation.getFutureInventoryChart(**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
471

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

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

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

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

Romain Courteaud's avatar
Romain Courteaud committed
508 509 510 511




512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
# 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)

546 547 548 549 550 551 552 553 554 555 556
    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

557 558 559 560 561 562 563 564 565
    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)

566 567
    ##########################################################################
    # Applied Rule stuff
568
    def updateAppliedRule(self, rule_id,force=0,**kw):
569
      """
570 571
      Create a new Applied Rule is none is related, or call expand
      on the existing one.
572 573 574 575 576
      """
      if (rule_id is not None) and\
         (self.getSimulationState() not in \
                                       self.getPortalDraftOrderStateList()):
        # Nothing to do if we are already simulated
577
        self._createAppliedRule(rule_id,force=force,**kw)
578

579 580


581
    def _createAppliedRule(self, rule_id,force=0,activate_kw=None,**kw):
582 583 584 585 586 587 588 589 590 591 592 593 594
      """
        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')
595
      my_applied_rule = None
596 597 598 599
      if len(my_applied_rule_list) == 0:
        if self.isSimulated(): 
          # No need to create a DeliveryRule 
          # if we are already in the simulation process
600 601 602 603 604 605 606 607 608 609 610 611
          pass
        else:
          # 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
          my_applied_rule.reindexObject(activate_kw=activate_kw,**kw)
612 613 614 615
      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
616 617
        raise "SimulationError", 'Delivery %s has more than one applied'\
                                 ' rule.' % self.getRelativeUrl()
618

619 620 621 622 623 624 625
      my_applied_rule_id = None
      expand_activate_kw = {}
      if my_applied_rule is not None:
        my_applied_rule_id = my_applied_rule.getId()
        expand_activate_kw['after_path_and_method_id'] = \
                (my_applied_rule.getPath(),\
               ['immediateReindexObject', 'recursiveImmediateReindexObject'])
626 627
      # We are now certain we have a single applied rule
      # It is time to expand it
628 629 630 631
      self.activate( activate_kw=activate_kw,
                     **expand_activate_kw
        ).expand(applied_rule_id=my_applied_rule_id,force=force,
                 activate_kw=activate_kw,**kw)
632 633

    security.declareProtected(Permissions.ModifyPortalContent, 'expand')
634
    def expand(self, applied_rule_id=None, force=0, activate_kw=None,**kw):
635 636
      """
        Reexpand applied rule
637 638 639 640 641 642 643 644 645 646 647 648 649 650
        
        Also reexpand all rules related to movements
      """
      excluded_rule_path_list = []
      if applied_rule_id is not None:
        my_applied_rule = self.portal_simulation.get(applied_rule_id, None)
        if my_applied_rule is not None:
          excluded_rule_path_list.append(my_applied_rule.getPath())
          my_applied_rule.expand(force=force, activate_kw=activate_kw,**kw)
          # once expanded, the applied_rule must be reindexed
          # because some simulation_movement may change even
          # if there are not edited (acquisition)
          my_applied_rule.recursiveReindexObject(activate_kw=activate_kw)
        else:
651
          LOG("ERP5", PROBLEM,
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
              "Could not expand applied rule %s for delivery %s" %\
                  (applied_rule_id, self.getId()))
      self.expandRuleRelatedToMovement(
                  excluded_rule_path_list=excluded_rule_path_list,
                  force=force,
                  activate_kw=activate_kw,
                  **kw)

    security.declareProtected(Permissions.ModifyPortalContent,
        'expandRuleRelatedToMovement')
    def expandRuleRelatedToMovement(self,excluded_rule_path_list=None,
                                    activate_kw=None,**kw):
      """
      Some delivery movement may be related to another applied rule than
      the one related to the delivery. Delivery movements may be related
      to many simulation movements from many different root applied rules,
      so it is required to expand the applied rule parent to related
      simulation movements.

      exclude_rule_path : do not expand this applied rule (or children
                          applied rule)
      """
      if excluded_rule_path_list is None:
        excluded_rule_path_list = []
      to_expand_list = []
      # we might use a zsql method, because it can be very slow
      for m in self.getMovementList():
        if m.isSimulated():
          sim_movement_list = m.getDeliveryRelatedValueList()
          for sim_movement in sim_movement_list:
            if sim_movement.getRootAppliedRule().getPath() \
                not in excluded_rule_path_list:
              parent_value = sim_movement.getParentValue()
              if parent_value not in to_expand_list:
                to_expand_list.append(parent_value)
      for rule in to_expand_list:
        rule.expand(activate_kw=activate_kw,**kw)
        rule.recursiveReindexObject(activate_kw=activate_kw)
690 691

    security.declareProtected( Permissions.AccessContentsInformation,
692
                               'getRootCausalityValueList')
693 694 695 696 697 698
    def getRootCausalityValueList(self):
      """
        Returns the initial causality value for this movement.
        This method will look at the causality and check if the
        causality has already a causality
      """
699 700
      causality_value_list = [x for x in self.getCausalityValueList()
                                if x is not self]
701 702 703 704 705
      initial_list = []
      if len(causality_value_list)==0:
        initial_list = [self]
      else:
        for causality in causality_value_list:
706 707 708 709 710 711
          # The causality may be something which has not this method
          # (e.g. item)
          if hasattr(causality, 'getRootCausalityValueList'):
            tmp_causality_list = causality.getRootCausalityValueList()
            initial_list.extend([x for x in tmp_causality_list 
                                 if x not in initial_list])
712 713 714 715 716 717 718
      return initial_list


    # XXX Temp hack, should be removed has soon as the structure of
    # the order/delivery builder will be reviewed. It might
    # be reviewed if we plan to configure movement groups in the zmi
    security.declareProtected( Permissions.ModifyPortalContent,
719
                               'setRootCausalityValueList')
720
    def setRootCausalityValueList(self,value):
721
      """
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
      This is a hack
      """
      pass

    security.declareProtected( Permissions.AccessContentsInformation,
                               'getParentExplanationValue')
    def getParentExplanationValue(self):
      """
        This method should be removed as soon as movement groups
        will be rewritten. It is a temp hack
      """
      return self

    # XXX Temp hack, should be removed has soon as the structure of
    # the order/delivery builder will be reviewed. It might
    # be reviewed if we plan to configure movement groups in the zmi
    security.declareProtected( Permissions.ModifyPortalContent,
                               'setParentExplanationValue')
    def setParentExplanationValue(self,value):
      """
      This is a hack
743 744 745
      """
      pass