MovementGroup.py 17.8 KB
Newer Older
Sebastien Robin's avatar
Sebastien Robin committed
1 2
##############################################################################
#
3
# Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
Sebastien Robin's avatar
Sebastien Robin committed
4
#                    Sebastien Robin <seb@nexedi.com>
Sebastien Robin's avatar
Sebastien Robin committed
5
#                    Yoshinori Okuji <yo@nexedi.com>
6
#                    Romain Courteaud <romain@nexedi.com>
Sebastien Robin's avatar
Sebastien Robin committed
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.
#
##############################################################################

"""
Define in this class all classes intended to group every kind of movement
"""

from AccessControl import ClassSecurityInfo
Sebastien Robin's avatar
Sebastien Robin committed
36 37
from Globals import InitializeClass, DTMLFile
from zLOG import LOG
38
from Products.PythonScripts.Utility import allow_class
Sebastien Robin's avatar
Sebastien Robin committed
39

40
class RootMovementGroup:
Sebastien Robin's avatar
Sebastien Robin committed
41

42 43 44 45 46 47 48 49 50 51 52 53 54 55
  def __init__(self, class_list, movement=None, last_line_class_name=None,
               separate_method_name_list=[]):
    self._nested_class = None
    self.setNestedClass(class_list=class_list)
    self._movement_list = []
    self._group_list = []

    self._class_list = class_list
    self._last_line_class_name = last_line_class_name
    self._separate_method_name_list = separate_method_name_list

    if movement is not None :
      self.append(movement)

Sebastien Robin's avatar
Sebastien Robin committed
56 57 58
  def getNestedClass(self, class_list):
    if len(class_list)>0:
      return class_list[0]
Sebastien Robin's avatar
Sebastien Robin committed
59 60
    return None

61
  def setNestedClass(self,class_list):
Sebastien Robin's avatar
Sebastien Robin committed
62 63 64
    """
      This sets an appropriate nested class.
    """
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
    self._nested_class = self.getNestedClass(class_list)

  def _appendGroup(self, movement):
    nested_instance = self._nested_class(
                    movement=movement,
                    class_list=self._class_list[1:],
                    last_line_class_name=self._last_line_class_name,
                    separate_method_name_list=self._separate_method_name_list)
    self._group_list.append(nested_instance)

  def append(self, movement):
    is_movement_in_group = 0
    for group in self.getGroupList():
      if group.test(movement) :
        group.append(movement)
        is_movement_in_group = 1
Sebastien Robin's avatar
Sebastien Robin committed
81
        break
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
    if is_movement_in_group == 0 :
      if self._nested_class is not None:
        self._appendGroup(movement)
      else:
        # We are on a node group
        movement_list = self.getMovementList()
        if len(movement_list) > 0:
          # We have a conflict here, because it is forbidden to have 
          # 2 movements on the same node group
          tmp_result = self._separate(movement)
          self._movement_list, split_movement_list = tmp_result
          # XXX Do something with split_movement_list !
        else:
          # No movement on this node, we can add it
          self._movement_list.append(movement)
Sebastien Robin's avatar
Sebastien Robin committed
97

98
  def getGroupList(self):
99
    return self._group_list
Sebastien Robin's avatar
Sebastien Robin committed
100

101 102 103 104 105
  def setGroupEdit(self, **kw):
    """
      Store properties for the futur created object 
    """
    self._property_dict = kw
Sebastien Robin's avatar
Sebastien Robin committed
106

107 108 109 110 111 112 113 114 115 116 117 118 119
  def getGroupEditDict(self):
    """
      Get property dict for the futur created object 
    """
    if hasattr(self, '_property_dict'):
      return self._property_dict
    else:
      return {}

  def getMovementList(self):
    """
      Return movement list in the current group
    """
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
    movement_list = []
    group_list = self.getGroupList()
    if len(group_list) == 0:
      return self._movement_list
    else:
      for group in group_list:
        movement_list.extend(group.getMovementList())
      return movement_list

  def _separate(self, movement):
    """
      Separate 2 movements on a node group
    """
    movement_list = self.getMovementList()
    if len(movement_list) != 1:
      raise "ProgrammingError", "Can separate only 2 movements"
    else:
      old_movement = self.getMovementList()[0]

      new_stored_movement = old_movement
      added_movement = movement
      rejected_movement = None
