SolverTool.py 11 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
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability 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
# garantees 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

Jean-Paul Smets's avatar
Jean-Paul Smets committed
30 31
import zope.interface

32 33 34 35
from Products.CMFCore.utils import getToolByName

from AccessControl import ClassSecurityInfo
from Globals import InitializeClass, DTMLFile
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36
from Products.ERP5Type import Permissions, interfaces
37
from Products.ERP5Type.Tool.BaseTool import BaseTool
38 39
from Products.ERP5Type.Message import translateString
from Products.ERP5 import DeliverySolver
40 41 42 43 44 45

from Products.ERP5 import _dtmldir

from zLOG import LOG

class SolverTool(BaseTool):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
  """
    The SolverTool provides API to find out which solver can
    be applied in which case and contains SolverProcess instances
    which are used to keep track of solver decisions, solver
    history and global optimisation.

    NOTE: this class is experimental and is subject to be removed
  """
  id = 'portal_solvers'
  meta_type = 'ERP5 Solver Tool'
  portal_type = 'Solver Tool'
  allowed_types = ( 'ERP5 Solver Process', )

  # Declarative Security
  security = ClassSecurityInfo()

  #
  #   ZMI methods
  #
  security.declareProtected( Permissions.ManagePortal, 'manage_overview' )
  manage_overview = DTMLFile( 'explainSolverTool', _dtmldir )

  # Declarative interfaces
  zope.interface.implements(interfaces.IDeliverySolverFactory,
70
                            interfaces.IDivergenceController,
Jean-Paul Smets's avatar
Jean-Paul Smets committed
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
                           )

  # Implementation
  def filtered_meta_types(self, user=None):
    # Filters the list of available meta types.
    all = SolverTool.inheritedAttribute('filtered_meta_types')(self)
    meta_types = []
    for meta_type in self.all_meta_types():
      if meta_type['name'] in self.allowed_types:
        meta_types.append(meta_type)
    return meta_types

  def tpValues(self) :
    """ show the content in the left pane of the ZMI """
    return self.objectValues()

87
  # IDeliverySolverFactory implementation
Jean-Paul Smets's avatar
Jean-Paul Smets committed
88 89
  def newDeliverySolver(self, class_name, movement_list):
    """
90
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
91
    raise NotImplementedError
92

Jean-Paul Smets's avatar
Jean-Paul Smets committed
93
  def getDeliverySolverClassNameList(self):
94
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
95
    """
96 97 98
    # XXX Hardcoded for now. We need a new registration system for
    # delivery solvers.
    return ['FIFO', 'FILO',]
99

Jean-Paul Smets's avatar
Jean-Paul Smets committed
100 101 102
  def getDeliverySolverTranslatedItemList(self, class_name_list=None):
    """
    """
103 104 105 106
    return sorted([(self.getDeliverySolverTranslatedTitle(x), x) \
                   for x in self.getDeliverySolverClassNameList() \
                   if class_name_list is None or x in class_name_list],
                  key=lambda x:str(x[0]))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
107 108 109 110

  def getDeliverySolverTranslatedTitle(self, class_name):
    """
    """
111 112 113
    __import__('%s.%s' % (DeliverySolver.__name__, class_name))
    return translateString(
      getattr(getattr(DeliverySolver, class_name), class_name).title)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
114 115 116 117

  def getDeliverySolverTranslatedDescription(self, class_name):
    """
    """
