[Erp5-report] r37088 kazuhiko - in /erp5/trunk/products/ERP5: Document/ tests/
nobody at svn.erp5.org
nobody at svn.erp5.org
Tue Jul 13 16:10:49 CEST 2010
Author: kazuhiko
Date: Tue Jul 13 16:10:48 2010
New Revision: 37088
URL: http://svn.erp5.org?rev=37088&view=rev
Log:
add a Movement Split Solver, that moves several movements to another delivery. a test is added to confirm if it works fine with cells.
Added:
erp5/trunk/products/ERP5/Document/MovementSplitSolver.py
Modified:
erp5/trunk/products/ERP5/tests/testPackingList.py
Added: erp5/trunk/products/ERP5/Document/MovementSplitSolver.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/MovementSplitSolver.py?rev=37088&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/Document/MovementSplitSolver.py (added)
+++ erp5/trunk/products/ERP5/Document/MovementSplitSolver.py [utf8] Tue Jul 13 16:10:48 2010
@@ -0,0 +1,249 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsibility of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# guarantees and support are strongly advised 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.
+#
+##############################################################################
+
+import zope.interface
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet, interfaces
+from Products.ERP5Type.XMLObject import XMLObject
+from Products.ERP5.mixin.solver import SolverMixin
+from Products.ERP5.mixin.configurable import ConfigurableMixin
+from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
+from Products.ERP5Type.Message import translateString
+
+class MovementSplitSolver(SolverMixin, ConfigurableMixin, XMLObject):
+ meta_type = 'ERP5 Movement Split Solver'
+ portal_type = 'Movement Split Solver'
+ add_permission = Permissions.AddPortalContent
+ isIndexable = 0 # We do not want to fill the catalog with objects on which we need no reporting
+
+ # Declarative security
+ security = ClassSecurityInfo()
+ security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+ # Default Properties
+ property_sheets = ( PropertySheet.Base
+ , PropertySheet.XMLObject
+ , PropertySheet.CategoryCore
+ , PropertySheet.DublinCore
+ , PropertySheet.Arrow
+ , PropertySheet.TargetSolver
+ )
+ # Declarative interfaces
+ zope.interface.implements(interfaces.ISolver,
+ interfaces.IConfigurable,
+ )
+
+ # ISolver Implementation
+ def solve(self, activate_kw=None):
+ """
+ This method splits a Delivery and move movements in to a new
+ Delivery. Splitting is done by duplicating the Delivery, removing
+ needless lines excess and updating related content.
+ """
+ delivery_dict = {}
+ for simulation_movement in self.getDeliveryValueList():
+ movement = simulation_movement.getDeliveryValue()
+ delivery = movement.getRootDeliveryValue()
+ delivery_dict.setdefault(delivery, []).append(simulation_movement)
+
+ for delivery, split_simulation_movement_list \
+ in delivery_dict.iteritems():
+ # First, duplicate the whole delivery document including its
+ # sub objects.
+ applied_rule = delivery.getCausalityRelatedValue(
+ portal_type='Applied Rule')
+ parent = delivery.getParentValue()
+ cp, = UnrestrictedMethod(lambda parent, *ids:
+ parent._duplicate(parent.manage_copyObjects(ids=ids))
+ )(parent, delivery.getId())
+ new_delivery = parent[cp['new_id']]
+ old_delivery_url = delivery.getRelativeUrl()
+ new_delivery_url = new_delivery.getRelativeUrl()
+
+ reindex_path_list = [new_delivery.getPath()]
+ update_related_content_tag_list = []
+
+ old_simulation_movement_list = []
+ new_simulation_movement_list = []
+
+ def _isDescendant(parent, child):
+ """
+ /1 and /1/2 => True
+ /1 and /1 => True
+ /1/2 and /1 => False
+ """
+ return ('%s/' % child.getRelativeUrl()).startswith(
+ '%s/' % parent.getRelativeUrl())
+
+ def _delete(obj):
+ parent = obj.getParentValue()
+ parent.deleteContent(obj.getId())
+ if len(parent) == 0 and parent != parent.getRootDeliveryValue():
+ _delete(parent)
+
+ for movement in delivery.getMovementList():
+ simulation_movement_list = movement.getDeliveryRelatedValueList()
+ old = []
+ new = []
+ for simulation_movement in simulation_movement_list:
+ for parent in split_simulation_movement_list:
+ if _isDescendant(parent, simulation_movement):
+ new.append(simulation_movement)
+ break
+ else:
+ old.append(simulation_movement)
+ if len(new) == 0:
+ # Case 1. the movement is only used for the old delivery.
+ # * remove from the new delivery
+ old_simulation_movement_list.extend(
+ [x.getRelativeUrl() for x in simulation_movement_list])
+ _delete(delivery.unrestrictedTraverse(
+ movement.getRelativeUrl().replace(
+ old_delivery_url, new_delivery_url)))
+ elif len(old) == 0:
+ # Case 2. the movement is only used for the new delivery.
+ # * update related content on the new movement
+ # * remove from the old delivery
+ new_movement_url = movement.getRelativeUrl().replace(
+ old_delivery_url, new_delivery_url)
+ movement.updateRelatedContent(movement.getRelativeUrl(),
+ new_movement_url)
+ update_related_content_tag_list.append('%s_updateRelatedContent'
+ % movement.getPath())
+ new_movement_path = movement.getPath().replace(
+ old_delivery_url, new_delivery_url)
+ reindex_path_list.append(new_movement_path)
+ reindex_path_list.extend(
+ [x.getPath() for x in simulation_movement_list])
+ new_simulation_movement_list.extend(
+ [x.getRelativeUrl() for x in simulation_movement_list])
+ _delete(movement)
+ else:
+ # Case 3. the movement is used for both the old and the new
+ # delivery.
+ # * modify 'delivery' value on simulation movements that are
+ # related to the new delivery.
+ # * recalculate quantity on simulation movements
+ for simulation_moment in new:
+ simulation_movement.setDelivery(
+ simulation_movement.getDelivery().replace(
+ '%s/' % old_delivery_url, '%s/' % new_delivery_url))
+ reindex_path_list.append(simulation_movement.getRelativeUrl())
+ quantity_dict = {}
+ for simulation_movement in simulation_movement_list:
+ delivery_movement = simulation_movement.getDeliveryValue()
+ quantity_dict[delivery_movement] = \
+ quantity_dict.get(delivery_movement, 0) + \
+ simulation_movement.getQuantity()
+ for simulation_movement in simulation_movement_list:
+ delivery_movement = simulation_movement.getDeliveryValue()
+ total_quantity = quantity_dict[delivery_movement]
+ quantity = simulation_movement.getQuantity()
+ delivery_ratio = quantity / total_quantity
+ delivery_error = total_quantity * delivery_ratio - quantity
+ simulation_movement.edit(delivery_ratio=delivery_ratio,
+ delivery_error=delivery_error)
+ for movement, quantity in quantity_dict.iteritems():
+ movement.setQuantity(quantity)
+
+ assert delivery.getMovementList() and new_delivery.getMovementList()
+
+ # check if root applied rule exists and needs to be modified
+ if applied_rule is not None:
+ movement_list = [x.getRelativeUrl() for x in \
+ applied_rule.objectValues()]
+ new_root_simulation_movement_list = \
+ [x for x in new_simulation_movement_list if x in movement_list]
+ old_root_simulation_movement_list = \
+ [x for x in old_simulation_movement_list if x in movement_list]
+
+ if len(new_root_simulation_movement_list) == 0:
+ # we need to do nothing
+ pass
+ elif len(old_root_simulation_movement_list) == 0:
+ # we need to modify the causality to the new delivery
+ applied_rule.setCausality(new_delivery_url)
+ else:
+ # we need to split simulation movement tree
+ new_applied_rule = delivery.getPortalObject().portal_simulation.newContent(
+ portal_type='Applied Rule',
+ specialise=applied_rule.getSpecialise(),
+ causality=new_delivery_url)
+ id_list = [x.rsplit('/', 1)[-1] for x in \
+ new_root_simulation_movement_list]
+ cut_data = applied_rule.manage_cutObjects(id_list)
+ new_applied_rule.manage_pasteObjects(cut_data)
+ reindex_path_list = [\
+ x.replace('%s/' % applied_rule.getRelativeUrl(),
+ '%s/' % new_applied_rule.getRelativeUrl()) for x in \
+ reindex_path_list]
+
+ # Update variation category list
+ def _updateVariationCategoryList(document):
+ line_dict = {}
+ for movement in document.getMovementList():
+ parent = movement.getParentValue()
+ if getattr(parent, 'setVariationCategoryList', None) is not None:
+ line_dict.setdefault(parent, []).extend(
+ movement.getVariationCategoryList())
+ for line, category_list in line_dict.iteritems():
+ line.setVariationCategoryList(sorted(set(category_list)))
+ _updateVariationCategoryList(delivery)
+ _updateVariationCategoryList(new_delivery)
+
+ # Set comment on old and new delivery explaining what (and when) happened
+ doActionFor = delivery.getPortalObject().portal_workflow.doActionFor
+ doActionFor(delivery, 'edit_action', comment=translateString(
+ 'Split to Delivery ${new_delivery_url}',
+ mapping={'new_delivery_url':new_delivery_url}))
+ doActionFor(new_delivery, 'edit_action', comment=translateString(
+ 'Split from Delivery ${old_delivery_url}',
+ mapping={'old_delivery_url':old_delivery_url}))
+
+ # Update causality state
+ activate_kw = dict(after_tag_list=update_related_content_tag_list,
+ after_path_and_method_id=(reindex_path_list,
+ ('immediateReindexObject','recursiveImmediateReindexObject')))
+ delivery.activate(**activate_kw).updateCausalityState()
+ new_delivery.activate(**activate_kw).updateCausalityState()
+
+ # Update causality values
+ delivery.activate(**activate_kw).fixConsistency(
+ filter={'id':'causality_validity'})
+ new_delivery.activate(**activate_kw).fixConsistency(
+ filter={'id':'causality_validity'})
+ for related_value in delivery.getCausalityRelatedValueList():
+ if related_value.getPortalType() == 'Applied Rule':
+ continue
+ related_value.activate(**activate_kw).fixConsistency(
+ filter={'id':'causality_validity'})
+
+ # Finish solving
+ if self.getPortalObject().portal_workflow.isTransitionPossible(
+ self, 'succeed'):
+ self.succeed()
Modified: erp5/trunk/products/ERP5/tests/testPackingList.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/tests/testPackingList.py?rev=37088&r1=37087&r2=37088&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/tests/testPackingList.py [utf8] (original)
+++ erp5/trunk/products/ERP5/tests/testPackingList.py [utf8] Tue Jul 13 16:10:48 2010
@@ -1601,7 +1601,7 @@ class TestPackingList(TestPackingListMix
self._testSubContentReindexing(packing_list, [container, container_line,
container_cell])
-class TestAutomaticSolvingPackingList(TestPackingListMixin, ERP5TypeTestCase):
+class TestSolvingPackingList(TestPackingListMixin, ERP5TypeTestCase):
quiet = 0
def afterSetUp(self, quiet=1, run=1):
@@ -1661,6 +1661,34 @@ class TestAutomaticSolvingPackingList(Te
self.portal.portal_rules.default_delivery_simulation_rule.default_quantity_tester.edit(
solver=('portal_solvers/Automatic Quantity Adopt Solver',))
+ def stepSetUpMovementSplitSolver(self, sequence=None, sequence_list=None):
+ self._setUpTargetSolver('Movement Split Solver',
+ 'MovementSplitSolver', ())
+
+ def stepSplitMovementWithVariatedResources(self, sequence=None,
+ sequence_list=None):
+ packing_list = sequence.get('packing_list')
+ simulation_movement_list = sum(
+ [x.getDeliveryRelatedValueList() for x in \
+ packing_list.getMovementList()[:10]], [])
+ solver_process = self.portal.portal_solver_processes.newContent(
+ portal_type='Solver Process')
+ target_solver = solver_process.newContent(
+ portal_type='Movement Split Solver',
+ delivery_value_list=simulation_movement_list)
+ target_solver.solve()
+
+ def stepCheckSplitMovementWithVariatedResources(self, sequence=None,
+ sequence_list=None):
+ packing_list = sequence.get('packing_list')
+ order = packing_list.getCausalityValue()
+ new_packing_list = filter(lambda x:x != packing_list,
+ order.getCausalityRelatedValueList(
+ portal_type=packing_list.getPortalType()))[0]
+ self.assertEquals(len(packing_list.getMovementList()),
+ len(order.getMovementList()) - 10)
+ self.assertEquals(len(new_packing_list.getMovementList()), 10)
+
def test_01_PackingListDecreaseQuantity(self, quiet=quiet):
"""
Change the quantity on an delivery line, then
@@ -1703,6 +1731,30 @@ class TestAutomaticSolvingPackingList(Te
sequence_list.play(self, quiet=quiet)
+ def test_09_AddContainersWithVariatedResources(self, quiet=quiet):
+ sequence_list = SequenceList()
+
+ # Test with a order with cells
+ sequence_string = '\
+ stepSetUpMovementSplitSolver \
+ ' + self.variated_default_sequence + '\
+ stepAddPackingListContainer \
+ stepAddPackingListContainerLine \
+ stepSetContainerLineSmallQuantity \
+ stepCheckContainerLineSmallQuantity \
+ stepCheckPackingListIsNotPacked \
+ stepSetContainerFullQuantity \
+ stepTic \
+ stepCheckPackingListIsPacked \
+ stepSplitMovementWithVariatedResources \
+ stepTic \
+ stepCheckSplitMovementWithVariatedResources \
+ '
+ # XXX Check if there is a new packing list created
+ sequence_list.addSequenceString(sequence_string)
+
+ sequence_list.play(self, quiet=quiet)
+
class TestPurchasePackingListMixin(TestPackingListMixin):
"""Mixing class with steps to test purchase packing lists.
"""
@@ -1740,6 +1792,6 @@ class TestPurchasePackingList(TestPurcha
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestPackingList))
- suite.addTest(unittest.makeSuite(TestAutomaticSolvingPackingList))
+ suite.addTest(unittest.makeSuite(TestSolvingPackingList))
suite.addTest(unittest.makeSuite(TestPurchasePackingList))
return suite
More information about the Erp5-report
mailing list