movement_collection_updater.py 7.94 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 31 32 33 34 35
# -*- 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
from Products.ERP5Type import Permissions, interfaces
from Products.ERP5.MovementCollectionDiff import MovementCollectionDiff
from Products.ERP5.mixin.rule import _compare

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

  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,
54
                                movement_generator=None,
55
                                updating_tester_list=None):
56 57 58 59 60 61 62 63 64 65 66
    """
    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)
67

68
    updating_tester_list -- a list of testers used to build updatable dicts
69 70 71
    """
    # We suppose here that we have an IMovementCollection in hand
    decision_movement_list = context.getMovementList()
72
    prevision_movement_list = movement_generator.getGeneratedMovementList(
73 74
      # XXX-JPS This mixin is not self-contained
      movement_list=self._getMovementGeneratorMovementList(context), rounding=rounding)
75 76 77

    # Get divergence testers
    tester_list = self._getMatchingTesterList()
Julien Muchembled's avatar
Julien Muchembled committed
78
    if not tester_list and len(prevision_movement_list) > 1:
79 80 81
      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, ))
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

    # 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)
103
      prevision_movement_dict.setdefault(tester_key, []).append(movement)
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 137

    # 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):
138 139
            # XXX is it OK to have more than 2 decision_movements?
            # XXX-JPS - I think yes
140 141 142 143
            map_list.append(decision_movement)
        prevision_to_decision_map.append((prevision_movement, map_list))

    # Third, time to create the diff
144
    movement_collection_diff = MovementCollectionDiff(
145
                                   updating_tester_list=updating_tester_list)
146 147 148 149 150 151 152
    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,
153
                               movement_generator=None,
154
                               updating_tester_list=None):
155 156 157 158 159 160 161 162 163 164
    """
    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)
165

166
    updating_tester_list -- a list of testers used to build updatable dicts
167 168
    """
    movement_diff = self.getMovementCollectionDiff(context,
169
                 rounding=rounding, movement_generator=movement_generator,
170
                 updating_tester_list=updating_tester_list)
171 172 173 174 175 176 177

    # Apply Diff
    for movement in movement_diff.getDeletableMovementList():
      movement.getParentValue().deleteContent(movement.getId())
    for movement in movement_diff.getUpdatableMovementList():
      kw = movement_diff.getMovementPropertyDict(movement)
      movement.edit(**kw)
178 179
      for property_id in kw.iterkeys():
        movement.clearRecordedProperty(property_id)
180 181 182
    for movement in movement_diff.getNewMovementList():
      kw = movement_diff.getMovementPropertyDict(movement)
      movement = context.newContent(portal_type=self.movement_type, **kw)
183 184