DeliveryRule.py 11.3 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2
##############################################################################
#
Romain Courteaud's avatar
Romain Courteaud committed
3
# Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
4
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Romain Courteaud's avatar
Romain Courteaud committed
5
#                    Romain Courteaud <romain@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
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
#
# 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.
#
##############################################################################

from AccessControl import ClassSecurityInfo
31
from Products.ERP5Type import Permissions, PropertySheet, Constraint, interfaces
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32 33 34 35 36
from Products.ERP5.Document.Rule import Rule

from zLOG import LOG

class DeliveryRule(Rule):
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
  """
    Delivery Rule object make sure orphaned movements in a Delivery
    (ie. movements which have no explanation in terms of order)
    are part of the simulation process
  """

  # CMF Type Definition
  meta_type = 'ERP5 Delivery Rule'
  portal_type = 'Delivery Rule'

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

  # Simulation workflow
  security.declareProtected(Permissions.ModifyPortalContent, 'expand')
53
  def expand(self, applied_rule, delivery_movement_type_list=None, **kw):
54 55 56 57 58 59 60 61 62 63 64
    """
    Expands the additional Delivery movements to a new simulation tree.
    Expand is only allowed to create or modify simulation movements for
    delivery lines which are not already linked to another simulation
    movement.

    If the movement is not in current state, has no delivered child, and not
    in delivery movements, it can be deleted.
    Else if the movement is not in current state, it can be modified.
    Else, it cannot be modified.
    """
65 66 67 68
    if self._isBPM():
      Rule.expand(self, applied_rule,
          delivery_movement_type_list=delivery_movement_type_list, **kw)
      return
69 70 71
    existing_movement_list = []
    immutable_movement_list = []
    delivery = applied_rule.getDefaultCausalityValue()
72 73
    if delivery_movement_type_list is None:
      delivery_movement_type_list = self.getPortalDeliveryMovementTypeList()
74
    if delivery is not None:
75 76
      delivery_movement_list = delivery.getMovementList(
                                            portal_type=delivery_movement_type_list)
77
      # Check existing movements
78
      for movement in applied_rule.contentValues(portal_type=self.movement_type):
79 80 81 82 83
        if movement.getLastExpandSimulationState() not in \
          self.getPortalCurrentInventoryStateList():
          # XXX: This condition is quick and dirty hack - knowing if Simulation
          #      Movement is frozen shall not be ever hardcoded, this is BPM
          #      configuration
84
          movement_delivery = movement.getDeliveryValue()
85
          if not movement._isTreeDelivered(ignore_first=1) and \
86 87 88 89 90 91 92 93 94
              movement_delivery not in delivery_movement_list:
            applied_rule._delObject(movement.getId())
          else:
            existing_movement_list.append(movement)
        else:
          existing_movement_list.append(movement)
          immutable_movement_list.append(movement)

      # Create or modify movements
95
      for deliv_mvt in delivery_movement_list:
96
        sim_mvt = self._getDeliveryRelatedSimulationMovement(deliv_mvt)
97 98 99
        if sim_mvt is None:
          # create a new deliv_mvt
          if deliv_mvt.getParentUid() == deliv_mvt.getExplanationUid():
100
            # We are on a line
101
            new_id = deliv_mvt.getId()
102
          else:
103
            # We are on a cell
104 105
            new_id = "%s_%s" % (deliv_mvt.getParentId(), deliv_mvt.getId())
          # Generate the simulation deliv_mvt
106
          # XXX Hardcoded value
107
          new_sim_mvt = applied_rule.newContent(
108
              portal_type=self.movement_type,
109
              id=new_id,
110
              order_value=deliv_mvt,
111
              order_ratio=1,
112
              delivery_value=deliv_mvt,
113 114
              delivery_ratio=1,
              deliverable=1,
115

116 117
              source=deliv_mvt.getSource(),
              source_section=deliv_mvt.getSourceSection(),
118
              source_function=deliv_mvt.getSourceFunction(),
119
              source_account=deliv_mvt.getSourceAccount(),
120 121
              destination=deliv_mvt.getDestination(),
              destination_section=deliv_mvt.getDestinationSection(),
122
              destination_function=deliv_mvt.getDestinationFunction(),
123
              destination_account=deliv_mvt.getDestinationAccount(),
124 125 126
              start_date=deliv_mvt.getStartDate(),
              stop_date=deliv_mvt.getStopDate(),

127 128 129
              resource=deliv_mvt.getResource(),
              variation_category_list=deliv_mvt.getVariationCategoryList(),
              variation_property_dict=deliv_mvt.getVariationPropertyDict(),
130 131 132 133 134 135
              aggregate_list=deliv_mvt.getAggregateList(),

              quantity=deliv_mvt.getQuantity(),
              quantity_unit=deliv_mvt.getQuantityUnit(),
              price=deliv_mvt.getPrice(),
              price_currency=deliv_mvt.getPriceCurrency(),
136 137
              base_contribution_list=deliv_mvt.getBaseContributionList(),
              base_application_list=deliv_mvt.getBaseApplicationList(),
138
              description=deliv_mvt.getDescription(),
139
          )
140 141
          if deliv_mvt.hasTitle():
            new_sim_mvt.setTitle(deliv_mvt.getTitle())