118 119 120
    __import__('%s.%s' % (DeliverySolver.__name__, class_name))
    return translateString(
      getattr(getattr(DeliverySolver, class_name), class_name).__doc__)
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150

  # IDivergenceController implementation
  def isDivergent(self, delivery_or_movement=None):
    """
    Returns True if any of the movements provided 
    in delivery_or_movement is divergent

    delivery_or_movement -- a movement, a delivery, 
                            or a list thereof
    """

  def newSolverProcess(self, delivery_or_movement=None):
    """
    Builds a new solver process from the divergence
    analaysis of delivery_or_movement. All movements
    which are not divergence are placed in a Solver
    Decision with no Divergence Tester specified.

    delivery_or_movement -- a movement, a delivery, 
                            or a list thereof
    """
    # Do not create a new solver process if no divergence
    if not self.isDivergent(delivery_or_movement=delivery_or_movement):
      return None

    # We suppose here that delivery_or_movement is a list of
    # delivery lines. Let group decisions in such way
    # that a single decision is created per divergence tester instance
    # and per application level list
    solver_decision_dict = {}
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
151
    for movement in delivery_or_movement:
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
      for simulation_movement in movement.getDeliveryRelatedValueList():
        simulation_movemet_url = simulation_movement.getRelativeUrl()
        for divergence_tester in simulation_movement.getParentValue().getDivergenceTesterValueList():
          application_list = map(lambda x:x.getRelativeUrl(), 
                 self.getSolverDecisionApplicationValueList(simulation_movement, divergence_tester))
          application_list.sort()
          solver_decision_key = (divergence_tester.getRelativeUrl(), application_list)
          movement_dict = solver_decision_dict.setdefaults(solver_decision_key, {})
          movement_dict[simulation_movemet_url] = None

    # Now build the solver process instances based on the previous
    # grouping
    new_solver = self.newContent(portal_type='Solver Process')
    for solver_decision_key, movement_dict in solver_decision_dict.items():
      new_decision = self.newContent(portal_type='Solver Decision')
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
167
      new_decision._setDeliveryList(movement_dict.keys())
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
      new_decision._setSolver(solver_decision_key[0])
      # No need to set application_list or....?

  def getSolverProcessValueList(self, delivery_or_movement=None, validation_state=None):
    """
    Returns the list of solver processes which are
    are in a given state and which apply to delivery_or_movement.
    This method is useful to find applicable solver processes
    for a delivery.

    delivery_or_movement -- a movement, a delivery, 
                            or a list thereof

    validation_state -- a state of a list of states
                        to filter the result
    """

  def getSolverDecisionValueList(self, delivery_or_movement=None, validation_state=None):
    """
    Returns the list of solver decisions which apply
    to a given movement.

    delivery_or_movement -- a movement, a simulation movement, a delivery, 
                            or a list thereof

    validation_state -- a state of a list of states
                        to filter the result
    """

  def getSolverDecisionApplicationValueList(self, movement, divergence_tester=None):
    """
    Returns the list of documents at which a given divergence resolution
    can be resolved at. For example, in most cases, date divergences can
    only be resolved at delivery level whereas quantities are usually
    resolved at cell level.

    The result of this method is a list of ERP5 documents.

    NOTE: renaming probably required. I do not like this name nor the one
    of the interface definition.
    """
    # Short Term Implementation Approach
    return self.SolverTool_getSolverDecisionApplicationValueList(movement, divergence_tester)

    # Alternate short Term Implementation Approach
    return divergence_tester.getTypeBasedMethod('getSolverDecisionApplicationValueList')( 
                                                movement, divergence_tester)

    # Alternate short Term Implementation Approach
    test_property = divergence_tester.getTestedProperty()
    application_value = movement
    while not application_value.hasProperty(test_property):
      application_value = application_value.getParentValue()
    return application_value

    # Mid-term implementation (we suppose movement is a delivery)
    # use delivery builders to find out at which level the given
    # property can be modified
    test_property = divergence_tester.getTestedProperty()
    application_value_level = {}
    for simulation_movement in movement.getDeliveryRelatedValueList():
      business_path = simulation_movement.getCausalityValue()
      for delivery_builder in business_path.getDeliveryBuilderValueList():
        for movement_group in delivery_builder.contentValues(): # filter missing
          if test_property in movement_group.getTestedPropertyList():
            application_value_level[movement_group.getCollectGroupOrder()] = None
    result = []
    # Delivery level
    if 'delivery' in application_value_level:
      result.append(movement.getDeliveryValue())
    # Line level
    if 'line' in application_value_level and not movement.isLine():
      result.append(movement)
    elif 'line' in application_value_level and not movement.isLine():
      result.append(movement.getParentValue())
    # Cell level
    if 'cell' in application_value_level and movement.isCell():
      result.append(movement)
    # Group of lines level (we try to find the most appropriate enclosing group)
    if 'group' in application_value_level:
      application_value = movement
      while not application_value.hasProperty(test_property):
        application_value = application_value.getParentValue()
      if application_value not in result: result.append(application_value)
    # Group of lines level (we try to find the most appropriate enclosing group)
    if 'all_group' in application_value_level:
      application_value = movement
      while not application_value.hasProperty(test_property):
        application_value = application_value.getParentValue()
        if application_value not in result: result.append(application_value)
    return result

    # Longer-term implementation (we suppose movement is a delivery)
    # use delivery builders to find out at which level the given
    # property can be modified
    test_property = divergence_tester.getTestedProperty()
    application_value_level = {}
    for simulation_movement in movement.getDeliveryRelatedValueList():
      business_path = simulation_movement.getCausalityValue()
      for delivery_builder in business_path.getDeliveryBuilderValueList():
        for property_group in delivery_builder.contentValues(portal_type="Property group"):
          if test_property in property_group.getTestedPropertyList():
            application_value_level[property_group.getCollectGroupOrder()] = None
    # etc. same