TradeModelPath.py 9.34 KB
Newer Older
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
1
# -*- coding: utf-8 -*-
2 3 4 5
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
6
#                    Yusuke Muraoka <yusuke@nexedi.com>
7
#                    Łukasz Nowak <luke@nexedi.com>
8 9
#
# WARNING: This program as such is intended to be used by professional
10
# programmers who take the whole responsibility of assessing all potential
11 12
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
13
# guarantees and support are strongly advised to contract a Free Software
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
# 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.
#
##############################################################################

from AccessControl import ClassSecurityInfo

Łukasz Nowak's avatar
Łukasz Nowak committed
34
from Products.ERP5Type import Permissions, PropertySheet, interfaces
35
from Products.ERP5.Document.Path import Path
Julien Muchembled's avatar
Julien Muchembled committed
36
from Products.ERP5Type.Core.Predicate import Predicate
37
from Products.ERP5.ExplanationCache import _getExplanationCache
38 39 40

import zope.interface

41 42
from zLOG import LOG

43
class TradeModelPath(Path):
44
  """
45
    The TradeModelPath class embeds all information related to
46
    lead times and parties involved at a given phase of a business
47
    process. TradeModelPath are also the most common way to trigger
Jean-Paul Smets's avatar
Jean-Paul Smets committed
48
    the build deliveries from buildable movements.
49

50 51
    The idea is to invoke isBuildable() on the collected simulation
    movements (which are orphan) during build "after select" process
52

53
    Here is the typical code of an alarm in charge of the building process::
54

55
      builder = portal_deliveries.a_delivery_builder
56 57 58 59
      for trade_model_path in builder.getDeliveryBuilderRelatedValueList():
        builder.build(causality_uid=trade_model_path.getUid(),) # Select movements

    WRONG - too slow
60

61
      Pros: global select is possible by not providing a causality_uid
62
      Cons: global select retrieves long lists of orphan movements which
Jean-Paul Smets's avatar
Jean-Paul Smets committed
63 64
            are not yet buildable the build process could be rather
            slow or require activities
65

66
    TODO:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
67 68
    - IArrowBase implementation has too many comments which need to be
      fixed
69 70
    - _getExplanationRelatedMovementValueList may be superfluous. Make
      sure it is really needed
71
  """
72 73
  meta_type = 'ERP5 Trade Model Path'
  portal_type = 'Trade Model Path'
74 75 76 77 78 79 80 81 82 83 84

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

  # Declarative properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.XMLObject
                    , PropertySheet.CategoryCore
                    , PropertySheet.DublinCore
                    , PropertySheet.Folder
85
                    , PropertySheet.Reference
86 87
                    , PropertySheet.Comment
                    , PropertySheet.Arrow
88
                    , PropertySheet.Amount
89
                    , PropertySheet.Chain # XXX-JPS Why N
90
                    , PropertySheet.SortIndex
91
                    , PropertySheet.TradeModelPath
92
                    , PropertySheet.FlowCapacity
93
                    , PropertySheet.Reference
94
                    , PropertySheet.PaymentCondition # XXX-JPS must be renames some day
95 96 97
                    )

  # Declarative interfaces
98
  zope.interface.implements(interfaces.ICategoryAccessProvider,
99
                            interfaces.IArrowBase,
100
                            interfaces.ITradeModelPath,
101
                            interfaces.IPredicate,
102
                            )
103

Jean-Paul Smets's avatar
Jean-Paul Smets committed
104 105 106
  # Helper Methods
  def _getExplanationRelatedSimulationMovementValueList(self, explanation):
    explanation_cache = _getExplanationCache(explanation)