Sebastien Robin's avatar
Sebastien Robin committed
142

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
      for separate_method_name in self._separate_method_name_list:
        method = getattr(self, separate_method_name)

        new_stored_movement,\
        rejected_movement= method(new_stored_movement,
                                       added_movement=added_movement)
        added_movement = None

      return [new_stored_movement], [rejected_movement]

  ########################################################
  # Separate methods
  ########################################################
  def _genericCalculation(self, movement, added_movement=None):
    """
      Generic creation of FakeMovement
    """
    if added_movement is not None:
      # Create a fake movement
      new_movement = FakeMovement([movement, added_movement])
    else:
      new_movement = movement
    return new_movement

  def calculateAveragePrice(self, movement, added_movement=None):
    """
      Create a new movement with a average price
    """
    new_movement = self._genericCalculation(movement, 
                                            added_movement=added_movement)
    new_movement.setPriceMethod("getAveragePrice")
    return new_movement, None

  def calculateAddQuantity(self, movement, added_movement=None):
    """
      Create a new movement with the sum of quantity
    """
    new_movement = self._genericCalculation(movement, 
                                            added_movement=added_movement)
    new_movement.setQuantityMethod("getAddQuantity")
    return new_movement, None
Sebastien Robin's avatar
Sebastien Robin committed
184

185 186 187
allow_class(RootMovementGroup)

class OrderMovementGroup(RootMovementGroup):
188
  def __init__(self,movement, **kw):
189
    #LOG('OrderMovementGroup.__init__, kw:',0,kw)
190
    RootMovementGroup.__init__(self, movement=movement, **kw)
Sebastien Robin's avatar
Sebastien Robin committed
191 192 193
    if hasattr(movement, 'getRootAppliedRule'):
      # This is a simulation movement
      order_value = movement.getRootAppliedRule().getCausalityValue(
194
                              portal_type=movement.getPortalOrderTypeList())
Sebastien Robin's avatar
Sebastien Robin committed
195 196 197 198
      if order_value is None:
        # In some cases (ex. DeliveryRule), there is no order
        # we may consider a PackingList as the order in the OrderGroup
        order_value = movement.getRootAppliedRule().getCausalityValue(
199
                        portal_type=movement.getPortalDeliveryTypeList())
Sebastien Robin's avatar
Sebastien Robin committed
200 201 202 203 204 205 206 207 208 209
    else:
      # This is a temp movement
      order_value = None
    if order_value is None:
      order_relative_url = None
    else:
      # get the id of the enclosing delivery
      # for this cell or line
      order_relative_url = order_value.getRelativeUrl()
    self.order = order_relative_url
210
    self.setGroupEdit(causality_value=order_value)
Sebastien Robin's avatar
Sebastien Robin committed
211 212 213 214

  def test(self,movement):
    if hasattr(movement, 'getRootAppliedRule'):
      order_value = movement.getRootAppliedRule().getCausalityValue(
215
                        portal_type=movement.getPortalOrderTypeList())
Sebastien Robin's avatar
Sebastien Robin committed
216 217 218 219 220

      if order_value is None:
        # In some cases (ex. DeliveryRule), there is no order
        # we may consider a PackingList as the order in the OrderGroup
        order_value = movement.getRootAppliedRule().getCausalityValue(
221
                        portal_type=movement.getPortalDeliveryTypeList())
Sebastien Robin's avatar
Sebastien Robin committed
222 223 224 225 226 227 228 229 230 231 232 233 234 235
    else:
      # This is a temp movement
      order_value = None
    if order_value is None:
      order_relative_url = None
    else:
      # get the id of the enclosing delivery
      # for this cell or line
      order_relative_url = order_value.getRelativeUrl()
    if order_relative_url == self.order:
      return 1
    else :
      return 0

