TradeModelSolver.py 6.39 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
    """
Julien Muchembled's avatar
Julien Muchembled committed
66 67 68 69 70 71 72
    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
73 74 75
    for simulation_movement in self.getDeliveryValueList():
      delivery_dict.setdefault(simulation_movement.getDeliveryValue(),
                               []).append(simulation_movement)
76 77 78 79

    # 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
80
    trade_model_related_movement_dict = {}
81 82
    for delivery in {movement.getRootDeliveryValue()
                     for movement in delivery_dict}:
Julien Muchembled's avatar
Julien Muchembled committed
83
      for movement in delivery.getMovementList():
84
        movement_list = delivery_dict.get(movement)
Julien Muchembled's avatar
Julien Muchembled committed
85
        # hard coded reference name
86 87 88 89 90 91 92 93 94 95 96
        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
97

Julien Muchembled's avatar
Julien Muchembled committed
98
    with self.defaultActivateParameterDict(activate_kw, True):
99 100 101
      # 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
102
        if movement in trade_model_related_movement_dict:
103 104 105 106 107 108
          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
109 110
              new_value *= simulation_movement.getDeliveryRatio()
            value_dict[solved_property] = new_value
111 112 113
          for property_id, value in value_dict.iteritems():
            if not simulation_movement.isPropertyRecorded(property_id):
              simulation_movement.recordProperty(property_id)
114
            simulation_movement.setProperty(property_id, value)
115
          simulation_movement.expand('immediate')
116 117 118

      # Third, adopt changes on trade model related lines.
      # XXX non-linear case is not yet supported.
Julien Muchembled's avatar
Julien Muchembled committed
119 120
      for movement, simulation_movement_list in \
          trade_model_related_movement_dict.iteritems():
121 122
        for solved_property in solved_property_list:
          if solved_property == 'quantity':
Julien Muchembled's avatar
Julien Muchembled committed
123 124
            total_quantity = sum(x.getQuantity()
              for x in simulation_movement_list)
125 126 127
            movement.setQuantity(total_quantity)
            for simulation_movement in simulation_movement_list:
              quantity = simulation_movement.getQuantity()
Julien Muchembled's avatar
Julien Muchembled committed
128
              if total_quantity:
129 130 131 132 133
                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
134
                                       delivery_error=delivery_error)
135
          else:
136 137 138
            # 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
139
              simulation_movement_list[0].getProperty(solved_property))
140 141

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