Commit 5cd7a01c authored by Kazuhiko Shiozaki's avatar Kazuhiko Shiozaki
Browse files

* Movement Group becomes ERP5 Document. See...

* Movement Group becomes ERP5 Document. See http://www.erp5.org/HowToConfigureBuilder for the detail.
* add _duplicate() in CopySupport.py. This method is similar to copy and paste, but it preserves workflow history.
* add a new target solver CopyAndPropagate.py that updates values according to passed divergence list and propagate them.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@23647 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 56831a07
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class BaseVariantMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Base Variant Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
category_list = movement.getVariationBaseCategoryList()
if category_list is None:
category_list = []
category_list.sort()
property_dict['_base_category_list'] = category_list
return property_dict
def test(self, object, property_dict):
category_list = object.getVariationBaseCategoryList()
if category_list is None:
category_list = []
category_list.sort()
return property_dict['_base_category_list'] == category_list
def testToUpdate(self, object, property_dict, **kw):
# This movement group does not affect updating.
return True, {}
......@@ -28,7 +28,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type.ObjectMessage import ObjectMessage
from Products.ERP5Type.DivergenceMessage import DivergenceMessage
from Products.ERP5Type import Permissions, PropertySheet, Interface
from Products.ERP5.Document.PropertyDivergenceTester import \
PropertyDivergenceTester
......@@ -69,12 +69,6 @@ class CategoryDivergenceTester(PropertyDivergenceTester):
divergence_message_list = []
tested_property = self.getTestedPropertyList()
# Get the solver script list
solver_script_list = self.getSolverScriptList()
if solver_script_list is None:
solver_script_list = []
solver_script_list = self._splitStringList(solver_script_list)
delivery_mvt = simulation_movement.getDeliveryValue()
for tested_property_id, tested_property_title in \
self._splitStringList(tested_property):
......@@ -107,17 +101,16 @@ class CategoryDivergenceTester(PropertyDivergenceTester):
else:
simulation_category_title_list.append(category_value.getTitle())
delivery_mvt_property = ' , '.join(delivery_mvt_category_title_list)
simulation_mvt_property = ' , '.join(simulation_category_title_list)
message = ObjectMessage(
message = DivergenceMessage(
divergence_scope='category',
object_relative_url=delivery_mvt.getRelativeUrl(),
simulation_movement=simulation_movement,
decision_value=delivery_mvt_property ,
prevision_value=simulation_mvt_property,
decision_value=delivery_mvt_category_list,
prevision_value=simulation_category_list,
decision_title=', '.join(delivery_mvt_category_title_list),
prevision_title=', '.join(simulation_category_title_list),
tested_property=tested_property_id,
message=tested_property_title,
solver_script_list=solver_script_list
)
divergence_message_list.append(message)
......
##############################################################################
#
# 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 Products.ERP5.Document.PropertyMovementGroup import PropertyMovementGroup
class CategoryMovementGroup(PropertyMovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
This movement group is used to group movements that have the same
categories (eg. source, destination, etc.).
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Category Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
for prop in self.getTestedPropertyList():
property_dict['%s_list' % prop] = movement.getPropertyList(prop, None)
return property_dict
def test(self, object, property_dict, property_list=None, **kw):
if property_list not in (None, []):
target_property_list = [x for x in self.getTestedPropertyList() \
if x in property_list]
else:
target_property_list = self.getTestedPropertyList()
for prop in target_property_list:
if sorted(property_dict['%s_list' % prop]) != \
sorted(object.getPropertyList(prop, None)):
return False
return True
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class CausalityAssignmentMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
This movement group is used in order to define the causality on lines
and cells.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Causality Assignment Movement Group'
def _getPropertyDict(self, movement, **kw):
return self._addCausalityToEdit(movement)
def _separate(self, movement_list):
property_dict = {}
for movement in movement_list:
self._addCausalityToEdit(movement, property_dict)
return [[movement_list, property_dict]]
def testToUpdate(self, movement, property_dict, **kw):
# We can always update.
return True, property_dict
def _addCausalityToEdit(self, movement, property_dict=None):
if property_dict is None:
property_dict = {}
parent = movement
# Go upper into the simulation tree in order to find an order link
while parent.getOrderValue() is None and not(parent.isRootAppliedRule()):
parent = parent.getParentValue()
order_movement = parent.getOrderValue()
if order_movement is not None:
causality = property_dict.get('causality_list', [])
order_movement_url = order_movement.getRelativeUrl()
if order_movement_url not in causality:
causality.append(order_movement_url)
property_dict['causality_list'] = causality
return property_dict
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class CausalityMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Causality Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
explanation_relative_url = self._getExplanationRelativeUrl(movement)
property_dict['_explanation'] = explanation_relative_url
return property_dict
def testToUpdate(self, movement, property_dict, **kw):
# we don't care the difference of explanation url when updating
#return True, property_dict
return True, {}
def _getExplanationRelativeUrl(self, movement):
""" Get the order value for a movement """
if hasattr(movement, 'getParentValue'):
# This is a simulation movement
if movement.getParentValue() != movement.getRootAppliedRule() :
# get the explanation of parent movement if we have not been
# created by the root applied rule.
movement = movement.getParentValue().getParentValue()
explanation_value = movement.getExplanationValue()
if explanation_value is None:
raise ValueError, 'No explanation for movement %s' % movement.getPath()
else:
# This is a temp movement
explanation_value = None
if explanation_value is None:
explanation_relative_url = None
else:
# get the enclosing delivery for this cell or line
if hasattr(explanation_value, 'getExplanationValue') :
explanation_value = explanation_value.getExplanationValue()
explanation_relative_url = explanation_value.getRelativeUrl()
return explanation_relative_url
......@@ -856,3 +856,15 @@ class Delivery(XMLObject, ImmobilisationDelivery):
"""
pass
def getBuilderList(self):
"""Returns appropriate builder list."""
return self._getTypeBasedMethod('getBuilderList')()
def getRuleReference(self):
"""Returns an appropriate rule reference."""
method = self._getTypeBasedMethod('getRuleReference')
if method is not None:
return method()
else:
raise 'SimulationError', '%s_getRuleReference script is missing.' \
% self.getPortalType()
......@@ -27,16 +27,10 @@
##############################################################################
from AccessControl import ClassSecurityInfo
from Products.CMFCore.utils import getToolByName
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
from Products.ERP5 import MovementGroup
from Products.ERP5Type.Utils import convertToUpperCase
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.OrderBuilder import OrderBuilder
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
from zLOG import LOG
class SelectMethodError(Exception): pass
class SelectMovementError(Exception): pass
......@@ -149,7 +143,8 @@ class DeliveryBuilder(OrderBuilder):
def _setDeliveryMovementProperties(self, delivery_movement,
simulation_movement, property_dict,
update_existing_movement=0):
update_existing_movement=0,
force_update=0):
"""
Initialize or update delivery movement properties.
Set delivery ratio on simulation movement.
......@@ -159,116 +154,168 @@ class DeliveryBuilder(OrderBuilder):
OrderBuilder._setDeliveryMovementProperties(
self, delivery_movement,
simulation_movement, property_dict,
update_existing_movement=update_existing_movement)
# Check if simulation movement is not already linked to a existing
# movement
if simulation_movement.getDeliveryValue() is not None:
raise SelectMovementError,\
"simulation_movement '%s' must not be selected !" %\
simulation_movement.getRelativeUrl()
# Update simulation movement
update_existing_movement=update_existing_movement,
force_update=force_update)
simulation_movement.edit(delivery_value=delivery_movement)
# Simulation consistency propagation
security.declareProtected(Permissions.ModifyPortalContent,
'updateFromSimulation')
def updateFromSimulation(self, delivery_relative_url, create_new_delivery=1):
def updateFromSimulation(self, delivery_relative_url, **kw):
"""
Update all lines of this transaction based on movements in the
simulation related to this transaction.
"""
updateFromSimulation = UnrestrictedMethod(self._updateFromSimulation)
return updateFromSimulation(delivery_relative_url,
create_new_delivery=create_new_delivery)
# We have to get a delivery, else, raise a Error
delivery = self.getPortalObject().restrictedTraverse(delivery_relative_url)
divergence_to_adopt_list = delivery.getDivergenceList()
return self.solveDivergence(
delivery_relative_url,
divergence_to_adopt_list=divergence_to_adopt_list)
def solveDeliveryGroupDivergence(self, *args, **kw):
"""
solve each divergence according to users decision (accept, adopt
or do nothing).
"""
solveDeliveryGroupDivergence = UnrestrictedMethod(self._solveDeliveryGroupDivergence)
return solveDeliveryGroupDivergence(*args, **kw)
def _solveDeliveryGroupDivergence(self, delivery_relative_url,
property_dict=None, comment=None):
if property_dict in (None, {}):
return
delivery = self.getPortalObject().restrictedTraverse(delivery_relative_url)
delivery.edit(comment=comment, **property_dict)
def _updateFromSimulation(self, delivery_relative_url, create_new_delivery=1):
# Try to remove existing properties/categories from Movements that
# should exist on Deliveries.
for movement in delivery.getMovementList():
for prop in property_dict.keys():
# XXX The following should be implemented in better way.
if movement.hasProperty(prop):
try:
# for Property
movement._delProperty(prop)
except AttributeError:
# for Category
movement.setProperty(prop, None)
divergence_to_accept_list = []
for divergence in delivery.getDivergenceList():
if divergence.getProperty('tested_property') not in property_dict.keys():
continue
divergence_to_accept_list.append(divergence)
self._solveDivergence(delivery_relative_url,
divergence_to_accept_list=divergence_to_accept_list)
def solveDivergence(self, *args, **kw):
"""
solve each divergence according to users decision (accept, adopt
or do nothing).
"""
solveDivergence = UnrestrictedMethod(self._solveDivergence)
return solveDivergence(*args, **kw)
def _solveDivergence(self, delivery_relative_url,
divergence_to_accept_list=None,
divergence_to_adopt_list=None,
**kw):
# We have to get a delivery, else, raise a Error
delivery = self.getPortalObject().restrictedTraverse(delivery_relative_url)
delivery_uid = delivery.getUid()
if divergence_to_accept_list is None:
divergence_to_accept_list = []
if divergence_to_adopt_list is None:
divergence_to_adopt_list = []
if not len(divergence_to_accept_list) and \
not len(divergence_to_adopt_list):
return
# First, we update simulation movements according to
# divergence_to_accept_list.
if len(divergence_to_accept_list):
solver_script = delivery._getTypeBasedMethod('acceptDecision',
'Delivery_acceptDecision')
solver_script(divergence_to_accept_list)
# Then, we update delivery/line/cell from simulation movements
# according to divergence_to_adopt_list.
if not len(divergence_to_adopt_list):
return
# Select
movement_list = delivery.getMovementList()
simulation_movement_list = []
for movement in delivery.getMovementList():
for movement in movement_list:
movement.edit(quantity=0)
for simulation_movement in movement.getDeliveryRelatedValueList(
portal_type="Simulation Movement"):
simulation_movement.setDelivery(None)
simulation_movement_list.append(simulation_movement)
# Collect
root_group = self.collectMovement(simulation_movement_list)
# Update delivery
rejected_movement_list = self._deliveryUpdateGroupProcessing(
delivery,
root_group)
# Build
portal = self.getPortalObject()
delivery_module = getattr(portal, self.getDeliveryModule())
delivery_to_update_list = [delivery]
self._resetUpdated()
delivery_list = self._deliveryGroupProcessing(
delivery_module,
root_group,
self.getDeliveryMovementGroupList(),
delivery_to_update_list=delivery_to_update_list,
divergence_list=divergence_to_adopt_list,
force_update=1)
# Then, we should re-apply quantity divergence according to 'Do
# nothing' quanity divergence list because all quantity are already
# calculated in adopt prevision phase.
quantity_dict = {}
for divergence in delivery.getDivergenceList():
if divergence.getProperty('divergence_scope') != 'quantity' or \
divergence in divergence_to_accept_list or \
divergence in divergence_to_adopt_list:
continue
s_m = divergence.getProperty('simulation_movement')
delivery_movement = s_m.getDeliveryValue()
quantity_gap = divergence.getProperty('decision_value') - \
divergence.getProperty('prevision_value')
delivery_movement.setQuantity(delivery_movement.getQuantity() + \
quantity_gap)
quantity_dict[s_m] = \
divergence.getProperty('decision_value')
# Finally, recalculate delivery_ratio
#
# Here, created/updated movements are not indexed yet. So we try to
# gather delivery relations from simulation movements.
delivery_dict = {}
for s_m in simulation_movement_list:
delivery_path = s_m.getDelivery()
delivery_dict[delivery_path] = \
delivery_dict.get(delivery_path, []) + \
[s_m]
for s_m_list_per_movement in delivery_dict.values():
total_quantity = reduce(lambda x, y: \
x + quantity_dict.get(y, y.getQuantity()),
s_m_list_per_movement, 0)
if total_quantity != 0.0:
for s_m in s_m_list_per_movement:
delivery_ratio = quantity_dict.get(s_m, s_m.getQuantity()) \
/ total_quantity
s_m.edit(delivery_ratio=delivery_ratio)
else:
for s_m in s_m_list_per_movement:
delivery_ratio = 1.0 / len(s_m_list_per_movement)
s_m.edit(delivery_ratio=delivery_ratio)
for sim_mvt in root_group.getMovementList():
sim_mvt.immediateReindexObject()
# Call afterscript if new deliveries are created
new_delivery_list = [x for x in delivery_list if x != delivery]
self.callAfterBuildingScript(new_delivery_list, simulation_movement_list)
# Store the good quantity value on delivery line
for movement in delivery.getMovementList():
total_quantity = 0
sim_mvt_list = movement.getDeliveryRelatedValueList(
portal_type="Simulation Movement")
for simulation_movement in sim_mvt_list:
total_quantity += simulation_movement.getQuantity()
if total_quantity != 0:
for simulation_movement in sim_mvt_list:
quantity = simulation_movement.getQuantity()
#simulation_movement.setDeliveryRatio(quantity/total_quantity)
simulation_movement.edit(delivery_ratio=quantity/total_quantity)
else:
if len(sim_mvt_list) != 0:
# Distribute equally ratio to all movement
mvt_ratio = 1 / len(sim_mvt_list)
for simulation_movement in sim_mvt_list:
#simulation_movement.setDeliveryRatio(mvt_ratio)
simulation_movement.edit(delivery_ratio=mvt_ratio)
movement.edit(quantity=total_quantity)
# To update the divergence status, the simulation movements
# must be reindexed, and then the delivery must be touched
path_list = []
# Launch delivery creation
if (create_new_delivery == 1) and\
(rejected_movement_list != []):
movement_relative_url_list = []
for movement in rejected_movement_list:
# XXX FIXME Not very generic...
if movement.__class__.__name__ == "FakeMovement":
movement_relative_url_list.extend(
[x.getRelativeUrl() for x in movement.getMovementList()])
else:
movement_relative_url_list.append(movement.getRelativeUrl())
self.activate(activity="SQLQueue").build(
movement_relative_url_list=movement_relative_url_list)
def _deliveryUpdateGroupProcessing(self, delivery, movement_group):
"""
Update delivery movement
"""
rejected_movement_list = []
property_dict = {}
for collect_order in self.getDeliveryCollectOrderList():
for group in movement_group.getGroupList()[1:]:
rejected_movement_list.extend(group.getMovementList())
movement_group = movement_group.getGroupList()[0]
property_dict.update(movement_group.getGroupEditDict())
# Put properties on delivery
delivery.edit(**property_dict)
# Then, reconnect simulation to delivery line
for group in movement_group.getGroupList():
self._deliveryLineGroupProcessing(
delivery,
group,
self.getDeliveryLineCollectOrderList()[1:],
{},
update_requested=1)
return rejected_movement_list
return delivery_list
##############################################################################
#
# 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 Products.ERP5Type.XMLObject import XMLObject
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet
class MovementGroup(XMLObject):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Movement Group'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Default Properties
property_sheets = (
PropertySheet.Base,
PropertySheet.SimpleItem,
PropertySheet.CategoryCore,
PropertySheet.MovementGroup,
PropertySheet.SortIndex,
)
def _getPropertyDict(self, movement, **kw):
# This method should be defined in sub classes.
raise NotImplementedError
def _separate(self, movement_list):
# By default, we separate movements by _getPropertyDict() values.
# You can override this method in each MovementGroup class.
tmp_dict = {}
for movement in movement_list:
property_dict = self._getPropertyDict(movement)
# XXX it can be wrong. we need a good way to get hash value, or
# we should compare for all pairs.
key = repr(property_dict)
if tmp_dict.has_key(key):
tmp_dict[key][0].append(movement)
else:
tmp_dict[key] = [[movement], property_dict]
return tmp_dict.values()
def separate(self, movement_list):
# We sort group of simulation movements by their IDs.
# DO NOT OVERRIDE THIS METHOD. Override _separate() instead.
return sorted([[sorted(x[0], lambda a,b:cmp(a.getId(), b.getId())), x[1]] \
for x in self._separate(movement_list)],
lambda a,b: cmp(a[0][0].getId(), b[0][0].getId()))
def test(self, object, property_dict, **kw):
raise NotImplementedError
def testToUpdate(self, object, property_dict, **kw):
if self.test(object, property_dict, **kw):
return True, property_dict
else:
return False, property_dict
This diff is collapsed.
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class OrderMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
This movement group is used to group movements that come from the same
Order.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Order Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
order_relative_url = self._getOrderRelativeUrl(movement)
property_dict['causality'] = order_relative_url
return property_dict
def testToUpdate(self, movement, property_dict, **kw):
if movement.getCausality() == property_dict['causality']:
return True, property_dict
else:
return False, property_dict
def _getOrderRelativeUrl(self, movement):
if hasattr(movement, 'getRootAppliedRule'):
# This is a simulation movement
order_relative_url = movement.getRootAppliedRule().getCausality(
portal_type=movement.getPortalOrderTypeList())
if order_relative_url is None:
# In some cases (ex. DeliveryRule), there is no order
# we may consider a PackingList as the order in the OrderGroup
order_relative_url = movement.getRootAppliedRule().getCausality(
portal_type=movement.getPortalDeliveryTypeList())
else:
# This is a temp movement
order_relative_url = None
return order_relative_url
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class ParentExplanationMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
This movement group was first created for building Sale invoices
transactions lines. We need to put accounting lines in the same
invoices than invoice lines.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Parent Explanation Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
parent_explanation_value = movement.getParentExplanationValue()
property_dict['parent_explanation_value'] = parent_explanation_value
return property_dict
def test(self, object, property_dict, property_list=None, **kw):
return object.getParentExplanationValue() == \
property_dict['parent_explanation_value']
......@@ -30,7 +30,7 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type.ObjectMessage import ObjectMessage
from Products.ERP5Type.DivergenceMessage import DivergenceMessage
from Products.ERP5Type import Permissions, PropertySheet, Interface
class PropertyDivergenceTester(XMLObject):
......@@ -75,26 +75,20 @@ class PropertyDivergenceTester(XMLObject):
divergence_message_list = []
tested_property = self.getTestedPropertyList()
# Get the list of solvers callable in this case
solver_script_list = self.getSolverScriptList()
if solver_script_list is None:
solver_script_list = []
solver_script_list = self._splitStringList(solver_script_list)
delivery_mvt = simulation_movement.getDeliveryValue()
for tested_property_id, tested_property_title in \
self._splitStringList(tested_property):
delivery_mvt_property = delivery_mvt.getProperty(tested_property_id)
simulation_mvt_property = simulation_movement.getProperty(tested_property_id)
if delivery_mvt_property != simulation_mvt_property:
message = ObjectMessage(
message = DivergenceMessage(
divergence_scope='property',
object_relative_url=delivery_mvt.getRelativeUrl(),
simulation_movement=simulation_movement,
decision_value=delivery_mvt_property ,
prevision_value=simulation_mvt_property,
tested_property=tested_property_id,
message=tested_property_title,
solver_script_list=solver_script_list
)
divergence_message_list.append(message)
......
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class PropertyMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
This movement group is used to group movements that have the same
properties (eg. start_date, stop_date, etc.).
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Property Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
for prop in self.getTestedPropertyList():
property_dict[prop] = movement.getProperty(prop, None)
return property_dict
def test(self, object, property_dict, property_list=None, **kw):
if property_list not in (None, []):
target_property_list = [x for x in self.getTestedPropertyList() \
if x in property_list]
else:
target_property_list = self.getTestedPropertyList()
for prop in target_property_list:
if property_dict[prop] != object.getProperty(prop, None):
return False
return True
......@@ -28,7 +28,7 @@
from AccessControl import ClassSecurityInfo
from Products.ERP5Type.ObjectMessage import ObjectMessage
from Products.ERP5Type.DivergenceMessage import DivergenceMessage
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Products.ERP5.Document.PropertyDivergenceTester import \
PropertyDivergenceTester
......@@ -73,17 +73,13 @@ class QuantityDivergenceTester(PropertyDivergenceTester):
quantity = simulation_movement.getCorrectedQuantity()
d_error = simulation_movement.getDeliveryError()
solver_script_list = self.getSolverScriptList()
if solver_script_list is None:
solver_script_list = []
message = ObjectMessage(object_relative_url= delivery.getRelativeUrl(),
message = DivergenceMessage(object_relative_url= delivery.getRelativeUrl(),
divergence_scope='quantity',
simulation_movement = simulation_movement,
decision_value = d_quantity ,
prevision_value = quantity,
tested_property='quantity',
message='Quantity',
solver_script_list=self._splitStringList(solver_script_list)
)
......
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class QuantitySignMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Quantity Sign Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
quantity = movement.getQuantity()
property_dict['quantity_sign'] = cmp(quantity, 0)
return property_dict
def _separate(self, movement_list):
tmp_list = [[], [], []] # -1:minus, 0:zero, 1:plus
for movement in movement_list:
tmp_list[cmp(movement.getQuantity(), 0)].append(movement)
if len(tmp_list[1]):
if len(tmp_list[-1]):
return[
[tmp_list[1]+tmp_list[0], {'quantity_sign':1}],
[tmp_list[-1], {'quantity_sign':-1}],
]
else:
return[
[tmp_list[1]+tmp_list[0], {'quantity_sign':1}],
]
elif len(tmp_list[-1]):
return[
[tmp_list[-1]+tmp_list[0], {'quantity_sign':-1}],
]
else:
return [
[tmp_list[0], {'quantity_sign':0}],
]
def test(self, object, property_dict, **kw):
quantity = object.getQuantity()
sign1 = property_dict['quantity_sign']
sign2 = cmp(quantity, 0)
if sign2 == 0:
return 1
if sign1 == 0:
property_dict['quantity_sign'] = sign2
return 1
return sign1 == sign2
def testToUpdate(self, object, property_dict, **kw):
if object.getQuantitySign() == property_dict['quantity_sign']:
return True, property_dict
else:
return False, property_dict
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class RequirementMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Requirement Movement Group'
def _getPropertyDict(self, movement, **kw):
return {'requirement':self._getRequirementList(movement)}
def testToUpdate(self, movement, property_dict, **kw):
# We can always update
return True, property_dict
def _getRequirementList(self, movement):
order_value = movement.getOrderValue()
requirement_list = []
if order_value is not None:
if 'Line' in order_value.getPortalType():
requirement_list = order_value.getRequirementList()
return requirement_list
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class RootAppliedRuleCausalityMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
This movement group is used to group movements whose root apply rule
has the same causality.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Root Applied Rule Causality Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
root_causality_value = self._getRootCausalityValue(movement)
property_dict['root_causality_value_list'] = [root_causality_value]
return property_dict
def testToUpdate(self, movement, property_dict, **kw):
# We can always update
return True, property_dict
def _getRootCausalityValue(self, movement):
""" Get the causality value of the root applied rule for a movement """
return movement.getRootAppliedRule().getCausalityValue()
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class SplitMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Split Movement Group'
def _getPropertyDict(self, movement, **kw):
return {}
def testToUpdate(self, object, property_dict, **kw):
return True, property_dict
def _separate(self, movement_list):
return [[[movement], {}] for movement in movement_list]
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class TitleMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Title Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
property_dict['title'] = self._getTitle(movement)
return property_dict
def test(self, object, property_dict, **kw):
return self._getTitle(object) == property_dict['title']
def testToUpdate(self, object, property_dict, **kw):
# If title is different, we want to update existing object instead
# of creating a new one.
return True, property_dict
def _getTitle(self,movement):
order_value = movement.getOrderValue()
title = ''
if order_value is not None:
if "Line" in order_value.getPortalType():
title = order_value.getTitle()
elif "Cell" in order_value.getPortalType():
title = order_value.getParentValue().getTitle()
return title
##############################################################################
#
# 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 Products.ERP5.Document.MovementGroup import MovementGroup
class VariantMovementGroup(MovementGroup):
"""
The purpose of MovementGroup is to define how movements are grouped,
and how values are updated from simulation movements.
"""
meta_type = 'ERP5 Movement Group'
portal_type = 'Variant Movement Group'
def _getPropertyDict(self, movement, **kw):
property_dict = {}
category_list = movement.getVariationCategoryList()
if category_list is None:
category_list = []
category_list.sort()
property_dict['variation_category_list'] = category_list
return property_dict
def test(self, object, property_dict, **kw):
category_list = object.getVariationCategoryList()
if category_list is None:
category_list = []
category_list.sort()
return property_dict['variation_category_list'] == category_list
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