Commit 7cf0d0d5 authored by Jérome Perrin's avatar Jérome Perrin

remove all old style solvers (that might still be used, this is to try a test run)

parent a9f243e7
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# 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 DeliverySolver import DeliverySolver
class Copy(DeliverySolver):
"""
Copy target_values of simulation movements into
values. This solver should only be proposed
whenever target of Delivery and aggregated target
of simulation movements match. It allows to accept
a suggestion of a new expand.
No ASolver will be required afterwards
"""
def solveMovement(self, movement):
"""
Solves a movement
"""
movement.setQuantity(movement.getSimulationQuantity())
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# 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.
#
##############################################################################
class DeliverySolver:
"""
Delivery solver is used to have control of how quantity property is
accepted into simulation.
Delivery solver is only used for quantity property.
Delivery solver is working on movement's quantity and related simulation
movements' quantities.
Can be used to:
* distribute
* queue (FIFO, LIFO, ...)
* etc
"""
def __init__(self, simulation_tool=None, **kw):
"""
Initialisation
"""
self.simulation_tool = simulation_tool
self.__dict__.update(kw)
def solveMovement(self, movement):
"""
Solves a delivery movement
"""
raise NotImplementedError
def solveDelivery(self, delivery):
"""
Solves the delivery itself
"""
result_list = []
for movement in delivery.getMovementList():
result_list.append(self.solveMovement(movement))
return result_list
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2008-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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 DeliverySolver import DeliverySolver
class Distribute(DeliverySolver):
"""
Update new values equally on Simulation Movements.
"""
def solveDelivery(self, delivery):
for movement in delivery.getMovementList():
self.solveMovement(movement)
def solveMovement(self, movement):
"""
Solve a delivery by reducing / increasing each simulation movement
it relates to
"""
simulation_movement_list = movement.getDeliveryRelatedValueList()
simulation_quantity = 0.
for simulation_movement in simulation_movement_list:
quantity = simulation_movement.getCorrectedQuantity()
simulation_quantity += quantity
if simulation_quantity != 0:
for simulation_movement in simulation_movement_list:
simulation_movement.edit(delivery_ratio=simulation_movement.getCorrectedQuantity() / simulation_quantity)
else:
if len(simulation_movement_list) > 0:
delivery_ratio = 1./len(simulation_movement_list)
for simulation_movement in simulation_movement_list:
simulation_movement.edit(delivery_ratio=delivery_ratio)
##############################################################################
#
# Copyright (c) 2008,2010 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
##############################################################################
from TargetSolver import TargetSolver
class Copy(TargetSolver):
"""
This solver will copy properties and calculate quantities for
specified divergence list.
"""
def solveMovement(self, movement):
"""
Solves a movement.
"""
movement_relative_url = movement.getRelativeUrl()
for divergence in self.divergence_list:
if movement_relative_url == divergence.getProperty(
'object_relative_url'):
self._acceptDecision(divergence)
def _acceptDecision(self, divergence):
"""
Accept decision according to movement group
"""
scope = divergence.getProperty('divergence_scope')
simulation_movement = divergence.getProperty('simulation_movement')
delivery = simulation_movement.getDeliveryValue()
value_dict = {}
quantity_ratio = None
if scope == 'quantity':
property_id = 'quantity'
delivery_quantity = delivery.getQuantity()
delivery_ratio = simulation_movement.getDeliveryRatio()
old_quantity = simulation_movement.getQuantity(0.0)
new_quantity = delivery_quantity * delivery_ratio
quantity_ratio = 0
if old_quantity != 0.0:
quantity_ratio = new_quantity / old_quantity
quantity = old_quantity * quantity_ratio
quantity_error = delivery_quantity * delivery_ratio - quantity
value_dict['delivery_error'] = quantity_error
value_dict['quantity'] = quantity
elif scope == 'category':
property_id = divergence.getProperty('tested_property')
new_value_list = delivery.getPropertyList(property_id)
# variation_category should be edited as variation_category_list
if property_id == 'variation_category':
property_id = 'variation_category_list'
value_dict[property_id] = new_value_list
else: # otherwise we assume that scope is 'property'
property_id = divergence.getProperty('tested_property')
new_value = delivery.getProperty(property_id)
value_dict[property_id] = new_value
if not simulation_movement.isPropertyRecorded(property_id):
simulation_movement.recordProperty(property_id)
simulation_movement.edit(**value_dict)
# XXX: would it be safe to expand by activity ?
simulation_movement.expand('immediate')
##############################################################################
#
# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
##############################################################################
from TargetSolver import TargetSolver
class CopyAndPropagate(TargetSolver):
"""
This solver will copy properties and calculate quantities for
specified divergence list.
"""
def solveMovement(self, movement):
"""
Solves a movement.
"""
movement_relative_url = movement.getRelativeUrl()
for divergence in self.divergence_list:
if movement_relative_url == divergence.getProperty(
'object_relative_url'):
self._acceptDecision(divergence)
def _acceptDecision(self, divergence):
"""
Accept decision according to movement group
"""
scope = divergence.getProperty('divergence_scope')
simulation_movement = divergence.getProperty('simulation_movement')
delivery = simulation_movement.getDeliveryValue()
value_dict = {}
quantity_ratio = None
if scope == 'quantity':
property_id = 'quantity'
new_quantity = simulation_movement.getDeliveryQuantity() * \
simulation_movement.getDeliveryRatio()
old_quantity = simulation_movement.getQuantity()
quantity_ratio = 0
if old_quantity not in (None, 0.0):
quantity_ratio = new_quantity / old_quantity
elif scope == 'category':
property_id = divergence.getProperty('tested_property')
new_value_list = delivery.getPropertyList(property_id)
# variation_category should be edited as variation_category_list
if property_id == 'variation_category':
property_id = 'variation_category_list'
value_dict[property_id] = new_value_list
else: # otherwise we assume that scope is 'property'
property_id = divergence.getProperty('tested_property')
new_value = delivery.getProperty(property_id)
value_dict[property_id] = new_value
self._solveRecursively(simulation_movement,
quantity_ratio=quantity_ratio,
value_dict=value_dict,
property_id=property_id)
def _solveRecursively(self, simulation_movement, is_last_movement=1,
quantity_ratio=None, value_dict=None,
property_id=None):
"""
Update value of the current simulation movement, and update
his parent movement.
"""
delivery = simulation_movement.getDeliveryValue()
if is_last_movement and quantity_ratio is not None:
delivery_quantity = delivery.getQuantity()
delivery_ratio = simulation_movement.getDeliveryRatio()
quantity = simulation_movement.getQuantity() * quantity_ratio
quantity_error = delivery_quantity * delivery_ratio - quantity
value_dict['delivery_error'] = quantity_error
value_dict['quantity'] = quantity
applied_rule = simulation_movement.getParentValue()
parent_movement = applied_rule.getParentValue()
if parent_movement.getPortalType() == 'Simulation Movement' and \
not parent_movement.isFrozen():
# backtrack to the parent movement while it is not frozen
self._solveRecursively(parent_movement, is_last_movement=0,
quantity_ratio=quantity_ratio,
value_dict=value_dict,
property_id=property_id)
else:
if not simulation_movement.isPropertyRecorded(property_id):
simulation_movement.recordProperty(property_id)
simulation_movement.edit(**value_dict)
# XXX would it be safe to expand by activity ?
simulation_movement.expand('immediate')
##############################################################################
#
# Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
#
# 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
from Products.ERP5Type.DateUtils import createDateTimeFromMillis
class CopyToTarget(TargetSolver):
"""
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 ...)
"""
def _generateValueDeltaDict(self, simulation_movement):
"""
Get interesting values
XXX: better description is possible. But is it needed ?
"""
# Get interesting value
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]
# Calculate delta
quantity_ratio = 0
if old_quantity not in (None,0.0): # XXX: What if quantity happens to be an integer ?
quantity_ratio = new_quantity / old_quantity
start_date_delta = 0
stop_date_delta = 0
# get the date delta in milliseconds, to prevent rounding issues
if new_start_date is not None and old_start_date is not None:
start_date_delta = new_start_date.millis() - old_start_date.millis()
if new_stop_date is not None and old_stop_date is not None:
stop_date_delta = new_stop_date.millis() - old_stop_date.millis()
return {
'quantity_ratio': quantity_ratio,
'start_date_delta': start_date_delta,
'stop_date_delta': stop_date_delta,
}
def solve(self, simulation_movement):
"""
Adopt values as new target
"""
value_dict = self._generateValueDeltaDict(simulation_movement)
# Modify recursively simulation movement
self._recursivelySolve(simulation_movement, **value_dict)
def _generateValueDict(self, simulation_movement, quantity_ratio=1,
start_date_delta=0, stop_date_delta=0,
**value_delta_dict):
"""
Generate values to save on simulation movement.
"""
value_dict = {}
# Modify quantity, start_date, stop_date
start_date = simulation_movement.getStartDate()
if start_date is not None:
value_dict['start_date'] = createDateTimeFromMillis(start_date.millis() + start_date_delta)
stop_date = simulation_movement.getStopDate()
if stop_date is not None:
value_dict['stop_date'] = createDateTimeFromMillis(stop_date.millis() + stop_date_delta)
value_dict['quantity'] = simulation_movement.getQuantity() * quantity_ratio
return value_dict
def _getParentParameters(self, simulation_movement,
**value_delta_dict):
"""
Get parent movement, and its value delta dict.
"""
#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
applied_rule = simulation_movement.getParentValue()
parent_movement = applied_rule.getParentValue()
if parent_movement.getPortalType() != "Simulation Movement":
parent_movement = None
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)
return parent_movement, value_delta_dict
def _recursivelySolve(self, simulation_movement, is_last_movement=1, **value_delta_dict):
"""
Update value of the current simulation movement, and update
his parent movement.
"""
value_dict = self._generateValueDict(simulation_movement, **value_delta_dict)
parent_movement, parent_value_delta_dict = \
self._getParentParameters(simulation_movement, **value_delta_dict)
#if parent is not None and parent_movement.isFrozen():
# If backtraxcking is not possible, we have to make sure that the
# divergence is solved locally by using profit and loss
# 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:
delivery_quantity = \
simulation_movement.getDeliveryValue().getQuantity()
delivery_ratio = simulation_movement.getDeliveryRatio()
simulation_movement.setDeliveryError(delivery_quantity * delivery_ratio -
value_dict['quantity'])
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():
# backtrack to the parent movement only if it is not frozen
self._recursivelySolve(parent_movement, is_last_movement=0,
**parent_value_delta_dict)
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# 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 Products.ERP5.Tool.SimulationTool import registerTargetSolver
from TargetSolver import TargetSolver
class Defer(TargetSolver):
"""
Split and defer simulation movement.
Many 'deliverable movements' are created in the Simulation and
may need to be delivered later. Solver accumulates such movements
in the solving process and creates a new delivery
"""
def solve(self, movement):
"""
Split a movement and accumulate
"""
def close(self):
"""
After resolution has taken place, create a new delivery
with deliverable split movements.
"""
registerTargetSolver(Defer)
##############################################################################
#
# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees 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 CopyToTarget import CopyToTarget
class ProfitAndLoss(CopyToTarget):
"""
Profit and Loss target rule.
Many 'deliverable movements' are created in the Simulation and
either source or destination is set to a ProfitAndLoss, depending
on the parent applied rule.
"""
def solve(self, simulation_movement):
"""
Movement difference as a profit (ie. a quantity coming from nowhere)
Accumulate into delivered movement
"""
delivery_line = simulation_movement.getDeliveryValue()
delivery_line_quantity = delivery_line.getQuantity()
if delivery_line_quantity is not None:
target_quantity = delivery_line_quantity * simulation_movement.getDeliveryRatio()
added_quantity = simulation_movement.getQuantity() - target_quantity
simulation_movement.edit(profit_quantity=added_quantity)
delivery_line.activate(
after_path_and_method_id=(
simulation_movement.getPath(),
['immediateReindexObject', 'recursiveImmediateReindexObject']
)
).edit()
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# 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 Products.ERP5.Tool.SimulationTool import registerTargetSolver
from TargetSolver import TargetSolver
class Redirect(TargetSolver):
"""
Redirects all simulation movements to new target
"""
def solve(self, simulation_movement, new_target):
"""
Updates all sources and destinations to new values defined
in self by mapping
source -> target_source
destination -> target_destination
source_section -> target_source_section
destination_section -> target_destination_section
"""
for p in ('source', 'destination'):
for q in ('source', 'destination'):
if simulation_movement.getProperty(p) == getattr(self, q):
self.setProperty(p, getattr(self, 'target_%s' % q))
break
for p in ('source_section', 'destination_section'):
for q in ('source_section', 'destination_section'):
if simulation_movement.getProperty(p) == getattr(self, q):
self.setProperty(p, getattr(self, 'target_%s' % q))
break
delivery_value = simulation_movement.getDeliveryValue() # Get delivery movement
if delivery_value is not None:
delivery_value = delivery_value.getDeliveryValue() # Get root delivery
if delivery_value is not None:
delivery_value.activate(after_method_id = 'propagateResourceToSimulation').updateFromSimulation()
registerTargetSolver(Redirect)
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# 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 Products.ERP5.Tool.SimulationTool import registerTargetSolver
from TargetSolver import TargetSolver
class Reduce(TargetSolver):
"""
Reduce target quantities if needed
"""
def solve(self, movement, new_target):
"""
Solves reverse causality
"""
# We should access the previous_target globaly
#previous_target = self.getPreviousTarget(movement)
#registerTargetSolver(Reduce)
##############################################################################
#
# Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
#
# 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 CopyToTarget import CopyToTarget
class ResourceBackpropagation(CopyToTarget):
"""
This solver is based on CopyToTarget, but it also backpropagates resource
related properties and categories
"""
def _generateValueDeltaDict(self, simulation_movement):
"""
Get interesting values
XXX: better description is possible. But is it needed ?
"""
value_delta_dict = CopyToTarget._generateValueDeltaDict(
self, simulation_movement)
value_delta_dict.update(
{
'resource_list' :
simulation_movement.getDeliveryValue().getResourceList(),
'variation_category_list':
simulation_movement.getDeliveryValue().getVariationCategoryList(),
'variation_property_dict':
simulation_movement.getDeliveryValue().getVariationPropertyDict(),
})
return value_delta_dict
def _generateValueDict(self, simulation_movement, quantity_ratio=1,
start_date_delta=0, stop_date_delta=0,
resource_list=[],
variation_category_list=[],
variation_property_dict={},
**value_delta_dict):
"""
Generate values to save on simulation movement.
"""
value_dict = CopyToTarget._generateValueDict(
self, simulation_movement, quantity_ratio=quantity_ratio,
start_date_delta=start_date_delta, stop_date_delta=stop_date_delta,
resource_list=resource_list,
variation_category_list=variation_category_list,
variation_property_dict=variation_property_dict, **value_delta_dict)
# Modify resource etc.
if resource_list:
value_dict['resource_list'] = resource_list
if variation_category_list:
value_dict['variation_category_list'] = variation_category_list
if variation_property_dict:
value_dict['variation_property_dict'] = variation_property_dict
return value_dict
##############################################################################
#
# Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
#
# 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 Products.ERP5.MovementCollectionDiff import _getPropertyAndCategoryList
from Products.ERP5Type.Globals import PersistentMapping
from CopyToTarget import CopyToTarget
from Acquisition import aq_base
class SplitAndDefer(CopyToTarget):
"""
Split and defer simulation movement.
Many 'deliverable movements' are created in the Simulation and
may need to be delivered later. Solver accumulates such movements
in the solving process and creates a new delivery
This only works when some movements can not be delivered
(excessive qty is not covered)
"""
def solve(self, simulation_movement):
"""
Split a simulation movement and accumulate
"""
movement_quantity = simulation_movement.getQuantity()
delivery_quantity = simulation_movement.getDeliveryQuantity()
new_movement_quantity = delivery_quantity * simulation_movement.getDeliveryRatio()
applied_rule = simulation_movement.getParentValue()
rule = applied_rule.getSpecialiseValue()
# When accounting, the debit price is expressed by a minus quantity.
# Thus, we must take into account the both minus and plus quantity.
if ((movement_quantity < new_movement_quantity <= 0) or
(movement_quantity > new_movement_quantity >= 0)):
split_index = 0
new_id = "%s_split_%s" % (simulation_movement.getId(), split_index)
while getattr(aq_base(applied_rule), new_id, None) is not None:
split_index += 1
new_id = "%s_split_%s" % (simulation_movement.getId(), split_index)
# Adopt different dates for deferred movements
movement_dict = _getPropertyAndCategoryList(simulation_movement)
# new properties
movement_dict.update(
portal_type="Simulation Movement",
id=new_id,
quantity=movement_quantity - new_movement_quantity,
activate_kw=self.activate_kw,
delivery=None,
**self.additional_parameters
)
new_movement = applied_rule.newContent(**movement_dict)
# Dirty code until IPropertyRecordable is revised.
# Merge original simulation movement recorded property to new one.
recorded_property_dict = simulation_movement._getRecordedPropertyDict(None)
if recorded_property_dict:
new_movement_recorded_property_dict = new_movement._getRecordedPropertyDict(None)
if new_movement_recorded_property_dict is None:
new_movement_recorded_property_dict = new_movement._recorded_property_dict = PersistentMapping()
new_movement_recorded_property_dict.update(recorded_property_dict)
# record zero quantity property, because this was originally zero.
# without this, splitanddefer after accept decision does not work
# properly.
current_quantity = new_movement.getQuantity()
new_movement.setQuantity(0)
new_movement.recordProperty('quantity')
new_movement.setQuantity(current_quantity)
start_date = getattr(self, 'start_date', None)
if start_date is not None:
new_movement.recordProperty('start_date')
new_movement.edit(start_date=start_date)
stop_date = getattr(self, 'stop_date', None)
if stop_date is not None:
new_movement.recordProperty('stop_date')
new_movement.edit(stop_date=stop_date)
new_movement.expand(activate_kw=self.additional_parameters)
# adopt new quantity on original simulation movement
simulation_movement.edit(quantity=new_movement_quantity)
simulation_movement.setDefaultActivateParameterDict(self.activate_kw)
simulation_movement.expand(activate_kw=self.additional_parameters)
# SplitAndDefer solves the divergence at the current level, no need to
# backtrack.
##############################################################################
#
# Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
#
# 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 CopyToTarget import CopyToTarget
from zLOG import LOG
class SplitQuantity(CopyToTarget):
"""
Split a simulation movement based on a resource quantity.
"""
def solve(self, simulation_movement):
"""
From simulation_movement, generate new_movement containing self.quantity
resources, of start_date self.start_date and stop_date self.stop_date.
movement.quantity is updated
XXX incomplete docstring
"""
split_index = 0
new_id = "%s_split_%s" % (simulation_movement.getId(), split_index)
while getattr(simulation_movement.getParentValue(), new_id, None) is not None:
split_index += 1
new_id = "%s_split_%s" % (simulation_movement.getId(), split_index)
# Adopt different dates for defferred movements
new_movement = simulation_movement.getParentValue().newContent(
portal_type = "Simulation Movement",
id = new_id,
efficiency = simulation_movement.getEfficiency(),
start_date=simulation_movement.getStartDate(),
stop_date=simulation_movement.getStopDate(),
# XXX resource
# 'order' category is deprecated. it is kept for compatibility.
order = simulation_movement.getOrder(),
quantity = self.quantity,
source = simulation_movement.getSource(),
destination = simulation_movement.getDestination(),
source_section = simulation_movement.getSourceSection(),
destination_section = simulation_movement.getDestinationSection(),
activate_kw = self.activate_kw,
**self.additional_parameters
)
new_movement.recordProperty('start_date')
new_movement.recordProperty('stop_date')
new_movement.edit(start_date=self.start_date,
stop_date=self.stop_date)
simulation_movement.setDefaultActivateParameterDict(self.activate_kw)
simulation_movement.edit (
quantity = (simulation_movement.getQuantity() - self.quantity)
* simulation_movement.getDeliveryRatio()
)
#XXX: vincent: I don't understand why it's multiplicated.
return new_movement
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# 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.
#
##############################################################################
class TargetSolver:
"""
Target solver is used to do backtracking of solved movement up to
simulation tree. It is able to detect if parent movement is frozen on
not, and take proper decision - invoke itself or compensate.
Target solver is able to generate new simulation tree for:
* split delivery
* simple backtrack
* loss generation
* etc
AppliedRules are considered to be linear. As a first approximation,
TargetSolver will be independent of Applied Rules.
Possible future solutions:
- call solve on AppliedRule to allow overriding by
AppliedRule
- call 'updateNewTarget' on Applied rule
to update parent target
This class is the base class for all target solvers.
It's virtual due to "solve", which needs to be overloaded.
"""
def __init__(self, additional_parameters=None, activate_kw=None, **kw):
"""
Creates an instance of TargetSolver with parameters
"""
self.__dict__.update(kw)
if additional_parameters is None:
additional_parameters = {}
self.additional_parameters = additional_parameters
if activate_kw is None:
activate_kw = {}
self.activate_kw = activate_kw
self.previous_target = {}
def solve(self, simulation_movement):
"""
Solve a simulation movement
This function must be implemented by the actual solver which deviates
from this class.
"""
raise NotImplementedError
def solveDelivery(self, delivery):
"""
Solves the whole delivery.
"""
# Then apply to all movements
for movement in delivery.getMovementList():
self.solveMovement(movement)
def solveMovement(self, movement):
"""
Solves a movement.
"""
# Apply to all simulation movements
simulation_movement_list = movement.getDeliveryRelatedValueList(
portal_type="Simulation Movement")
solved_movement_list = []
for simulation_movement in simulation_movement_list:
solved_movement_list.append(self.solve(simulation_movement))
return solved_movement_list
##############################################################################
#
# Copyright (c) 2006 Nexedi SARL and Contributors. All Rights Reserved.
# Romain Courteaud <romain@nexedi.com>
#
# 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 CopyToTarget import CopyToTarget
class TransformationSourcingCopyToTarget(CopyToTarget):
"""
Copy values simulation movement as target, and
recursively solve the sourcing tree.
"""
def _generateValueDeltaDict(self, simulation_movement):
"""
Get interesting value
"""
value_dict = CopyToTarget._generateValueDict(self, simulation_movement)
value_dict.update({
'aggregate_list':
simulation_movement.getDeliveryValue().getAggregateList(),
})
return value_dict
def _generateValueDict(self, simulation_movement, aggregate_list = None,
**value_delta_dict):
"""
Generate values to save on movement.
"""
value_dict = CopyToTarget._generateValueDict(self, simulation_movement,
**value_delta_dict)
# Modify aggregate_list
if aggregate_list is not None:
value_dict['aggregate_list'] = aggregate_list
return value_dict
def _getParentParameters(self, simulation_movement,
**value_delta_dict):
"""
Get parent movement, and its value delta dict.
"""
applied_rule = simulation_movement.getParentValue()
rule = applied_rule.getSpecialiseValue()
if rule.getPortalType() != "Transformation Sourcing Rule":
value_delta_dict.pop('aggregate_list', None)
return CopyToTarget._getParentParameters(self, simulation_movement,
**value_delta_dict)
...@@ -126,54 +126,6 @@ class SimulationTool(BaseTool): ...@@ -126,54 +126,6 @@ class SimulationTool(BaseTool):
['Manager',]) ['Manager',])
BaseTool.inheritedAttribute('manage_afterAdd')(self, item, container) BaseTool.inheritedAttribute('manage_afterAdd')(self, item, container)
def solveDelivery(self, delivery, delivery_solver_name, target_solver_name,
additional_parameters=None, **kw):
"""
Solves a delivery by calling first DeliverySolver, then TargetSolver
"""
return self._solveMovementOrDelivery(delivery, delivery_solver_name,
target_solver_name, delivery=1,
additional_parameters=additional_parameters, **kw)
def solveMovement(self, movement, delivery_solver_name, target_solver_name,
additional_parameters=None, **kw):
"""
Solves a movement by calling first DeliverySolver, then TargetSolver
"""
return self._solveMovementOrDelivery(movement, delivery_solver_name,
target_solver_name, movement=1,
additional_parameters=additional_parameters, **kw)
def _solveMovementOrDelivery(self, document, delivery_solver_name,
target_solver_name, movement=0, delivery=0,
additional_parameters=None,**kw):
"""
Solves a document by calling first DeliverySolver, then TargetSolver
"""
if movement == delivery:
raise ValueError('Parameters movement and delivery have to be'
' different')
solve_result = []
for solver_name, solver_module in ((delivery_solver_name, DeliverySolver),
(target_solver_name, TargetSolver)):
result = None
if solver_name is not None:
solver_file_path = "%s.%s" % (solver_module.__name__,
solver_name)
__import__(solver_file_path)
solver_file = getattr(solver_module, solver_name)
solver_class = getattr(solver_file, solver_name)
solver = solver_class(additional_parameters=additional_parameters,
**kw)
if movement:
result = solver.solveMovement(document)
if delivery:
result = solver.solveDelivery(document)
solve_result.append(result)
return solve_result
####################################################### #######################################################
# Stock Management # Stock Management
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment