diff --git a/product/ERP5/Document/AppliedRule.py b/product/ERP5/Document/AppliedRule.py index 94a0b2bfc817f0add021abf6639a4520fead0ef7..729185b65346af0eba92bbdd06b9e817e61de252 100644 --- a/product/ERP5/Document/AppliedRule.py +++ b/product/ERP5/Document/AppliedRule.py @@ -324,8 +324,13 @@ class AppliedRule(XMLObject, ExplainableMixin): # Delivery is/was not is draft state order_dict = {} old_dict = {} - for sm in list(self.objectValues()): - old_dict[sm.getOrder() or sm.getDelivery()] = sm_dict = {} + # Caller may want to drop duplicate SM, like a unbuilt SM if there's + # already a built one, or one with no quantity. So first call + # 'get_matching_key' on SM that would be kept. 'get_matching_key' would + # remember them and returns None for duplicates. + sort_sm = lambda x: (not x.getDelivery(), not x.getQuantity(), x.getId()) + for sm in sorted(self.objectValues(), key=sort_sm): + sm_dict = old_dict.setdefault(sm.getOrder() or sm.getDelivery(), {}) recurse_list = deque(({get_matching_key(sm): (sm,)},)) while recurse_list: for k, x in recurse_list.popleft().iteritems(): @@ -333,6 +338,8 @@ class AppliedRule(XMLObject, ExplainableMixin): continue if len(x) > 1: x = [x for x in x if x.getDelivery() or x.getQuantity()] + if len(x) > 1: + x.sort(key=sort_sm) sm_dict.setdefault(k, []).extend(x) for x in x: r = {} @@ -353,8 +360,11 @@ class AppliedRule(XMLObject, ExplainableMixin): # does not see the simulated movements we've just deleted. if delivery.isSimulated(): break + # Do not try to keep simulation tree for draft delivery + # if it was already out of sync. if delivery.getSimulationState() in draft_state_list and \ - any(x not in old_dict for x in delivery.getMovementList()): + any(x.getRelativeUrl() not in old_dict + for x in delivery.getMovementList()): break if root_rule: self.setSpecialise(root_rule) @@ -376,7 +386,10 @@ class AppliedRule(XMLObject, ExplainableMixin): # currently expanded applied rule. We first try to preserve same # tree structure (new & old parent SM match), then we look for an # old possible parent that is in the same branch. - old_parent = old_dict[new_parent] + try: + old_parent = old_dict[new_parent] + except KeyError: + old_parent = simulation_tool best_dict = {} for old_sm in sm_list: parent = old_sm.getParentValue().getParentValue() @@ -398,24 +411,31 @@ class AppliedRule(XMLObject, ExplainableMixin): # We may have several old matching SM, e.g. in case of split. for old_sm in sm_list: movement = old_sm.getDeliveryValue() + if sm is None: + sm = context.newContent(portal_type=rule.movement_type) + sm.__dict__ = dict(kw, **sm.__dict__) + order_dict[sm] = sm_dict if delivery: assert movement.getRelativeUrl() == delivery elif movement is not None: - if sm is None: - sm = context.newContent(portal_type=rule.movement_type) - sm.__dict__ = dict(kw, **sm.__dict__) - order_dict[sm] = sm_dict sm._setDeliveryValue(movement) delivery_set.add(sm.getExplanationValue()) recorded_property_dict = {} edit_kw = {} + kw['quantity'] = 0 for tester in rule._getUpdatingTesterList(): old = get_original_property_dict(tester, old_sm, sm, movement) if old is not None: new = tester.getUpdatablePropertyDict(sm, movement) if old != new: - recorded_property_dict.update(new) edit_kw.update(old) + if 'quantity' in new and old_sm is not sm_list[-1]: + quantity = new.pop('quantity') + kw['quantity'] = quantity - old.pop('quantity') + if new != old or sm.quantity != quantity: + raise NotImplementedError # quantity_unit/efficiency ? + else: + recorded_property_dict.update(new) if recorded_property_dict: sm._recorded_property_dict = PersistentMapping( recorded_property_dict) @@ -423,6 +443,9 @@ class AppliedRule(XMLObject, ExplainableMixin): old_dict[sm] = old_sm sm = None deleted = old_dict.items() + for delivery, sm_dict in deleted: + if not sm_dict: + del old_dict[delivery] from Products.ERP5.mixin.movement_collection_updater import \ MovementCollectionUpdaterMixin as mixin # Patch is already protected by WorkflowMethod.disable lock.