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

* 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 @@ ...@@ -28,7 +28,7 @@
from AccessControl import ClassSecurityInfo 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.ERP5Type import Permissions, PropertySheet, Interface
from Products.ERP5.Document.PropertyDivergenceTester import \ from Products.ERP5.Document.PropertyDivergenceTester import \
PropertyDivergenceTester PropertyDivergenceTester
...@@ -69,12 +69,6 @@ class CategoryDivergenceTester(PropertyDivergenceTester): ...@@ -69,12 +69,6 @@ class CategoryDivergenceTester(PropertyDivergenceTester):
divergence_message_list = [] divergence_message_list = []
tested_property = self.getTestedPropertyList() 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() delivery_mvt = simulation_movement.getDeliveryValue()
for tested_property_id, tested_property_title in \ for tested_property_id, tested_property_title in \
self._splitStringList(tested_property): self._splitStringList(tested_property):
...@@ -107,17 +101,16 @@ class CategoryDivergenceTester(PropertyDivergenceTester): ...@@ -107,17 +101,16 @@ class CategoryDivergenceTester(PropertyDivergenceTester):
else: else:
simulation_category_title_list.append(category_value.getTitle()) simulation_category_title_list.append(category_value.getTitle())
delivery_mvt_property = ' , '.join(delivery_mvt_category_title_list) message = DivergenceMessage(
simulation_mvt_property = ' , '.join(simulation_category_title_list) divergence_scope='category',
message = ObjectMessage(
object_relative_url=delivery_mvt.getRelativeUrl(), object_relative_url=delivery_mvt.getRelativeUrl(),
simulation_movement=simulation_movement, simulation_movement=simulation_movement,
decision_value=delivery_mvt_property , decision_value=delivery_mvt_category_list,
prevision_value=simulation_mvt_property, 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, tested_property=tested_property_id,
message=tested_property_title, message=tested_property_title,
solver_script_list=solver_script_list
) )
divergence_message_list.append(message) 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): ...@@ -856,3 +856,15 @@ class Delivery(XMLObject, ImmobilisationDelivery):
""" """
pass 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()
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.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 ...@@ -30,7 +30,7 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type.XMLObject import XMLObject 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 from Products.ERP5Type import Permissions, PropertySheet, Interface
class PropertyDivergenceTester(XMLObject): class PropertyDivergenceTester(XMLObject):
...@@ -75,26 +75,20 @@ class PropertyDivergenceTester(XMLObject): ...@@ -75,26 +75,20 @@ class PropertyDivergenceTester(XMLObject):
divergence_message_list = [] divergence_message_list = []
tested_property = self.getTestedPropertyList() 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() delivery_mvt = simulation_movement.getDeliveryValue()
for tested_property_id, tested_property_title in \ for tested_property_id, tested_property_title in \
self._splitStringList(tested_property): self._splitStringList(tested_property):
delivery_mvt_property = delivery_mvt.getProperty(tested_property_id) delivery_mvt_property = delivery_mvt.getProperty(tested_property_id)
simulation_mvt_property = simulation_movement.getProperty(tested_property_id) simulation_mvt_property = simulation_movement.getProperty(tested_property_id)
if delivery_mvt_property != simulation_mvt_property: if delivery_mvt_property != simulation_mvt_property:
message = ObjectMessage( message = DivergenceMessage(
divergence_scope='property',
object_relative_url=delivery_mvt.getRelativeUrl(), object_relative_url=delivery_mvt.getRelativeUrl(),
simulation_movement=simulation_movement, simulation_movement=simulation_movement,
decision_value=delivery_mvt_property , decision_value=delivery_mvt_property ,
prevision_value=simulation_mvt_property, prevision_value=simulation_mvt_property,
tested_property=tested_property_id, tested_property=tested_property_id,
message=tested_property_title, message=tested_property_title,
solver_script_list=solver_script_list
) )
divergence_message_list.append(message) 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 @@ ...@@ -28,7 +28,7 @@
from AccessControl import ClassSecurityInfo 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.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Products.ERP5.Document.PropertyDivergenceTester import \ from Products.ERP5.Document.PropertyDivergenceTester import \
PropertyDivergenceTester PropertyDivergenceTester
...@@ -73,17 +73,13 @@ class QuantityDivergenceTester(PropertyDivergenceTester): ...@@ -73,17 +73,13 @@ class QuantityDivergenceTester(PropertyDivergenceTester):
quantity = simulation_movement.getCorrectedQuantity() quantity = simulation_movement.getCorrectedQuantity()
d_error = simulation_movement.getDeliveryError() d_error = simulation_movement.getDeliveryError()
solver_script_list = self.getSolverScriptList() message = DivergenceMessage(object_relative_url= delivery.getRelativeUrl(),
if solver_script_list is None: divergence_scope='quantity',
solver_script_list = []
message = ObjectMessage(object_relative_url= delivery.getRelativeUrl(),
simulation_movement = simulation_movement, simulation_movement = simulation_movement,
decision_value = d_quantity , decision_value = d_quantity ,
prevision_value = quantity, prevision_value = quantity,
tested_property='quantity', tested_property='quantity',
message='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
...@@ -1013,6 +1013,14 @@ class ERP5Site(FolderMixIn, CMFSite): ...@@ -1013,6 +1013,14 @@ class ERP5Site(FolderMixIn, CMFSite):
""" """
return self._getPortalGroupedTypeList('inventory_movement') return self._getPortalGroupedTypeList('inventory_movement')
security.declareProtected(Permissions.AccessContentsInformation,
'getPortalMovementGroupTypeList')
def getPortalMovementGroupTypeList(self):
"""
Return movement group types.
"""
return self._getPortalGroupedTypeList('movement_group')
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getDefaultModuleId') 'getDefaultModuleId')
def getDefaultModuleId(self, portal_type, default=MARKER): def getDefaultModuleId(self, portal_type, default=MARKER):
......
This diff is collapsed.
...@@ -38,9 +38,4 @@ class DivergenceTester: ...@@ -38,9 +38,4 @@ class DivergenceTester:
'type' : 'lines', 'type' : 'lines',
'default' : (), 'default' : (),
'mode' : 'w' }, 'mode' : 'w' },
{ 'id' : 'solver_script',
'description' : 'List of scripts used to call the solvers',
'type' : 'string',
'multivalued' : 1,
'mode' : 'w' },
) )
##############################################################################
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
class MovementGroup:
_properties = (
{ 'id' : 'tested_property',
'description' : 'Property used to Test',
'type' : 'lines',
'default' : (),
'mode' : 'w' },
)
_categories = ('collect_order_group', 'divergence_scope',)
##############################################################################
#
# 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':
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)
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)
def _solveRecursively(self, simulation_movement, is_last_movement=1,
quantity_ratio=None, value_dict=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
simulation_movement.edit(**value_dict)
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)
...@@ -1436,10 +1436,23 @@ class TestSaleInvoiceMixin(TestInvoiceMixin, ...@@ -1436,10 +1436,23 @@ class TestSaleInvoiceMixin(TestInvoiceMixin,
""" """
portal = self.getPortal() portal = self.getPortal()
builder = portal.portal_deliveries.sale_invoice_transaction_builder builder = portal.portal_deliveries.sale_invoice_transaction_builder
previous_list = builder.getDeliveryCollectOrderList() delivery_movement_group_list = builder.getDeliveryMovementGroupList()
new_list = [x for x in previous_list if x != 'DateMovementGroup'] uf = self.getPortal().acl_users
new_list.append('ParentExplanationMovementGroup') uf._doAddUser('admin', '', ['Manager'], [])
builder.setDeliveryCollectOrderList(new_list) user = uf.getUserById('admin').__of__(uf)
newSecurityManager(None, user)
for movement_group in delivery_movement_group_list:
if movement_group.getPortalType() == 'Property Movement Group':
# it contains 'start_date' and 'stop_date' only, so we remove
# movement group itself.
builder.deleteContent(movement_group.getId())
builder.newContent(
portal_type = 'Parent Explanation Movement Group',
collect_order_group='delivery',
int_index=len(delivery_movement_group_list)+1
)
user = uf.getUserById('test_invoice_user').__of__(uf)
newSecurityManager(None, user)
def stepEditInvoice(self, sequence=None, sequence_list=None, **kw): def stepEditInvoice(self, sequence=None, sequence_list=None, **kw):
"""Edit the current invoice, to trigger updateAppliedRule.""" """Edit the current invoice, to trigger updateAppliedRule."""
...@@ -1582,8 +1595,27 @@ class TestSaleInvoiceMixin(TestInvoiceMixin, ...@@ -1582,8 +1595,27 @@ class TestSaleInvoiceMixin(TestInvoiceMixin,
split and defer at the invoice level split and defer at the invoice level
""" """
invoice = sequence.get('invoice') invoice = sequence.get('invoice')
invoice.portal_workflow.doActionFor(invoice,'split_prevision_action', kw = {'listbox':[
start_date=self.datetime + 15, stop_date=self.datetime + 25) {'listbox_key':line.getRelativeUrl(),
'choice':'SplitAndDefer'} for line in invoice.getMovementList()]}
self.portal.portal_workflow.doActionFor(
invoice,
'split_and_defer_action',
start_date=self.datetime + 15,
stop_date=self.datetime + 25,
**kw)
pass
def stepUnifyStartDateWithDecisionInvoice(self, sequence=None,
sequence_list=None):
invoice = sequence.get('invoice')
self._solveDeliveryGroupDivergence(invoice, 'start_date',
invoice.getRelativeUrl())
def stepAcceptDecisionQuantityInvoice(self,sequence=None, sequence_list=None):
invoice = sequence.get('invoice')
print repr(invoice.getDivergenceList())
self._solveDivergence(invoice, 'quantity', 'accept')
def stepAcceptDecisionInvoice(self, sequence=None, sequence_list=None, def stepAcceptDecisionInvoice(self, sequence=None, sequence_list=None,
**kw): **kw):
...@@ -1772,7 +1804,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase): ...@@ -1772,7 +1804,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase):
"""Tests for sale invoice. """Tests for sale invoice.
""" """
RUN_ALL_TESTS = 1 RUN_ALL_TESTS = 1
quiet = 1 quiet = 0
# fix inheritance # fix inheritance
login = TestInvoiceMixin.login login = TestInvoiceMixin.login
...@@ -2038,7 +2070,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase): ...@@ -2038,7 +2070,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase):
stepCheckInvoiceIsCalculating stepCheckInvoiceIsCalculating
stepTic stepTic
stepCheckInvoiceIsDiverged stepCheckInvoiceIsDiverged
stepAcceptDecisionInvoice stepUnifyStartDateWithDecisionInvoice
stepTic stepTic
stepCheckInvoiceNotSplitted stepCheckInvoiceNotSplitted
...@@ -2079,7 +2111,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase): ...@@ -2079,7 +2111,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase):
stepCheckInvoiceIsCalculating stepCheckInvoiceIsCalculating
stepTic stepTic
stepCheckInvoiceIsDiverged stepCheckInvoiceIsDiverged
stepAcceptDecisionInvoice stepUnifyStartDateWithDecisionInvoice
stepTic stepTic
stepCheckInvoiceNotSplitted stepCheckInvoiceNotSplitted
...@@ -2111,7 +2143,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase): ...@@ -2111,7 +2143,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase):
stepCheckPackingListIsCalculating stepCheckPackingListIsCalculating
stepTic stepTic
stepCheckPackingListIsDiverged stepCheckPackingListIsDiverged
stepAcceptDecisionPackingList stepAcceptDecisionQuantity
stepTic stepTic
stepCheckPackingListIsSolved stepCheckPackingListIsSolved
stepCheckPackingListNotSplitted stepCheckPackingListNotSplitted
...@@ -2181,7 +2213,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase): ...@@ -2181,7 +2213,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase):
stepCheckPackingListIsCalculating stepCheckPackingListIsCalculating
stepTic stepTic
stepCheckPackingListIsDiverged stepCheckPackingListIsDiverged
stepAcceptDecisionPackingList stepAcceptDecisionQuantity
stepTic stepTic
stepCheckPackingListIsSolved stepCheckPackingListIsSolved
stepCheckPackingListNotSplitted stepCheckPackingListNotSplitted
...@@ -2205,8 +2237,10 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase): ...@@ -2205,8 +2237,10 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase):
end_sequence = \ end_sequence = \
""" """
stepCheckInvoiceIsDiverged stepCheckInvoiceIsDiverged
stepAcceptDecisionInvoice stepAcceptDecisionQuantityInvoice
stepTic stepTic
stepCheckInvoiceIsNotDivergent
stepCheckInvoiceIsSolved
stepStartInvoice stepStartInvoice
stepTic stepTic
stepStopInvoice stepStopInvoice
...@@ -2300,7 +2334,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase): ...@@ -2300,7 +2334,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase):
stepCheckInvoiceIsCalculating stepCheckInvoiceIsCalculating
stepTic stepTic
stepCheckInvoiceIsDiverged stepCheckInvoiceIsDiverged
stepAcceptDecisionInvoice stepAcceptDecisionQuantityInvoice
stepTic stepTic
stepStartInvoice stepStartInvoice
stepTic stepTic
...@@ -2340,7 +2374,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase): ...@@ -2340,7 +2374,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase):
stepCheckInvoiceIsCalculating stepCheckInvoiceIsCalculating
stepTic stepTic
stepCheckInvoiceIsDiverged stepCheckInvoiceIsDiverged
stepAcceptDecisionInvoice stepAcceptDecisionQuantityInvoice
stepTic stepTic
stepStartInvoice stepStartInvoice
stepTic stepTic
...@@ -2464,7 +2498,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase): ...@@ -2464,7 +2498,7 @@ class TestSaleInvoice(TestSaleInvoiceMixin, ERP5TypeTestCase):
stepCheckInvoiceIsCalculating stepCheckInvoiceIsCalculating
stepTic stepTic
stepCheckInvoiceIsDiverged stepCheckInvoiceIsDiverged
stepAcceptDecisionInvoice stepAcceptDecisionQuantityInvoice
stepTic stepTic
stepStartInvoice stepStartInvoice
stepTic stepTic
...@@ -2624,4 +2658,3 @@ def test_suite(): ...@@ -2624,4 +2658,3 @@ def test_suite():
suite.addTest(unittest.makeSuite(TestSaleInvoice)) suite.addTest(unittest.makeSuite(TestSaleInvoice))
suite.addTest(unittest.makeSuite(TestPurchaseInvoice)) suite.addTest(unittest.makeSuite(TestPurchaseInvoice))
return suite return suite
...@@ -57,6 +57,9 @@ class TestOrderMixin: ...@@ -57,6 +57,9 @@ class TestOrderMixin:
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
""" """
""" """
return ('erp5_base','erp5_pdm', 'erp5_trade', 'erp5_apparel',
'erp5_accounting', 'erp5_invoicing',
'erp5_project', 'erp5_payroll', 'erp5_mrp')
return ('erp5_base','erp5_pdm', 'erp5_trade', 'erp5_apparel',) return ('erp5_base','erp5_pdm', 'erp5_trade', 'erp5_apparel',)
def login(self, quiet=0, run=1): def login(self, quiet=0, run=1):
...@@ -122,7 +125,7 @@ class TestOrderMixin: ...@@ -122,7 +125,7 @@ class TestOrderMixin:
resource_module = portal.getDefaultModule(self.resource_portal_type) resource_module = portal.getDefaultModule(self.resource_portal_type)
resource = resource_module.newContent(portal_type=self.resource_portal_type) resource = resource_module.newContent(portal_type=self.resource_portal_type)
resource.edit( resource.edit(
title = "NotVariatedResource", title = "NotVariatedResource%s" % resource.getId(),
industrial_phase_list=["phase1", "phase2"], industrial_phase_list=["phase1", "phase2"],
product_line = 'apparel' product_line = 'apparel'
) )
...@@ -141,7 +144,7 @@ class TestOrderMixin: ...@@ -141,7 +144,7 @@ class TestOrderMixin:
resource_module = portal.getDefaultModule(self.resource_portal_type) resource_module = portal.getDefaultModule(self.resource_portal_type)
resource = resource_module.newContent(portal_type=self.resource_portal_type) resource = resource_module.newContent(portal_type=self.resource_portal_type)
resource.edit( resource.edit(
title = "VariatedResource", title = "VariatedResource%s" % resource.getId(),
industrial_phase_list=["phase1", "phase2"], industrial_phase_list=["phase1", "phase2"],
product_line = 'apparel' product_line = 'apparel'
) )
...@@ -169,7 +172,7 @@ class TestOrderMixin: ...@@ -169,7 +172,7 @@ class TestOrderMixin:
sequence.edit( resource_list = resource_list ) sequence.edit( resource_list = resource_list )
def stepCreateOrganisation(self, sequence=None, sequence_list=None, def stepCreateOrganisation(self, sequence=None, sequence_list=None,
title='organisation', **kw): title=None, **kw):
""" """
Create a empty organisation Create a empty organisation
""" """
...@@ -180,10 +183,11 @@ class TestOrderMixin: ...@@ -180,10 +183,11 @@ class TestOrderMixin:
portal_type=organisation_portal_type) portal_type=organisation_portal_type)
organisation = organisation_module.newContent( \ organisation = organisation_module.newContent( \
portal_type=organisation_portal_type) portal_type=organisation_portal_type)
organisation.edit( if title is None:
title=title, organisation.edit(title='organisation%s' % organisation.getId())
) sequence.edit(organisation=organisation)
#sequence.edit(organisation=organisation) else:
organisation.edit(title=title)
sequence.edit(**{title:organisation}) sequence.edit(**{title:organisation})
def stepCreateOrder(self,sequence=None, sequence_list=None, **kw): def stepCreateOrder(self,sequence=None, sequence_list=None, **kw):
...@@ -196,7 +200,7 @@ class TestOrderMixin: ...@@ -196,7 +200,7 @@ class TestOrderMixin:
order_module = portal.getDefaultModule(portal_type=self.order_portal_type) order_module = portal.getDefaultModule(portal_type=self.order_portal_type)
order = order_module.newContent(portal_type=self.order_portal_type) order = order_module.newContent(portal_type=self.order_portal_type)
order.edit( order.edit(
title = "Order", title = "Order%s" % order.getId(),
start_date = self.datetime + 10, start_date = self.datetime + 10,
stop_date = self.datetime + 20, stop_date = self.datetime + 20,
) )
......
...@@ -51,12 +51,6 @@ class TestOrderBuilderMixin(TestOrderMixin): ...@@ -51,12 +51,6 @@ class TestOrderBuilderMixin(TestOrderMixin):
packing_list_line_portal_type = 'Internal Packing List Line' packing_list_line_portal_type = 'Internal Packing List Line'
packing_list_cell_portal_type = 'Internal Packing List Cell' packing_list_cell_portal_type = 'Internal Packing List Cell'
delivery_cell_collect_order_tuple = ('VariantMovementGroup',)
delivery_collect_order_tuple = ('PathMovementGroup',
'SectionPathMovementGroup', 'DateMovementGroup')
delivery_line_collect_order_tuple = ('ResourceMovementGroup',
'BaseVariantMovementGroup')
# hardcoded values # hardcoded values
order_builder_hardcoded_time_diff = 10.0 order_builder_hardcoded_time_diff = 10.0
...@@ -109,15 +103,43 @@ class TestOrderBuilderMixin(TestOrderMixin): ...@@ -109,15 +103,43 @@ class TestOrderBuilderMixin(TestOrderMixin):
delivery_line_portal_type = self.order_line_portal_type, delivery_line_portal_type = self.order_line_portal_type,
delivery_cell_portal_type = self.order_cell_portal_type, delivery_cell_portal_type = self.order_cell_portal_type,
delivery_collect_order = self.delivery_collect_order_tuple,
delivery_line_collect_order = self.delivery_line_collect_order_tuple,
delivery_cell_collect_order = self.delivery_cell_collect_order_tuple,
destination_value = organisation, destination_value = organisation,
resource_portal_type = self.resource_portal_type, resource_portal_type = self.resource_portal_type,
) )
order_builder.newContent(
portal_type = 'Category Movement Group',
collect_order_group='delivery',
tested_property=['source', 'destination',
'source_section', 'destination_section'],
int_index=1
)
order_builder.newContent(
portal_type = 'Property Movement Group',
collect_order_group='delivery',
tested_property=['start_date', 'stop_date'],
int_index=2
)
order_builder.newContent(
portal_type = 'Category Movement Group',
collect_order_group='line',
tested_property=['resource'],
int_index=1
)
order_builder.newContent(
portal_type = 'Base Variant Movement Group',
collect_order_group='line',
int_index=2
)
order_builder.newContent(
portal_type = 'Variant Movement Group',
collect_order_group='cell',
int_index=1
)
def stepCheckGeneratedDocumentListVariated(self, sequence=None, sequence_list=None, def stepCheckGeneratedDocumentListVariated(self, sequence=None, sequence_list=None,
**kw): **kw):
""" """
......
This diff is collapsed.
...@@ -509,6 +509,7 @@ class TestPayrollMixin(ERP5ReportTestCase): ...@@ -509,6 +509,7 @@ class TestPayrollMixin(ERP5ReportTestCase):
class TestPayroll(TestPayrollMixin): class TestPayroll(TestPayrollMixin):
quiet = 0
def test_01_modelCreation(self): def test_01_modelCreation(self):
''' '''
......
...@@ -49,27 +49,27 @@ class TestProductionPackingReportListMixin(TestProductionOrderMixin, TestPacking ...@@ -49,27 +49,27 @@ class TestProductionPackingReportListMixin(TestProductionOrderMixin, TestPacking
def stepAcceptDecisionSupplyDeliveryPackingList(self, sequence=None, sequence_list=None, **kw): def stepAcceptDecisionSupplyDeliveryPackingList(self, sequence=None, sequence_list=None, **kw):
packing_list = sequence.get('supply_delivery_packing_list') packing_list = sequence.get('supply_delivery_packing_list')
self.modifyPackingListState('accept_decision_action', sequence=sequence, packing_list=packing_list) self._solveDivergence(packing_list, 'quantity', 'accept')
def stepAcceptDecisionProducedDeliveryPackingList(self, sequence=None, sequence_list=None, **kw): def stepAcceptDecisionProducedDeliveryPackingList(self, sequence=None, sequence_list=None, **kw):
packing_list = sequence.get('produced_delivery_packing_list') packing_list = sequence.get('produced_delivery_packing_list')
self.modifyPackingListState('accept_decision_action', sequence=sequence, packing_list=packing_list) self._solveDivergence(packing_list, 'quantity', 'accept')
def stepAdoptPrevisionSupplyDeliveryPackingList(self, sequence=None, sequence_list=None, **kw): def stepAdoptPrevisionSupplyDeliveryPackingList(self, sequence=None, sequence_list=None, **kw):
packing_list = sequence.get('supply_delivery_packing_list') packing_list = sequence.get('supply_delivery_packing_list')
self.modifyPackingListState('adopt_prevision_action', sequence=sequence, packing_list=packing_list) self._solveDivergence(packing_list, 'quantity', 'adopt')
def stepAdoptPrevisionProducedDeliveryPackingList(self, sequence=None, sequence_list=None, **kw): def stepAdoptPrevisionProducedDeliveryPackingList(self, sequence=None, sequence_list=None, **kw):
packing_list = sequence.get('produced_delivery_packing_list') packing_list = sequence.get('produced_delivery_packing_list')
self.modifyPackingListState('adopt_prevision_action', sequence=sequence, packing_list=packing_list) self._solveDivergence(packing_list, 'quantity', 'adopt')
def stepAdoptPrevisionProducedReport(self, sequence=None, sequence_list=None, **kw): def stepAdoptPrevisionProducedReport(self, sequence=None, sequence_list=None, **kw):
packing_list = sequence.get('produced_report') packing_list = sequence.get('produced_report')
self.modifyPackingListState('adopt_prevision_action', sequence=sequence, packing_list=packing_list) self._solveDivergence(packing_list, 'quantity', 'adopt')
def stepAdoptPrevisionConsumedReport(self, sequence=None, sequence_list=None, **kw): def stepAdoptPrevisionConsumedReport(self, sequence=None, sequence_list=None, **kw):
packing_list = sequence.get('consumed_report') packing_list = sequence.get('consumed_report')
self.modifyPackingListState('adopt_prevision_action', sequence=sequence, packing_list=packing_list) self._solveDivergence(packing_list, 'quantity', 'adopt')
def stepSetReadyProducedDeliveryPackingList(self, sequence=None, sequence_list=None, **kw): def stepSetReadyProducedDeliveryPackingList(self, sequence=None, sequence_list=None, **kw):
packing_list = sequence.get('produced_delivery_packing_list') packing_list = sequence.get('produced_delivery_packing_list')
......
...@@ -20,8 +20,9 @@ from OFS.CopySupport import CopyContainer as OriginalCopyContainer ...@@ -20,8 +20,9 @@ from OFS.CopySupport import CopyContainer as OriginalCopyContainer
from OFS.CopySupport import CopyError from OFS.CopySupport import CopyError
from OFS.CopySupport import eNotSupported, eNoItemsSpecified from OFS.CopySupport import eNotSupported, eNoItemsSpecified
from OFS.CopySupport import _cb_encode, _cb_decode, cookie_path from OFS.CopySupport import _cb_encode, _cb_decode, cookie_path
from OFS.CopySupport import sanity_check
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Acquisition import aq_base from Acquisition import aq_base, aq_inner, aq_parent
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Globals import PersistentMapping, MessageDialog from Globals import PersistentMapping, MessageDialog
from Products.ERP5Type.Utils import get_request from Products.ERP5Type.Utils import get_request
...@@ -357,6 +358,8 @@ class CopyContainer: ...@@ -357,6 +358,8 @@ class CopyContainer:
catalog.beforeUnindexObject(None,path=path,uid=uid) catalog.beforeUnindexObject(None,path=path,uid=uid)
# Then start activity in order to remove lines in catalog, # Then start activity in order to remove lines in catalog,
# sql wich generate locks # sql wich generate locks
if path is None:
path = self.getPath()
# - serialization_tag is used in order to prevent unindexation to # - serialization_tag is used in order to prevent unindexation to
# happen before/in parallel with reindexations of the same object. # happen before/in parallel with reindexations of the same object.
catalog.activate(activity='SQLQueue', catalog.activate(activity='SQLQueue',
...@@ -427,6 +430,92 @@ class CopyContainer: ...@@ -427,6 +430,92 @@ class CopyContainer:
path_item_list=previous_path, path_item_list=previous_path,
new_id=self.id) new_id=self.id)
def _duplicate(self, cp):
try: cp = _cb_decode(cp)
except: raise CopyError, 'Clipboard Error'
oblist=[]
op=cp[0]
app = self.getPhysicalRoot()
result = []
for mdata in cp[1]:
m = Moniker.loadMoniker(mdata)
try: ob = m.bind(app)
except: raise CopyError, 'Not Found'
self._verifyObjectPaste(ob, validate_src=1)
oblist.append(ob)
if op==0:
for ob in oblist:
if not ob.cb_isCopyable():
raise CopyError, 'Not Supported'
try: ob._notifyOfCopyTo(self, op=0)
except: raise CopyError, 'Copy Error'
ob = ob._getCopy(self)
orig_id = ob.getId()
id = self._get_id(ob.getId())
result.append({'id':orig_id, 'new_id':id})
ob._setId(id)
self._setObject(id, ob)
ob = self._getOb(id)
ob._postCopy(self, op=0)
ob._postDuplicate()
ob.wl_clearLocks()
if op==1:
# Move operation
for ob in oblist:
id = ob.getId()
if not ob.cb_isMoveable():
raise CopyError, 'Not Supported'
try: ob._notifyOfCopyTo(self, op=1)
except: raise CopyError, 'Move Error'
if not sanity_check(self, ob):
raise CopyError, 'This object cannot be pasted into itself'
# try to make ownership explicit so that it gets carried
# along to the new location if needed.
ob.manage_changeOwnershipType(explicit=1)
aq_parent(aq_inner(ob))._delObject(id)
ob = aq_base(ob)
orig_id = id
id = self._get_id(id)
result.append({'id':orig_id, 'new_id':id })
ob._setId(id)
self._setObject(id, ob, set_owner=0)
ob = self._getOb(id)
ob._postCopy(self, op=1)
# try to make ownership implicit if possible
ob.manage_changeOwnershipType(explicit=0)
return result
def _postDuplicate(self):
self_base = aq_base(self)
portal_catalog = getToolByName(self, 'portal_catalog')
self_base.uid = portal_catalog.newUid()
# Give the Owner local role to the current user, zope only does this if no
# local role has been defined on the object, which breaks ERP5Security
if getattr(self_base, '__ac_local_roles__', None) is not None:
user = getSecurityManager().getUser()
if user is not None:
userid = user.getId()
if userid is not None:
#remove previous owners
dict = self.__ac_local_roles__
for key, value in dict.items():
if 'Owner' in value:
value.remove('Owner')
#add new owner
l = dict.setdefault(userid, [])
l.append('Owner')
self.__recurse('_postDuplicate')
#### Helper methods #### Helper methods
def tryMethodCallWithTemporaryPermission(context, permission, method, def tryMethodCallWithTemporaryPermission(context, permission, method,
...@@ -455,4 +544,3 @@ def tryMethodCallWithTemporaryPermission(context, permission, method, ...@@ -455,4 +544,3 @@ def tryMethodCallWithTemporaryPermission(context, permission, method,
result = method(*method_argv, **method_kw) result = method(*method_argv, **method_kw)
p.setRoles(old_role_list) p.setRoles(old_role_list)
return result return result
##############################################################################
#
# 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.PythonScripts.Utility import allow_class
from Products.ERP5Type.ObjectMessage import ObjectMessage
from zLOG import LOG, PROBLEM, INFO
class DivergenceMessage(ObjectMessage):
"""
Divergence Message is used for notifications to user about divergences.
"""
def getMovementGroup(self):
divergence_scope = getattr(self, 'divergence_scope', None)
if divergence_scope is None:
return []
tested_property = getattr(self, 'tested_property', None)
movement_group_list = []
delivery = self.simulation_movement.getDeliveryValue().getParentValue()
for builder in delivery.getBuilderList():
for movement_group in builder.getMovementGroupList():
if movement_group.getDivergenceScope() == divergence_scope:
if tested_property is None or \
tested_property in movement_group.getTestedPropertyList():
return movement_group
def getCollectOrderGroup(self):
movement_group = self.getMovementGroup()
if movement_group is not None:
return movement_group.getCollectOrderGroup()
elif getattr(self, 'divergence_scope', None) == 'quantity':
# We have no MovementGroup for quantity, so try to guess from the
# portal_type.
portal_type = self.getObject().getPortalType()
if 'Line' in portal_type:
return 'line'
elif 'Cell' in portal_type:
return 'cell'
return None
allow_class(DivergenceMessage)
...@@ -193,6 +193,8 @@ class ERP5TypeInformationMixIn( FactoryTypeInformation, ...@@ -193,6 +193,8 @@ class ERP5TypeInformationMixIn( FactoryTypeInformation,
'project', 'project',
# Module # Module
'module', 'module',
# Movement Group
'movement_group',
) )
group_list = () group_list = ()
......
...@@ -84,7 +84,7 @@ class ObjectMessage: ...@@ -84,7 +84,7 @@ class ObjectMessage:
if request is not None: if request is not None:
for item in request: for item in request:
if item.meta_type == 'ERP5 Site': if item.meta_type == 'ERP5 Site':
return item.restrictedTraverse(self.object_relative_url) return item.unrestrictedTraverse(self.object_relative_url)
return None return None
......
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