document.erp5.TradeModelPath.py 10 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 erp5.component.document.Path import Path
36
from erp5.component.module.ExplanationCache import _getExplanationCache
37
from erp5.component.interface.ITradeModelPath import ITradeModelPath
38
from erp5.component.interface.IArrowBase import IArrowBase
39 40 41

import zope.interface

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

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

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

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

    WRONG - too slow
59

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

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

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

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

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

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

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

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

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

161
  # XXX-JPS UNkonwn ?
162 163
  security.declareProtected(Permissions.AccessContentsInformation,
                            'getArrowCategoryDict')
164
  def getArrowCategoryDict(self, context=None, **kw):  # XXX-JPS do we need it in API ?
165 166 167 168
    # 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
169 170 171 172
    # dynamically computed values (if not exist), but on trade model path
    # we can also configure if we want the script to define **all** categories,
    # which means that if script does not return some categories they are not
    # set on the resulting movement, even if they were set on the input movement.
173 174
    result = {}
    dynamic_category_list = self._getDynamicCategoryList(context)
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
    for base_category_list, script_replace_category in (
        (self.getSourceArrowBaseCategoryList(),
         self.getSourceMethodReplaceCategory()),
        (self.getDestinationArrowBaseCategoryList(),
         self.getDestinationMethodReplaceCategory()),
    ):
      for base_category in base_category_list:
        if script_replace_category and context is not None:
          result[base_category] = self._filterCategoryList(
              dynamic_category_list, base_category, **kw)
        else:
          category_url_list = super(TradeModelPath, self)._getAcquiredCategoryMembershipList(
              base_category, **kw)
          if context is not None and not category_url_list:
            category_url_list = self._filterCategoryList(
                dynamic_category_list, base_category, **kw)
          if category_url_list:
            result[base_category] = category_url_list
193 194
    return result

195 196 197 198 199 200 201 202 203 204 205
  def _filterCategoryList(
      self,
      category_list,
      category,
      spec=(),
      filter=None, # pylint: disable=redefined-builtin
      portal_type=(),
      base=0,
      keep_default=1,
      checked_permission=None,
  ):
206 207 208 209
    """
      XXX - implementation missing
      TBD - look at CategoryTool._buildFilter for inspiration
    """
210 211 212 213
    # basic filtering:
    #  * remove categories which base name is not category
    #  * respect base parameter
    prefix = category + '/'
214
    start_index = 0 if base else len(prefix)
215 216 217
    return [category[start_index:]
            for category in category_list
            if category.startswith(prefix)]
218 219 220

  # Dynamic context based categories
  def _getDynamicCategoryList(self, context):
221 222
    return self._getDynamicSourceCategoryList(context) \
         + self._getDynamicDestinationCategoryList(context)
223 224 225 226 227 228

  def _getDynamicSourceCategoryList(self, context):
    method_id = self.getSourceMethodId()
    if method_id:
      method = getattr(self, method_id)
      return method(context)
229
    return []
230 231 232 233 234 235

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

238 239 240 241 242 243 244 245 246
  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.
247
    """
248 249 250 251 252 253
    if self.getQuantity():
      return self.getQuantity()
    elif self.getEfficiency():
      return amount.getQuantity() * self.getEfficiency()
    else:
      return amount.getQuantity()