236
allow_class(OrderMovementGroup)
Sebastien Robin's avatar
Sebastien Robin committed
237

238
class PathMovementGroup(RootMovementGroup):
Sebastien Robin's avatar
Sebastien Robin committed
239

Sebastien Robin's avatar
Sebastien Robin committed
240
  def __init__(self,movement,**kw):
241
    RootMovementGroup.__init__(self, movement=movement, **kw)
Sebastien Robin's avatar
Sebastien Robin committed
242
    self.source = movement.getSource()
243
    #LOG('PathGroup.__init__ source',0,self.source)
Sebastien Robin's avatar
Sebastien Robin committed
244
    self.destination = movement.getDestination()
245
    #LOG('PathGroup.__init__ destination',0,self.destination)
Sebastien Robin's avatar
Sebastien Robin committed
246
    self.source_section = movement.getSourceSection()
247
    #LOG('PathGroup.__init__ source_section',0,self.source_section)
Sebastien Robin's avatar
Sebastien Robin committed
248
    self.destination_section = movement.getDestinationSection()
249
    #LOG('PathGroup.__init__ destination_section',0,self.destination_section)
250 251 252 253 254 255
    self.setGroupEdit(
        source_value=movement.getSourceValue(),
        destination_value=movement.getDestinationValue(),
        source_section_value=movement.getSourceSectionValue(),
        destination_section_value=movement.getDestinationSectionValue(),
    )
Sebastien Robin's avatar
Sebastien Robin committed
256 257 258 259 260 261


  def test(self,movement):
    if movement.getSource() == self.source and \
      movement.getDestination() == self.destination and \
      movement.getSourceSection() == self.source_section and \
262
      movement.getDestinationSection() == self.destination_section  :
Sebastien Robin's avatar
Sebastien Robin committed
263 264 265 266 267

      return 1
    else :
      return 0

268
allow_class(PathMovementGroup)
Sebastien Robin's avatar
Sebastien Robin committed
269

270
class DateMovementGroup(RootMovementGroup):
Sebastien Robin's avatar
Sebastien Robin committed
271

Sebastien Robin's avatar
Sebastien Robin committed
272
  def __init__(self,movement,**kw):
273
    RootMovementGroup.__init__(self, movement=movement, **kw)
Sebastien Robin's avatar
Sebastien Robin committed
274 275
    self.start_date = movement.getStartDate()
    self.stop_date = movement.getStopDate()
276 277 278 279
    self.setGroupEdit(
        start_date=movement.getStartDate(),
        stop_date=movement.getStopDate()
    )
Sebastien Robin's avatar
Sebastien Robin committed
280 281 282 283 284 285 286 287

  def test(self,movement):
    if movement.getStartDate() == self.start_date and \
      movement.getStopDate() == self.stop_date :
      return 1
    else :
      return 0

288
allow_class(DateMovementGroup)
Sebastien Robin's avatar
Sebastien Robin committed
289

290
class CriterionMovementGroup(RootMovementGroup):
Sebastien Robin's avatar
Sebastien Robin committed
291

Sebastien Robin's avatar
Sebastien Robin committed
292
  def __init__(self,movement,**kw):
293
    RootMovementGroup.__init__(self, movement=movement, **kw)
Sebastien Robin's avatar
Sebastien Robin committed
294 295 296 297 298 299 300 301 302 303 304 305 306
    if hasattr(movement, 'getGroupCriterion'):
      self.criterion = movement.getGroupCriterion()
    else:
      self.criterion = None

  def test(self,movement):
    # we must have the same criterion
    if hasattr(movement, 'getGroupCriterion'):
      criterion = movement.getGroupCriterion()
    else:
      criterion = None
    return self.criterion == criterion

307
allow_class(CriterionMovementGroup)
Sebastien Robin's avatar
Sebastien Robin committed
308

309
class ResourceMovementGroup(RootMovementGroup):
Sebastien Robin's avatar
Sebastien Robin committed
310

Sebastien Robin's avatar
Sebastien Robin committed
311
  def __init__(self,movement,**kw):
