TradeModelSolver.py 6.51 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
# -*- 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
32
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
33 34 35
from Products.ERP5.Document.AcceptSolver import AcceptSolver

class TradeModelSolver(AcceptSolver):
Jérome Perrin's avatar
Jérome Perrin committed
36 37 38 39
  """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.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
  """
  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
62 63
  security.declarePrivate('solve')
  @UnrestrictedMethod
Sebastien Robin's avatar
Sebastien Robin committed
64
  def solve(self, activate_kw=None):
65
    """
66 67
    Adopt new values to simulation movements, with keeping the original
    one recorded, and then update Trade Model related lines accordingly.
68
    """
Julien Muchembled's avatar
Julien Muchembled committed
69 70 71 72 73 74 75
    portal = self.getPortalObject()
    solved_property_list = self.getConfigurationPropertyDict() \
                               .get('tested_property_list')
    if solved_property_list is None:
      solved_property_list = \
        portal.portal_types.getTypeInfo(self).getTestedPropertyList()
    delivery_dict = {} # {movement: simulation_movement_list}
Sebastien Robin's avatar
Sebastien Robin committed
76 77 78
    for simulation_movement in self.getDeliveryValueList():
      delivery_dict.setdefault(simulation_movement.getDeliveryValue(),
                               []).append(simulation_movement)
79 80 81 82

    # First, separate movements into invoice lines and trade model
    # related lines.
    # XXX is there any better way than using rule's reference?
Julien Muchembled's avatar
Julien Muchembled committed
83
    trade_model_related_movement_dict = {}
84 85
    for delivery in {movement.getRootDeliveryValue()
                     for movement in delivery_dict}:
Julien Muchembled's avatar
Julien Muchembled committed
86
      for movement in delivery.getMovementList():
87
        movement_list = delivery_dict.get(movement)
Julien Muchembled's avatar
Julien Muchembled committed
88
        # hard coded reference name
89 90 91 92 93 94 95 96 97 98 99
        if movement_list:
          rule = movement_list[0].getParentValue().getSpecialiseReference()
          if rule != 'default_trade_model_rule':
            continue
          movement_list = movement.getDeliveryRelatedValueList()
        else:
          movement_list = movement.getDeliveryRelatedValueList()
          rule = movement_list[0].getParentValue().getSpecialiseReference()
          if rule != 'default_trade_model_rule':
            continue
        trade_model_related_movement_dict[movement] = movement_list
100

Julien Muchembled's avatar
Julien Muchembled committed
101
    with self.defaultActivateParameterDict(activate_kw, True):
102 103 104
      # Second, apply changes on invoice lines to simulation movements,
      # then expand.
      for movement, simulation_movement_list in delivery_dict.iteritems():
Julien Muchembled's avatar
Julien Muchembled committed
105
        if movement in trade_model_related_movement_dict:
106 107 108 109 110 111
          continue
        for simulation_movement in simulation_movement_list:
          value_dict = {}
          for solved_property in solved_property_list:
            new_value = movement.getProperty(solved_property)
            if solved_property == 'quantity':
Julien Muchembled's avatar
Julien Muchembled committed
112 113
              new_value *= simulation_movement.getDeliveryRatio()
            value_dict[solved_property] = new_value
114 115 116
          for property_id, value in value_dict.iteritems():
            if not simulation_movement.isPropertyRecorded(property_id):
              simulation_movement.recordProperty(property_id)
117
            simulation_movement.setProperty(property_id, value)
118
          simulation_movement.expand('immediate')
119 120 121

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

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