[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