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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
94
95
96
97
98
99
100
101
102
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
137
138
139
140
141
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
184
185
186
187
188
189
190
191
192
193
194
195
# -*- 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, _getPropertyAndCategoryList)
from Products.ERP5.mixin.rule import _compare
class MovementCollectionUpdaterMixin:
"""Movement Collection Updater.
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,
movement_generator=None):
"""
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()
prevision_movement_list = movement_generator.getGeneratedMovementList(
# XXX-JPS This mixin is not self-contained
movement_list=self._getMovementGeneratorMovementList(context), rounding=rounding)
# Get divergence testers
tester_list = self._getMatchingTesterList()
if not tester_list and len(prevision_movement_list) > 1:
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, ))
# 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)
prevision_movement_dict.setdefault(tester_key, []).append(movement)
# 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):
# XXX is it OK to have more than 2 decision_movements?
# XXX-JPS - I think yes
map_list.append(decision_movement)
prevision_to_decision_map.append((prevision_movement, map_list))
# Third, time to create the diff
movement_collection_diff = MovementCollectionDiff()
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,
movement_generator=None):
"""
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,
rounding=rounding, movement_generator=movement_generator)
# Apply Diff
for movement in movement_diff.getDeletableMovementList():
movement.getParentValue().deleteContent(movement.getId())
for movement, kw in movement_diff.getUpdatableMovementList():
movement.edit(**kw)
for property_id in kw:
movement.clearRecordedProperty(property_id)
movement_list = movement_diff.getNewMovementList()
if not movement_list:
return
if context.isRootAppliedRule():
reindex_kw = {'activate_kw': {
'tag': 'built:' + context.getCausalityValue().getPath()}}
else:
reindex_kw = None
def newMovement(kw={}):
return context.newContent(portal_type=self.movement_type,
reindex_kw=reindex_kw, **kw)
for movement in movement_list:
d = movement.__dict__
assert movement.isTempObject()
if '_original' in d:
# slow but safe way (required for compensated movements)
newMovement(_getPropertyAndCategoryList(movement))
continue
# fast way (we had to make sure such optimization
# does not touch existing persistent data)
del movement.__dict__
movement = newMovement()
d.update(movement.__dict__)
movement.__dict__ = d