312
    RootMovementGroup.__init__(self, movement=movement, **kw)
Sebastien Robin's avatar
Sebastien Robin committed
313
    self.resource = movement.getResource()
314 315 316
    self.setGroupEdit(
        resource_value=self.resource
    )
Sebastien Robin's avatar
Sebastien Robin committed
317 318 319 320 321 322 323

  def test(self,movement):
    if movement.getResource() == self.resource :
      return 1
    else :
      return 0

324
allow_class(ResourceMovementGroup)
Sebastien Robin's avatar
Sebastien Robin committed
325

326
class BaseVariantMovementGroup(RootMovementGroup):
Sebastien Robin's avatar
Sebastien Robin committed
327

Sebastien Robin's avatar
Sebastien Robin committed
328
  def __init__(self,movement,**kw):
329
    RootMovementGroup.__init__(self, movement=movement, **kw)
Sebastien Robin's avatar
Sebastien Robin committed
330 331
    self.base_category_list = movement.getVariationBaseCategoryList()
    if self.base_category_list is None:
332
      #LOG('BaseVariantGroup __init__', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict())))
Sebastien Robin's avatar
Sebastien Robin committed
333 334 335 336 337 338 339 340
      self.base_category_list = []

  def test(self,movement):
    # we must have the same number of categories
    categories_identity = 0
    #LOG('BaseVariantGroup', 0, 'self.base_category_list = %s, movement = %s, movement.getVariationBaseCategoryList() = %s' % (repr(self.base_category_list), repr(movement), repr(movement.getVariationBaseCategoryList())))
    movement_base_category_list = movement.getVariationBaseCategoryList()
    if movement_base_category_list is None:
341
      #LOG('BaseVariantGroup test', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict())))
Sebastien Robin's avatar
Sebastien Robin committed
342 343 344 345 346 347 348 349 350
      movement_base_category_list = []
    if len(self.base_category_list) == len(movement_base_category_list):
      for category in movement_base_category_list:
        if not category in self.base_category_list :
          break
      else :
        categories_identity = 1
    return categories_identity

351
allow_class(BaseVariantMovementGroup)
Sebastien Robin's avatar
Sebastien Robin committed
352

353
class VariantMovementGroup(RootMovementGroup):
Sebastien Robin's avatar
Sebastien Robin committed
354

Sebastien Robin's avatar
Sebastien Robin committed
355
  def __init__(self,movement,**kw):
356
    RootMovementGroup.__init__(self, movement=movement, **kw)
Sebastien Robin's avatar
Sebastien Robin committed
357 358
    self.category_list = movement.getVariationCategoryList()
    if self.category_list is None:
359
      #LOG('VariantGroup __init__', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict())))
Sebastien Robin's avatar
Sebastien Robin committed
360
      self.category_list = []
361 362 363
    self.setGroupEdit(
        variation_category_list=self.category_list
    )
Sebastien Robin's avatar
Sebastien Robin committed
364 365 366 367 368 369

  def test(self,movement):
    # we must have the same number of categories
    categories_identity = 0
    movement_category_list = movement.getVariationCategoryList()
    if movement_category_list is None:
370
      #LOG('VariantGroup test', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict())))
Sebastien Robin's avatar
Sebastien Robin committed
371 372 373 374 375 376 377 378
      movement_category_list = []
    if len(self.category_list) == len(movement_category_list):
      for category in movement_category_list:
        if not category in self.category_list :
          break
      else :
        categories_identity = 1
    return categories_identity
Sebastien Robin's avatar
Sebastien Robin committed
379

380
allow_class(VariantMovementGroup)
381

Jean-Paul Smets's avatar
Jean-Paul Smets committed
382
from copy import copy
383

Jean-Paul Smets's avatar
Jean-Paul Smets committed
384 385 386 387
class CategoryMovementGroup(RootMovementGroup):  
  """
    This seems to be a useless class
  """
388
  def __init__(self,movement,**kw):
