DeliveryRule.py 11.4 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2
##############################################################################
#
3
# Copyright (c) 2002 - 2009 Nexedi SA 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
#
# WARNING: This program as such is intended to be used by professional
8
# programmers who take the whole responsibility of assessing all potential
Jean-Paul Smets's avatar
Jean-Paul Smets committed
9 10
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
11
# guarantees and support are strongly advised to contract a Free Software
Jean-Paul Smets's avatar
Jean-Paul Smets committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
# 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
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32 33 34
from Products.ERP5.Document.Rule import Rule

class DeliveryRule(Rule):
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
  """
    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')
51
  def expand(self, applied_rule, delivery_movement_type_list=None, **kw):
52 53 54 55 56 57 58 59 60 61 62
    """
    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.
    """
63 64 65 66
    if self._isBPM():
      Rule.expand(self, applied_rule,
          delivery_movement_type_list=delivery_movement_type_list, **kw)
      return
67 68 69
    existing_movement_list = []
    immutable_movement_list = []
    delivery = applied_rule.getDefaultCausalityValue()
70 71
    if delivery_movement_type_list is None:
      delivery_movement_type_list = self.getPortalDeliveryMovementTypeList()
72
    if delivery is not None:
73 74
      delivery_movement_list = delivery.getMovementList(
                                            portal_type=delivery_movement_type_list)
75
      # Check existing movements
76
      for movement in applied_rule.contentValues(portal_type=self.movement_type):
77 78 79 80 81
        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
82
          movement_delivery = movement.getDeliveryValue()
83
          if not movement._isTreeDelivered(ignore_first=1) and \
84 85 86 87 88 89 90 91 92
              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
93
      for deliv_mvt in delivery_movement_list:
94
        sim_mvt = self._getDeliveryRelatedSimulationMovement(deliv_mvt)
95 96 97
        if sim_mvt is None:
          # create a new deliv_mvt
          if deliv_mvt.getParentUid() == deliv_mvt.getExplanationUid():
98
            # We are on a line
99
            new_id = deliv_mvt.getId()
100
          else:
101
            # We are on a cell
102 103
            new_id = "%s_%s" % (deliv_mvt.getParentId(), deliv_mvt.getId())
          # Generate the simulation deliv_mvt
104
          # XXX Hardcoded value
105
          new_sim_mvt = applied_rule.newContent(
106
              portal_type=self.movement_type,
107
              id=new_id,
108
              order_value=deliv_mvt,
109
              order_ratio=1,
110
              delivery_value=deliv_mvt,
111 112
              delivery_ratio=1,
              deliverable=1,
113

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

125 126 127
              resource=deliv_mvt.getResource(),
              variation_category_list=deliv_mvt.getVariationCategoryList(),
              variation_property_dict=deliv_mvt.getVariationPropertyDict(),
128 129 130 131
              aggregate_list=deliv_mvt.getAggregateList(),

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

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

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

                quantity=deliv_mvt.getQuantity(),
                quantity_unit=deliv_mvt.getQuantityUnit(),
168
                incoterm=deliv_mvt.getIncoterm(),
169 170
                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,
    }