Esempio n. 1
0
  def solve(self, simulation_movement):
    """
      Split a simulation movement and accumulate
    """
    movement_quantity = simulation_movement.getQuantity()
    delivery_quantity = simulation_movement.getDeliveryQuantity()
    new_movement_quantity = delivery_quantity * simulation_movement.getDeliveryRatio()
    applied_rule = simulation_movement.getParentValue()
    rule = applied_rule.getSpecialiseValue()

    # When accounting, the debit price is expressed by a minus quantity.
    # Thus, we must take into account the both minus and plus quantity.
    if ((movement_quantity < new_movement_quantity <= 0) or
        (movement_quantity > new_movement_quantity >= 0)):
      split_index = 0
      new_id = "%s_split_%s" % (simulation_movement.getId(), split_index)
      while getattr(aq_base(applied_rule), new_id, None) is not None:
        split_index += 1
        new_id = "%s_split_%s" % (simulation_movement.getId(), split_index)
      # Adopt different dates for deferred movements
      from erp5.component.module.MovementCollectionDiff import _getPropertyAndCategoryList
      movement_dict = _getPropertyAndCategoryList(simulation_movement)
      # new properties
      movement_dict.update(
        portal_type="Simulation Movement",
        id=new_id,
        quantity=movement_quantity - new_movement_quantity,
        activate_kw=self.activate_kw,
        delivery=None,
        **self.additional_parameters
      )
      new_movement = applied_rule.newContent(**movement_dict)
      # Dirty code until IPropertyRecordable is revised.
      # Merge original simulation movement recorded property to new one.
      recorded_property_dict = simulation_movement._getRecordedPropertyDict(None)
      if recorded_property_dict:
        new_movement_recorded_property_dict = new_movement._getRecordedPropertyDict(None)
        if new_movement_recorded_property_dict is None:
          new_movement_recorded_property_dict = new_movement._recorded_property_dict = PersistentMapping()
        new_movement_recorded_property_dict.update(recorded_property_dict)
      # record zero quantity property, because this was originally zero.
      # without this, splitanddefer after accept decision does not work
      # properly.
      current_quantity = new_movement.getQuantity()
      new_movement.setQuantity(0)
      new_movement.recordProperty('quantity')
      new_movement.setQuantity(current_quantity)
      start_date = getattr(self, 'start_date', None)
      if start_date is not None:
        new_movement.recordProperty('start_date')
        new_movement.edit(start_date=start_date)
      stop_date = getattr(self, 'stop_date', None)
      if stop_date is not None:
        new_movement.recordProperty('stop_date')
        new_movement.edit(stop_date=stop_date)
      new_movement.expand(activate_kw=self.additional_parameters)
    # adopt new quantity on original simulation movement
    simulation_movement.edit(quantity=new_movement_quantity)
    simulation_movement.setDefaultActivateParameterDict(self.activate_kw)
    simulation_movement.expand(activate_kw=self.additional_parameters)
Esempio n. 2
0
    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)
        for movement in movement_diff.getNewMovementList():
            d = movement.__dict__
            assert movement.isTempObject()
            if '_original' in d:
                # slow but safe way (required for compensated movements)
                context.newContent(portal_type=self.movement_type,
                                   **_getPropertyAndCategoryList(movement))
                continue
            # fast way (we had to make sure such optimization
            # does not touch existing persistent data)
            del movement.__dict__
            movement = context.newContent(portal_type=self.movement_type)
            d.update(movement.__dict__)
            categories = d.pop('categories')
            movement.__dict__ = d
            # Force update of local indexes on linked objects
            # (important for 'delivery').
            movement._setCategoryList(categories)