142 143
        elif sim_mvt in existing_movement_list:
          if sim_mvt not in immutable_movement_list:
144
            # modification allowed
145
            # XXX Hardcoded value
146 147
            sim_mvt.edit(
                delivery_value=deliv_mvt,
148 149
                delivery_ratio=1,
                deliverable=1,
150

151 152
                source=deliv_mvt.getSource(),
                source_section=deliv_mvt.getSourceSection(),
153
                source_function=deliv_mvt.getSourceFunction(),
154
                source_account=deliv_mvt.getSourceAccount(),
155 156
                destination=deliv_mvt.getDestination(),
                destination_section=deliv_mvt.getDestinationSection(),
157
                destination_function=deliv_mvt.getDestinationFunction(),
158
                destination_account=deliv_mvt.getDestinationAccount(),
159 160 161
                start_date=deliv_mvt.getStartDate(),
                stop_date=deliv_mvt.getStopDate(),

162 163 164
                resource=deliv_mvt.getResource(),
                variation_category_list=deliv_mvt.getVariationCategoryList(),
                variation_property_dict=deliv_mvt.getVariationPropertyDict(),
165 166 167 168 169 170
                aggregate_list=deliv_mvt.getAggregateList(),

                quantity=deliv_mvt.getQuantity(),
                quantity_unit=deliv_mvt.getQuantityUnit(),
                price=deliv_mvt.getPrice(),
                price_currency=deliv_mvt.getPriceCurrency(),
171 172
                base_contribution_list=deliv_mvt.getBaseContributionList(),
                base_application_list=deliv_mvt.getBaseApplicationList(),
173
                description=deliv_mvt.getDescription(),
174
                force_update=1)
175 176
            if deliv_mvt.hasTitle():
              sim_mvt.setTitle(deliv_mvt.getTitle())
177 178 179 180 181 182 183 184 185
          else:
            # modification disallowed, must compensate
            pass

      # Now we can set the last expand simulation state to the current state
      applied_rule.setLastExpandSimulationState(delivery.getSimulationState())
    # Pass to base class
    Rule.expand(self, applied_rule, **kw)

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
  def _getDeliveryRelatedSimulationMovement(self, delivery_movement):
    """Helper method to get the delivery related simulation movement.
    This method is more robust than simply calling getDeliveryRelatedValue
    which will not work if simulation movements are not indexed.
    """
    simulation_movement = delivery_movement.getDeliveryRelatedValue()
    if simulation_movement is not None:
      return simulation_movement
    # simulation movement was not found, maybe simply because it's not indexed
    # yet. We'll look in the simulation tree and try to find it anyway before
    # creating another simulation movement.
    # Try to find the one from trade model rule, which is the most common case
    # where we may expand again before indexation of simulation movements is
    # finished.
    delivery = delivery_movement.getExplanationValue()
    for movement in delivery.getMovementList():
      related_simulation_movement = movement.getDeliveryRelatedValue()
      if related_simulation_movement is not None:
        for applied_rule in related_simulation_movement.contentValues():
          for simulation_movement in applied_rule.contentValues():
            if simulation_movement.getDeliveryValue() == delivery_movement:
              return simulation_movement
    return None

210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
  security.declareProtected(Permissions.ModifyPortalContent, 'solve')
  def solve(self, applied_rule, solution_list):
    """
      Solve inconsistency according to a certain number of solutions
      templates. This updates the

      -> new status -> solved

      This applies a solution to an applied rule. Once
      the solution is applied, the parent movement is checked.
      If it does not diverge, the rule is reexpanded. If not,
      diverge is called on the parent movement.
    """

  security.declareProtected(Permissions.ModifyPortalContent, 'diverge')
  def diverge(self, applied_rule):
    """
      -> new status -> diverged
Jean-Paul Smets's avatar
Jean-Paul Smets committed
228

229 230 231 232 233 234
      This basically sets the rule to "diverged"
      and blocks expansion process
    """

  # Solvers
  security.declareProtected(Permissions.AccessContentsInformation, 'isStable')
235
  def isStable(self, applied_rule):
236 237 238 239 240 241 242 243 244 245
    """
    Checks that the applied_rule is stable
    """
    return 0

  security.declareProtected(Permissions.AccessContentsInformation, 'getSolverList')
  def getSolverList(self, applied_rule):
    """
      Returns a list Divergence solvers
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
246

247 248 249 250 251 252
  # Deliverability / orderability
  def isOrderable(self, movement):
    return 1

  def isDeliverable(self, movement):
    if movement.getSimulationState() in movement.getPortalDraftOrderStateList():
Jean-Paul Smets's avatar
Jean-Paul Smets committed
253
      return 0
254
    return 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
255

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
  def _getInputMovementList(self, applied_rule):
    """Return list of movements from delivery"""
    delivery = applied_rule.getDefaultCausalityValue()
    if delivery is not None:
      return delivery.getMovementList(
                     portal_type=delivery.getPortalDeliveryMovementTypeList())
    return []

  def _getExpandablePropertyUpdateDict(self, applied_rule, movement,
      business_path, current_property_dict):
    """Delivery specific update dict"""
    return {
      'order_list': [movement.getRelativeUrl()],
      'delivery_list': [movement.getRelativeUrl()],
      'deliverable': 1,
    }