389
    RootMovementGroup.__init__(self, movement=movement, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
390
    self.category_list = list(movement.getCategoryList())
391 392
    if self.category_list is None:
      self.category_list = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
393
    self.category_list.sort()
394 395 396

  def test(self,movement):
    # we must have the same number of categories
Jean-Paul Smets's avatar
Jean-Paul Smets committed
397
    movement_category_list = list(movement.getCategoryList())
398 399
    if movement_category_list is None:
      movement_category_list = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
400 401 402 403
    movement_category_list.sort()
    if self.category_list == movement_category_list:
      return 1
    return 0
404 405

allow_class(CategoryMovementGroup)
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 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 531 532 533 534 535 536 537 538 539

class FakeMovement:
  """
    A fake movement which simulate some methods on a movement needed 
    by DeliveryBuilder.
    It contents a list a real ERP5 Movement and can modify them.
  """
  def __init__(self, movement_list):
    """
      Create a fake movement and store the list of real movement
    """
    self.__price_method = None
    self.__quantity_method = None
    self.__movement_list = []
    for movement in movement_list:
      self.append(movement)
    # This object must not be use when there is not 2 or more movements
    if len(movement_list) < 2:
      raise "ProgrammingError", "FakeMovement used where it does not."
    # All movements must share the same getVariationCategoryList
    # So, verify and raise a error if not
    # But, if DeliveryBuilder is well configured, this can never append ;)
    reference_variation_category_list = movement_list[0].\
                                           getVariationCategoryList()
    error_raising_needed = 0
    for movement in movement_list[1:]:
      variation_category_list = movement.getVariationCategoryList()
      if len(variation_category_list) !=\
         len(reference_variation_category_list):
        error_raising_needed = 1
        break

      for variation_category in variation_category_list:
        if variation_category not in reference_variation_category_list:
          error_raising_needed = 1
          break
    
    if error_raising_needed == 1:
      raise "ProgrammingError", "FakeMovement not well used."

  def append(self, movement):
    """
      Append movement to the movement list
    """
    if movement.__class__.__name__ == "FakeMovement":
      self.__movement_list.extend(movement.getMovementList())
      self.__price_method = movement.__price_method
      self.__quantity_method = movement.__quantity_method
    else:
      self.__movement_list.append(movement)

  def getMovementList(self):
    """
      Return content movement list
    """
    return self.__movement_list
    
  def _setDeliveryValue(self, object):
    """
      Set Delivery value for each movement
      And calculate delivery_ratio
    """
    for movement in self.__movement_list:
      movement._setDeliveryValue(object)
      movement.setDeliveryRatio(movement.getQuantity() / object.getQuantity())
      
  def getPrice(self):
    """
      Return calculated price
    """
    return getattr(self, self.__price_method)()
  
  def setPriceMethod(self, method):
    """
      Set the price method
    """
    self.__price_method = method

  def getQuantity(self):
    """
      Return calculated quantity
    """
    return getattr(self, self.__quantity_method)()
 
  def setQuantityMethod(self, method):
    """
      Set the quantity method
    """
    self.__quantity_method = method

  def getAveragePrice(self):
    """
      Return average price 
    """
    return (self.getAddPrice() / self.getAddQuantity())

  def getAddQuantity(self):
    """
      Return the total quantity
    """
    total_quantity = 0
    for movement in self.getMovementList():
      total_quantity += movement.getQuantity()
    return total_quantity

  def getAddPrice(self):
    """
      Return total price 
    """
    total_price = 0
    for movement in self.getMovementList():
      total_price += (movement.getQuantity() * movement.getPrice())
    return total_price

  def recursiveReindexObject(self):
    """
      Reindex all movements
    """
    for movement in self.getMovementList():
      movement.recursiveReindexObject()

  def getVariationBaseCategoryList(self):
    """
      Return variation base category list
      Which must be shared by all movement
    """
    return self.__movement_list[0].getVariationBaseCategoryList()

  def getVariationCategoryList(self):
    """
      Return variation base category list
      Which must be shared by all movement
    """
    return self.__movement_list[0].getVariationCategoryList()