movement_collection_updater.py 8.18 KB
Newer Older
1 2 3 4 5 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
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################

import zope.interface
from AccessControl import ClassSecurityInfo
31
from AccessControl.class_init import InitializeClass
32
from Products.ERP5Type import Permissions, interfaces
33 34
from Products.ERP5.MovementCollectionDiff import (
  MovementCollectionDiff, _getPropertyAndCategoryList)
35 36 37
from Products.ERP5.mixin.rule import _compare

class MovementCollectionUpdaterMixin:
38
  """Movement Collection Updater.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

  Documents which implement IMovementCollectionUpdater
  usually invoke an IMovementGenerator to generate
  an IMovementList and compare it to another IMovementList
  obtained from an IMovementCollection, thus generating
  an IMovementCollectionDiff.
  """

  # Declarative security
  security = ClassSecurityInfo()
  security.declareObjectProtected(Permissions.AccessContentsInformation)

  # Declarative interfaces
  zope.interface.implements(interfaces.IMovementCollectionUpdater,)

  # Implementation of IMovementCollectionUpdater
  def getMovementCollectionDiff(self, context, rounding=False,
56
                                movement_generator=None):
57 58 59 60 61 62 63 64 65 66 67 68 69 70
    """
    Return a IMovementCollectionDiff by comparing movements
    the list of movements of context and the list of movements
    generated by movement_generator on context.

    context -- an IMovementCollection usually, possibly
               an IMovementList or an IMovement

    movement_generator -- an optional IMovementGenerator
                          (if not specified, a context implicit
                          IMovementGenerator will be used)
    """
    # We suppose here that we have an IMovementCollection in hand
    decision_movement_list = context.getMovementList()
71
    prevision_movement_list = movement_generator.getGeneratedMovementList(
72 73
      # XXX-JPS This mixin is not self-contained
      movement_list=self._getMovementGeneratorMovementList(context), rounding=rounding)
74 75 76

    # Get divergence testers
    tester_list = self._getMatchingTesterList()
Julien Muchembled's avatar
Julien Muchembled committed
77
    if not tester_list and len(prevision_movement_list) > 1:
78 79 80
      raise ValueError("It is not possible to match movements from movement"
          " collection updater %r, because it does not contain any tester"
          " configured as matching provider" % (self, ))
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101

    # Create small groups of movements per hash keys
    decision_movement_dict = {}
    for movement in decision_movement_list:
      tester_key = []
      for tester in tester_list:
        if tester.test(movement):
          tester_key.append(tester.generateHashKey(movement))
        else:
          tester_key.append(None)
      tester_key = tuple(tester_key)
      decision_movement_dict.setdefault(tester_key, []).append(movement)
    prevision_movement_dict = {}
    for movement in prevision_movement_list:
      tester_key = []
      for tester in tester_list:
        if tester.test(movement):
          tester_key.append(tester.generateHashKey(movement))
        else:
          tester_key.append(None)
      tester_key = tuple(tester_key)
102
      prevision_movement_dict.setdefault(tester_key, []).append(movement)
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136

    # Prepare a mapping between prevision and decision
    # The prevision_to_decision_map is a list of tuples
    # of the form (prevision_movement_dict, list of decision_movement)
    prevision_to_decision_map = []

    # First find out all existing (decision) movements which belong to no group
    no_group_list = []
    for tester_key in decision_movement_dict.keys():
      if prevision_movement_dict.has_key(tester_key):
        for decision_movement in decision_movement_dict[tester_key]:
          no_match = True
          for prevision_movement in prevision_movement_dict[tester_key]:
            # Check if this movement belongs to an existing group
            if _compare(tester_list, prevision_movement, decision_movement):
              no_match = False
              break
          if no_match:
            # There is no matching.
            # So, let us add the decision movements to no_group_list
            no_group_list.append(decision_movement)
      else:
        # The tester key does not even exist.
        # So, let us add all decision movements to no_group_list
        no_group_list.extend(decision_movement_dict[tester_key])
    if len(no_group_list) > 0:
      prevision_to_decision_map.append((None, no_group_list))

    # Second, let us create small groups of movements
    for tester_key in prevision_movement_dict.keys():
      for prevision_movement in prevision_movement_dict[tester_key]:
        map_list = []
        for decision_movement in decision_movement_dict.get(tester_key, ()):
          if _compare(tester_list, prevision_movement, decision_movement):
137 138
            # XXX is it OK to have more than 2 decision_movements?
            # XXX-JPS - I think yes
139 140 141 142
            map_list.append(decision_movement)
        prevision_to_decision_map.append((prevision_movement, map_list))

    # Third, time to create the diff
143
    movement_collection_diff = MovementCollectionDiff()
144 145 146 147 148 149 150
    for (prevision_movement, decision_movement_list) in prevision_to_decision_map:
      self._extendMovementCollectionDiff(movement_collection_diff, prevision_movement,
                                         decision_movement_list)

    return movement_collection_diff

  def updateMovementCollection(self, context, rounding=False,
151
                               movement_generator=None):
152 153 154 155 156 157 158 159 160 161 162 163
    """
    Invoke getMovementCollectionDiff and update context with
    the resulting IMovementCollectionDiff.

    context -- an IMovementCollection usually, possibly
               an IMovementList or an IMovement

    movement_generator -- an optional IMovementGenerator
                          (if not specified, a context implicit
                          IMovementGenerator will be used)
    """
    movement_diff = self.getMovementCollectionDiff(context,
164
                 rounding=rounding, movement_generator=movement_generator)
165 166 167 168

    # Apply Diff
    for movement in movement_diff.getDeletableMovementList():
      movement.getParentValue().deleteContent(movement.getId())
169
    for movement, kw in movement_diff.getUpdatableMovementList():
170
      movement.edit(**kw)
171
      for property_id in kw:
172
        movement.clearRecordedProperty(property_id)
173
    for movement in movement_diff.getNewMovementList():
174
      d = movement.__dict__
175 176 177
      assert movement.isTempObject()
      if '_original' in d:
        # slow but safe way (required for compensated movements)
178 179
        context.newContent(portal_type=self.movement_type,
          **_getPropertyAndCategoryList(movement))
180 181 182
        continue
      # fast way (we had to make sure such optimization
      # does not touch existing persistent data)
183
      del movement.__dict__
184
      movement = context.newContent(portal_type=self.movement_type)
185
      d.update(movement.__dict__)
186
      categories = d.pop('categories')
187
      movement.__dict__ = d
188 189 190
      # Force update of local indexes on linked objects
      # (important for 'delivery').
      movement._setCategoryList(categories)
191 192

InitializeClass(MovementCollectionUpdaterMixin)