Esempio n. 3
0
 def _solveBySplitting(self, activate_kw=None):
     """
 contains all the logic to split. This method is convenient in case
 another solver needs it.
 """
     solver_dict = {}
     new_movement_list = []
     configuration_dict = self.getConfigurationPropertyDict()
     delivery_dict = {}
     for simulation_movement in self.getDeliveryValueList():
         delivery_dict.setdefault(simulation_movement.getDeliveryValue(),
                                  []).append(simulation_movement)
     for movement, simulation_movement_list in delivery_dict.iteritems():
         decision_quantity = movement.getQuantity()
         delivery_solver = self.getParentValue().newContent(
             portal_type=configuration_dict['delivery_solver'],
             temp_object=True)
         delivery_solver.setDeliveryValueList(simulation_movement_list)
         # Update the quantity using delivery solver algorithm
         split_list = delivery_solver.setTotalQuantity(
             decision_quantity, activate_kw=activate_kw)
         # Create split movements
         for (simulation_movement, split_quantity) in split_list:
             split_index = 0
             simulation_id = simulation_movement.getId().split("_split_")[0]
             new_id = "%s_split_%s" % (simulation_id, split_index)
             applied_rule = simulation_movement.getParentValue()
             while getattr(aq_base(applied_rule), new_id, None) is not None:
                 split_index += 1
                 new_id = "%s_split_%s" % (simulation_id, split_index)
             # Copy at same level
             kw = _getPropertyAndCategoryList(simulation_movement)
             kw.update(delivery=None, quantity=split_quantity)
             new_movement = applied_rule.newContent(
                 new_id,
                 simulation_movement.getPortalType(),
                 activate_kw=activate_kw,
                 **kw)
             new_movement_list.append(new_movement)
             # Dirty code until IPropertyRecordable is revised.
             # Merge original simulation movement recorded property to new one.
             recorded_property_dict = simulation_movement._getRecordedPropertyDict(
                 None)
             if recorded_property_dict:
                 new_movement_recorded_property_dict = new_movement._getRecordedPropertyDict(
                     None)
                 if new_movement_recorded_property_dict is None:
                     new_movement_recorded_property_dict = new_movement._recorded_property_dict = PersistentMapping(
                     )
                 new_movement_recorded_property_dict.update(
                     recorded_property_dict)
             # record zero quantity property, because this was originally zero.
             # without this, splitanddefer after accept decision does not work
             # properly.
             current_quantity = new_movement.getQuantity()
             new_movement.setQuantity(0)
             new_movement.recordProperty('quantity')
             new_movement.setQuantity(current_quantity)
             start_date = configuration_dict.get('start_date', None)
             if start_date is not None:
                 new_movement.recordProperty('start_date')
                 new_movement.setStartDate(start_date)
             stop_date = configuration_dict.get('stop_date', None)
             if stop_date is not None:
                 new_movement.recordProperty('stop_date')
                 new_movement.setStopDate(stop_date)
             if activate_kw:
                 new_movement.setDefaultActivateParameterDict({})
             simulation_movement.expand(activate_kw=activate_kw)
             new_movement.expand(activate_kw=activate_kw)
     # Finish solving
     if self.getPortalObject().portal_workflow.isTransitionPossible(
             self, 'succeed'):
         self.succeed()
     solver_dict["new_movement_list"] = new_movement_list
     return solver_dict
Esempio n. 4
0
    def _solve(self, activate_kw=None):
        """This method create new movement based on difference of aggregate sets.
    It supports only removed items.
    Quantity divergence is also solved with sum of aggregated quantities stored
    on each updated movements.
    """
        configuration_dict = self.getConfigurationPropertyDict()
        delivery_dict = {}
        portal = self.getPortalObject()
        for simulation_movement in self.getDeliveryValueList():
            delivery_dict.setdefault(simulation_movement.getDeliveryValue(),
                                     []).append(simulation_movement)

        for movement, simulation_movement_list in delivery_dict.iteritems():
            decision_aggregate_set = set(movement.getAggregateList())
            split_list = []
            for simulation_movement in simulation_movement_list:
                simulated_aggregate_set = set(
                    simulation_movement.getAggregateList())
                difference_set = simulated_aggregate_set.difference(
                    decision_aggregate_set)
                mirror_difference_set = decision_aggregate_set.difference(
                    simulated_aggregate_set)
                if difference_set:
                    # There is less aggregates in prevision compare to decision
                    split_list.append((simulation_movement, difference_set))
                elif mirror_difference_set:
                    # There is additional aggregates in decision compare to prevision
                    raise NotImplementedError('Additional items detected. This solver'\
                          ' does not support such divergence resolution.')
                else:
                    # Same set, no divergence
                    continue
            # Create split movements
            for (simulation_movement, splitted_aggregate_set) in split_list:
                split_index = 0
                new_id = "%s_split_%s" % (simulation_movement.getId(),
                                          split_index)
                applied_rule = simulation_movement.getParentValue()
                while getattr(aq_base(applied_rule), new_id, None) is not None:
                    split_index += 1
                    new_id = "%s_split_%s" % (simulation_movement.getId(),
                                              split_index)
                # Copy at same level
                kw = _getPropertyAndCategoryList(simulation_movement)
                previous_aggregate_list = simulation_movement.getAggregateList(
                )
                new_aggregate_list = list(set(previous_aggregate_list)\
                                        .symmetric_difference(splitted_aggregate_set))
                # freeze those properties only if not yet recorded
                # to avoid freezing already recorded value
                if not simulation_movement.isPropertyRecorded('aggregate'):
                    simulation_movement.recordProperty('aggregate')

                # edit prevision movement
                simulation_movement.setAggregateList(new_aggregate_list)
                total_quantity = sum(item.getQuantity() for item in\
                                           simulation_movement.getAggregateValueList())
                simulation_movement.setQuantity(total_quantity)

                # create compensation decision movement
                total_quantity = sum([portal.restrictedTraverse(aggregate).getQuantity()\
                                      for aggregate in splitted_aggregate_set])
                kw.update({
                    'portal_type': simulation_movement.getPortalType(),
                    'id': new_id,
                    'delivery': None
                })
                # propagate same recorded properties from original movement
                # to store them in recorded_property
                for frozen_property in (
                        'aggregate',
                        'start_date',
                        'stop_date',
                ):
                    if simulation_movement.isPropertyRecorded(frozen_property):
                        kw[frozen_property] = simulation_movement.getRecordedProperty(
                            frozen_property)

                new_movement = applied_rule.newContent(activate_kw=activate_kw,
                                                       **kw)
                # freeze aggregate property
                new_movement.recordProperty('aggregate')
                # edit compensation decision movement
                new_movement.setAggregateList(list(splitted_aggregate_set))
                new_movement.setQuantity(total_quantity)

                if activate_kw is not None:
                    new_movement.setDefaultActivateParameterDict(activate_kw)
                start_date = configuration_dict.get('start_date', None)
                if start_date is not None:
                    new_movement.recordProperty('start_date')
                    new_movement.setStartDate(start_date)
                stop_date = configuration_dict.get('stop_date', None)
                if stop_date is not None:
                    new_movement.recordProperty('stop_date')
                    new_movement.setStopDate(stop_date)

        # Finish solving
        if self.getPortalObject().portal_workflow.isTransitionPossible(
                self, 'succeed'):
            self.succeed()
