TradeModelSolver.py 6.59 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
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2010 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, PropertySheet, interfaces
from Products.ERP5.Document.AcceptSolver import AcceptSolver

class TradeModelSolver(AcceptSolver):
Jérome Perrin's avatar
Jérome Perrin committed
35 36 37 38
  """Solve Divergences on Invoice Lines, and dependant trade model lines.

  It consist in accepting decision from invoice lines, and adopting prevision
  on trade model lines.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
  """
  meta_type = 'ERP5 Trade Model Solver'
  portal_type = 'Trade Model Solver'
  add_permission = Permissions.AddPortalContent
  isIndexable = 0 # We do not want to fill the catalog with objects on which we need no reporting

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

  # Default Properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.XMLObject
                    , PropertySheet.CategoryCore
                    , PropertySheet.DublinCore
                    , PropertySheet.TargetSolver
                    )

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

  # ISolver Implementation
Sebastien Robin's avatar
Sebastien Robin committed
61
  def solve(self, activate_kw=None):
62
    """
63 64
    Adopt new values to simulation movements, with keeping the original
    one recorded, and then update Trade Model related lines accordingly.
65
    """
66
    configuration_dict = self.getConfigurationPropertyDict()
Sebastien Robin's avatar
Sebastien Robin committed
67
    portal_type = self.getPortalObject().portal_types.getTypeInfo(self)
68 69
    solved_property_list = configuration_dict.get('tested_property_list',
                                                  portal_type.getTestedPropertyList())
Sebastien Robin's avatar
Sebastien Robin committed
70 71 72 73
    delivery_dict = {}
    for simulation_movement in self.getDeliveryValueList():
      delivery_dict.setdefault(simulation_movement.getDeliveryValue(),
                               []).append(simulation_movement)
74 75

    # Here, items of delivery_list should be movements, not deliveries.
Sebastien Robin's avatar
Sebastien Robin committed
76 77 78 79 80 81
    delivery_set = set()
    solved_movement_list = delivery_dict.iterkeys()
    for movement in solved_movement_list:
      delivery = movement.getRootDeliveryValue()
      delivery_set.add(delivery)
    all_movement_list = sum([x.getMovementList() for x in delivery_set], [])
82 83 84 85 86 87 88 89 90 91 92 93 94

    # First, separate movements into invoice lines and trade model
    # related lines.
    # XXX is there any better way than using rule's reference?
    trade_model_related_movement_list = []
    for movement in all_movement_list:
      if movement in solved_movement_list:
        continue
      applied_rule = movement.getDeliveryRelatedValue().getParentValue()
      # hard coded reference name
      if applied_rule.getSpecialiseReference() == 'default_trade_model_rule':
        trade_model_related_movement_list.append(movement)

95 96
    # Second, apply changes on invoice lines to simulation movements,
    # then expand.
Sebastien Robin's avatar
Sebastien Robin committed
97
    for movement, simulation_movement_list in delivery_dict.iteritems():
98 99
      if movement in trade_model_related_movement_list:
        continue
Sebastien Robin's avatar
Sebastien Robin committed
100 101 102 103
      for simulation_movement in simulation_movement_list:
        if activate_kw is not None:
          simulation_movement.setDefaultActivateParameters(
            activate_kw=activate_kw, **activate_kw)
104 105 106 107 108 109 110 111
        value_dict = {}
        for solved_property in solved_property_list:
          new_value = movement.getProperty(solved_property)
          if solved_property == 'quantity':
            new_quantity = new_value * simulation_movement.getDeliveryRatio()
            value_dict.update({'quantity':new_quantity})
          else:
            value_dict.update({solved_property:new_value})
112 113 114 115
        for property_id, value in value_dict.iteritems():
          if not simulation_movement.isPropertyRecorded(property_id):
            simulation_movement.recordProperty(property_id)
          simulation_movement.setMappedProperty(property_id, value)
Sebastien Robin's avatar
Sebastien Robin committed
116
        simulation_movement.expand(activate_kw=activate_kw)
117 118

    # Third, adopt changes on trade model related lines.
119
    # XXX non-linear case is not yet supported.
120
    for movement in trade_model_related_movement_list:
Sebastien Robin's avatar
Sebastien Robin committed
121 122 123
      if activate_kw is not None:
        movement.setDefaultActivateParameters(
          activate_kw=activate_kw, **activate_kw)
124 125
      for solved_property in solved_property_list:
        if solved_property == 'quantity':
126
          simulation_movement_list = movement.getDeliveryRelatedValueList()
127
          total_quantity = sum(
128
            [x.getQuantity() for x in simulation_movement_list])
129
          movement.setQuantity(total_quantity)
130
          for simulation_movement in simulation_movement_list:
131 132 133 134
            quantity = simulation_movement.getQuantity()
            delivery_ratio = quantity / total_quantity
            delivery_error = total_quantity * delivery_ratio - quantity
            simulation_movement.edit(delivery_ratio=delivery_ratio,
Sebastien Robin's avatar
Sebastien Robin committed
135 136
                                     delivery_error=delivery_error,
                                     activate_kw=activate_kw)
137 138 139 140 141 142 143 144
        else:
          # XXX TODO we need to support multiple values for categories or
          # list type property.
          simulation_movement = movement.getDeliveryRelatedValue()
          movement.setProperty(solved_property,
                               simulation_movement.getProperty(solved_property))

    # Finish solving
Sebastien Robin's avatar
Sebastien Robin committed
145 146 147
    if self.getPortalObject().portal_workflow.isTransitionPossible(
      self, 'succeed'):
      self.succeed()