107
    return explanation_cache.getTradeModelPathRelatedSimulationMovementValueList(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
108

109 110
  def _getExplanationRelatedMovementValueList(self, explanation):
    explanation_cache = _getExplanationCache(explanation)
111
    return explanation_cache.getTradeModelPathRelatedMovementValueList(self)
112

113
  # IArrowBase implementation
114 115 116
  security.declareProtected(Permissions.AccessContentsInformation,
                            'getSourceArrowBaseCategoryList')
  def getSourceArrowBaseCategoryList(self):
117 118 119 120
    """
      Returns all categories which are used to define the source
      of this Arrow
    """
121
    # Naive implementation - we must use category groups instead - XXX
122 123 124
    return ('source',
            'source_account',
            'source_administration',
125
            #'source_advice',
126
            'source_carrier',
127
            'source_decision',
128
            'source_function',
Jérome Perrin's avatar
Jérome Perrin committed
129
            'source_funding',
130 131
            'source_payment',
            'source_project',
132
            #'source_referral',
133
            'source_section',
134
            'source_trade',
135 136
            #'source_transport'
            )
137

138 139 140
  security.declareProtected(Permissions.AccessContentsInformation,
                            'getDestinationArrowBaseCategoryList')
  def getDestinationArrowBaseCategoryList(self):
141 142 143 144
    """
      Returns all categories which are used to define the destination
      of this Arrow
    """
145
    # Naive implementation - we must use category groups instead - XXX-JPS review this later
146 147 148
    return ('destination',
            'destination_account',
            'destination_administration',
149 150
            #'destination_advice',
            #'destination_carrier',
151
            'destination_decision',
152
            'destination_function',
Jérome Perrin's avatar
Jérome Perrin committed
153
            'destination_funding',
154 155
            'destination_payment',
            'destination_project',
156
            #'destination_referral',
157
            'destination_section',
158
            'destination_trade',
159 160
            #'destination_transport'
            )
161

162
  # XXX-JPS UNkonwn ?
163 164
  security.declareProtected(Permissions.AccessContentsInformation,
                            'getArrowCategoryDict')
165
  def getArrowCategoryDict(self, context=None, **kw): # XXX-JPS do we need it in API ?
166 167 168 169 170
    # This method returns the dict like
    # {base_category_id:[category value url list], ...}
    # for all Arrow base categories.
    # Each category values are self's category values (if exist) or
    # dynamically computed values (if not exist).
171 172 173 174
    result = {}
    dynamic_category_list = self._getDynamicCategoryList(context)
    for base_category in self.getSourceArrowBaseCategoryList() +\
            self.getDestinationArrowBaseCategoryList():
175 176 177
      category_url_list = self._getAcquiredCategoryMembershipList(
        base_category, **kw)
      if len(category_url_list) == 0 and len(dynamic_category_list) > 0:
178 179 180 181 182 183
        category_url_list = self._filterCategoryList(dynamic_category_list,
                                                     base_category, **kw)
      if len(category_url_list) > 0:
        result[base_category] = category_url_list
    return result

184 185 186
  def _filterCategoryList(self, category_list, category, spec=(),
                          filter=None, portal_type=(), base=0,
                          keep_default=1, checked_permission=None):
187 188 189 190
    """
      XXX - implementation missing
      TBD - look at CategoryTool._buildFilter for inspiration
    """
191 192 193 194
    # basic filtering:
    #  * remove categories which base name is not category
    #  * respect base parameter
    prefix = category + '/'
195
    start_index = 0 if base else len(prefix)
196 197 198
    return [category[start_index:]
            for category in category_list
            if category.startswith(prefix)]
199 200 201

  # Dynamic context based categories
  def _getDynamicCategoryList(self, context):
202 203
    return self._getDynamicSourceCategoryList(context) \
         + self._getDynamicDestinationCategoryList(context)
204 205 206 207 208 209

  def _getDynamicSourceCategoryList(self, context):
    method_id = self.getSourceMethodId()
    if method_id:
      method = getattr(self, method_id)
      return method(context)
210
    return []
211 212 213 214 215 216

  def _getDynamicDestinationCategoryList(self, context):
    method_id = self.getDestinationMethodId()
    if method_id:
      method = getattr(self, method_id)
      return method(context)
217
    return []
218

219 220 221 222 223 224 225 226 227
  security.declareProtected(Permissions.AccessContentsInformation,
                                            'getExpectedQuantity')
  def getExpectedQuantity(self, amount):
    """Returns the new quantity for the provided amount taking
    into account the efficiency or the quantity defined on the business path.
    This is used to implement payment conditions or splitting production
    over multiple path. The total of getExpectedQuantity for all business
    path which are applicable should never exceed the original quantity.
    The implementation of this validation is left to rules.
228
    """
229 230 231 232 233 234
    if self.getQuantity():
      return self.getQuantity()
    elif self.getEfficiency():
      return amount.getQuantity() * self.getEfficiency()
    else:
      return amount.getQuantity()