Esempio n. 5
0
  def _getPropertyAndCategoryDict(self, explanation, amount, trade_model_path, delay_mode=None):
    """A private method to merge an amount and a business_link and return
    a dict of properties and categories which can be used to create a
    new movement.

    explanation -- an Order, Order Line, Delivery or Delivery Line or
                   Applied Rule which implicitely defines a simulation subtree

    amount -- an IAmount instance or an IMovement instance

    trade_model_path -- an ITradeModelPath instance

    delay_mode -- optional value to specify calculation mode ('min', 'max')
                  if no value specified use average delay
    """
    if explanation.getPortalType() == "Applied Rule":
      rule = explanation.getSpecialiseValue()
    else:
      rule = None
    if rule is None:
      property_dict = _getPropertyAndCategoryList(amount)
    else:
      property_dict = {}
      for tester in rule._getUpdatingTesterList():
        property_dict.update(tester.getUpdatablePropertyDict(
          amount, None))

    # Arrow categories
    property_dict.update(trade_model_path.getArrowCategoryDict(context=amount))

    # More categories
    for base_category in ('delivery_mode', 'incoterm', 'payment_mode', 'ledger'):
      value = trade_model_path.getPropertyList(base_category)
      if value:
        property_dict[base_category] = value

    # Amount quantities - XXX-JPS maybe we should consider handling unit conversions here
    # and specifying units
    if trade_model_path.getQuantity():
      property_dict['quantity'] = trade_model_path.getQuantity()
    elif trade_model_path.getEfficiency():
      property_dict['quantity'] = amount.getQuantity() *\
        trade_model_path.getEfficiency()
    else:
      property_dict['quantity'] = amount.getQuantity()

    # Dates - the main concept of BPM is to search for reference dates
    # in parent simulation movements at expand time. This means that
    # a trade date which is based on a trade phase which is handled
    # by a child applied rule is not supported in ERP5 BPM.
    # In the same spirit, date calculation at expand time is local, not
    # global.
    if explanation.getPortalType() == 'Applied Rule':
      if explanation.getParentValue().getPortalType() != "Simulation Tool":
        # It only makes sense to search for start and stop dates for
        # applied rules which are not root applied rules.
        # Date calculation by Business Process can be also disabled by
        # leaving 'trade_date' unset (XXX: a separate boolean property,
        # on the TMP or the rule, may be better).
        if trade_model_path.getTradeDate():
          property_dict['start_date'], property_dict['stop_date'] = \
            self.getExpectedTradeModelPathStartAndStopDate(
              explanation, trade_model_path, delay_mode=delay_mode)
    # Else, nothing to do. This method can be used without Applied Rule.
    return property_dict