CopyToTarget.py 7.1 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.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
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 TargetSolver import TargetSolver
31
from Products.ERP5Type.DateUtils import createDateTimeFromMillis
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32 33 34

class CopyToTarget(TargetSolver):
  """
35 36 37 38 39 40 41 42 43 44
    This solver calculates the ratio between the new (delivery) and old
    (simulation) quantity and applies this ratio to the simulation movement
    and to its parent, until a stable one is found

    XXX: This solver's name is not good, and it tries too many things.
    Once the new isDivergent engine is implemented, this solver can be
    splitted in smaller ones (one for profit and loss, one for backtracking)
    
    Backtracking alone is not enough to solve completely, it must be used with
    another solver (profit and loss, or creating a compensation branch ...)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
45
  """
46
  def _generateValueDeltaDict(self, simulation_movement):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
47
    """
48 49
      Get interesting values
      XXX: better description is possible. But is it needed ?
Jean-Paul Smets's avatar
Jean-Paul Smets committed
50
    """
51
    # Get interesting value
52 53 54 55 56 57 58
    old_quantity = simulation_movement.getQuantity()
    old_start_date = simulation_movement.getStartDate()
    old_stop_date = simulation_movement.getStopDate()
    new_quantity = simulation_movement.getDeliveryQuantity() * \
                   simulation_movement.getDeliveryRatio()
    new_start_date = simulation_movement.getDeliveryStartDateList()[0]
    new_stop_date = simulation_movement.getDeliveryStopDateList()[0]
59
    # Calculate delta
60
    quantity_ratio = 0
61
    if old_quantity not in (None,0.0): # XXX: What if quantity happens to be an integer ?
62
      quantity_ratio = new_quantity / old_quantity
63 64
    start_date_delta = 0
    stop_date_delta = 0
65
    # get the date delta in milliseconds, to prevent rounding issues
66
    if new_start_date is not None and old_start_date is not None:
67
      start_date_delta = new_start_date.millis() - old_start_date.millis()
68
    if new_stop_date is not None and old_stop_date is not None:
69
      stop_date_delta = new_stop_date.millis() - old_stop_date.millis()
70 71 72 73 74 75
    return {
      'quantity_ratio': quantity_ratio,
      'start_date_delta': start_date_delta,
      'stop_date_delta': stop_date_delta,
    }

76
  def solve(self, simulation_movement):
77
    """
78
      Adopt values as new target
79
    """
80
    value_dict = self._generateValueDeltaDict(simulation_movement)
81
    # Modify recursively simulation movement
82
    self._recursivelySolve(simulation_movement, **value_dict)
83

84
  def _generateValueDict(self, simulation_movement, quantity_ratio=1, 
85 86
                         start_date_delta=0, stop_date_delta=0,
                         **value_delta_dict):
87
    """
88
      Generate values to save on simulation movement.
89
    """
90
    value_dict = {}
91
    # Modify quantity, start_date, stop_date
92
    start_date = simulation_movement.getStartDate()
93
    if start_date is not None:
94
      value_dict['start_date'] = createDateTimeFromMillis(start_date.millis() + start_date_delta)
95
    stop_date = simulation_movement.getStopDate()
96
    if stop_date is not None:
97
      value_dict['stop_date'] = createDateTimeFromMillis(stop_date.millis() + stop_date_delta)
98
    value_dict['quantity'] = simulation_movement.getQuantity() * quantity_ratio
99 100
    return value_dict

101
  def _getParentParameters(self, simulation_movement, 
102 103
                           **value_delta_dict):
    """
104
      Get parent movement, and its value delta dict.
105
    """
106 107 108 109
    #XXX max_allowed_delta is the maximum number of days we want not to
    # account as a divergence. It should be configurable through a Rule
    max_allowed_delta = 15

110
    applied_rule = simulation_movement.getParentValue()
111
    parent_movement = applied_rule.getParentValue()
112 113
    if parent_movement.getPortalType() != "Simulation Movement":
      parent_movement = None
114 115 116 117 118 119 120

    for date_delta in ('start_date_delta', 'stop_date_delta'):
      if date_delta in value_delta_dict.keys():
        if abs(value_delta_dict[date_delta]) <= \
            applied_rule.getProperty('max_allowed_delta', max_allowed_delta):
          value_delta_dict.pop(date_delta)
        
121 122
    return parent_movement, value_delta_dict

123
  def _recursivelySolve(self, simulation_movement, is_last_movement=1, **value_delta_dict):
124
    """
125 126
      Update value of the current simulation movement, and update
      his parent movement.
127
    """
128
    value_dict = self._generateValueDict(simulation_movement, **value_delta_dict)
129

130
    parent_movement, parent_value_delta_dict = \
131
                self._getParentParameters(simulation_movement, **value_delta_dict)
Rafael Monnerat's avatar
Rafael Monnerat committed
132 133
    
    #if parent is not None and parent_movement.isFrozen():
134 135
      # If backtraxcking is not possible, we have to make sure that the
      # divergence is solved locally by using profit and loss
Rafael Monnerat's avatar
Rafael Monnerat committed
136 137 138 139 140 141 142
      # sm_quantity = simulation_movement.getQuantity()
      # delivery_quantity = \
      #      simulation_movement.getDeliveryValue().getQuantity()
      #  simulation_movement.edit(
      #    profit_quantity=sm_quantity - delivery_quantity)
    #else:
    if is_last_movement:
143 144
        delivery_quantity = \
            simulation_movement.getDeliveryValue().getQuantity()
145 146
        delivery_ratio = simulation_movement.getDeliveryRatio()
        simulation_movement.setDeliveryError(delivery_quantity * delivery_ratio -
147
            value_dict['quantity'])
Rafael Monnerat's avatar
Rafael Monnerat committed
148 149 150 151 152 153 154 155 156 157 158 159
    
    delivery = simulation_movement.getDeliveryValue()
    
    # XXX Hardcoded Set 
    simulation_movement.setDestination(delivery.getDestination())
    simulation_movement.setSource(delivery.getSource())
    simulation_movement.setDestinationSection(delivery.getDestinationSection())
    simulation_movement.setSourceSection(delivery.getSourceSection())
		
    simulation_movement.edit(**value_dict)
      
    if parent_movement is not None and not parent_movement.isFrozen():
160 161 162
        # backtrack to the parent movement only if it is not frozen
        self._recursivelySolve(parent_movement, is_last_movement=0,
            **parent_value_delta_dict)