diff --git a/product/ERP5/Document/DeliveryRule.py b/product/ERP5/Document/DeliveryRule.py
new file mode 100755
index 0000000000000000000000000000000000000000..56229d4636cd157d20d576a943fb3b8b0b54a357
--- /dev/null
+++ b/product/ERP5/Document/DeliveryRule.py
@@ -0,0 +1,243 @@
+##############################################################################
+#
+# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Jean-Paul Smets-Solane <jp@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
+from Products.ERP5.Document.Rule import Rule
+from Products.ERP5.ERP5Globals import movement_type_list, order_movement_type_list
+
+from zLOG import LOG
+
+class DeliveryRule(Rule):
+    """
+      Delivery Rule object make sure orphaned movements in a Delivery
+      (ie. movements which have no explanation in terms of order)
+      are part of the simulation process
+    """
+
+    # CMF Type Definition
+    meta_type = 'ERP5 Delivery Rule'
+    portal_type = 'Delivery Rule'
+    add_permission = Permissions.AddERP5Content
+    isPortalContent = 1
+    isRADContent = 1
+
+    # Declarative security
+    security = ClassSecurityInfo()
+    security.declareObjectProtected(Permissions.View)
+
+    # Default Properties
+    property_sheets = ( PropertySheet.Base
+                      , PropertySheet.XMLObject
+                      , PropertySheet.CategoryCore
+                      , PropertySheet.DublinCore
+                      )
+
+    # CMF Factory Type Information
+    factory_type_information = \
+      {    'id'             : portal_type
+         , 'meta_type'      : meta_type
+         , 'description'    : """\
+An ERP5 Rule..."""
+         , 'icon'           : 'rule_icon.gif'
+         , 'product'        : 'ERP5'
+         , 'factory'        : 'addDeliveryRule'
+         , 'immediate_view' : 'rule_view'
+         , 'allow_discussion'     : 1
+         , 'allowed_content_types': ()
+         , 'filter_content_types' : 1
+         , 'global_allow'   : 1
+         , 'actions'        :
+        ( { 'id'            : 'view'
+          , 'name'          : 'View'
+          , 'category'      : 'object_view'
+          , 'action'        : 'rule_view'
+          , 'permissions'   : (
+              Permissions.View, )
+          }
+        , { 'id'            : 'list'
+          , 'name'          : 'Object Contents'
+          , 'category'      : 'object_action'
+          , 'action'        : 'folder_contents'
+          , 'permissions'   : (
+              Permissions.View, )
+          }
+        , { 'id'            : 'print'
+          , 'name'          : 'Print'
+          , 'category'      : 'object_print'
+          , 'action'        : 'rule_print'
+          , 'permissions'   : (
+              Permissions.View, )
+          }
+        , { 'id'            : 'metadata'
+          , 'name'          : 'Metadata'
+          , 'category'      : 'object_view'
+          , 'action'        : 'metadata_edit'
+          , 'permissions'   : (
+              Permissions.View, )
+          }
+        , { 'id'            : 'translate'
+          , 'name'          : 'Translate'
+          , 'category'      : 'object_action'
+          , 'action'        : 'translation_template_view'
+          , 'permissions'   : (
+              Permissions.TranslateContent, )
+          }
+        )
+      }
+
+    def test(self, movement):
+      """
+        Tests if the rule (still) applies
+      """
+      # A deliveyr rule never applies since it is always explicitely instanciated
+      return 0
+
+    # Simulation workflow
+    security.declareProtected(Permissions.ModifyPortalContent, 'expand')
+    def expand(self, applied_rule):
+      """
+        Expands the current movement downwards.
+
+        -> new status -> expanded
+
+        An applied rule can be expanded only if its parent movement
+        is expanded.
+      """
+      delivery_line_type = 'Simulation Movement'
+
+      # Get the delivery when we come from
+      my_delivery = applied_rule.getDefaultCausalityValue() # Causality is a kind of Delivery (ex. Packing List)
+
+      # Only expand if my_delivery is not None and state is not 'confirmed'
+      if my_delivery is not None:
+        if my_delivery.getSimulationState() not in ('delivered', ):
+          # First, check each contained movement and make
+          # a list of delivery ids which do not need to be copied
+          # eventually delete movement which do not exist anylonger
+          existing_uid_list = []
+          for movement in applied_rule.contentValues(filter={'portal_type':movement_type_list}):
+            delivery_value = movement.getDeliveryValue(portal_type=order_movement_type_list)
+            if delivery_value is None:
+              movement.flushActivity(invoke=0)
+              applied_rule._delObject(movement.getId())
+            else:
+              if getattr(delivery_value, 'isCell', 0):
+                if len(delivery_value.getDeliveryRelatedValueList()) > 1:
+                  # Our delivery_value is already related to another simulation movement
+                  # Delete ourselve
+                  movement.flushActivity(invoke=0)
+                  applied_rule._delObject(movement.getId())
+                else:
+                  existing_uid_list += [delivery_value.getUid()]
+              elif delivery_value.hasCellContent():
+                # Do not keep head of cells
+                delivery_value.flushActivity(invoke=0)
+                applied_rule._delObject(movement.getId())
+              else:
+                if len(delivery_value.getDeliveryRelatedValueList()) > 1:
+                  # Our delivery_value is already related to another simulation movement
+                  # Delete ourselve
+                  movement.flushActivity(invoke=0)
+                  applied_rule._delObject(movement.getId())
+                else:
+                  existing_uid_list += [delivery_value.getUid()]
+
+          # Copy each movement (line or cell) from the delivery is that
+          for delivery_line_object in my_delivery.contentValues(filter={'portal_type':movement_type_list}):
+            try:
+              if delivery_line_object.hasCellContent():
+                for c in delivery_line_object.getCellValueList():
+                  if len(c.getDeliveryRelatedValueList()) == 0: # Only create if orphaned movement
+                    if c.getUid() not in existing_uid_list:
+                      new_id = delivery_line_object.getId() + '_' + c.getId()
+                      my_delivery.portal_types.constructContent(type_name=delivery_line_type,
+                          container=applied_rule,
+                          id=new_id,
+                          delivery_value = c
+                      )
+                      my_delivery.flushActivity(invoke=1) # Flush since we may need immediately getDeliveryRelatedValueList
+              else:
+                if len(delivery_line_object.getDeliveryRelatedValueList()) == 0: # Only create if orphaned movement
+                  if delivery_line_object.getUid() not in existing_uid_list:
+                    new_id = delivery_line_object.getId()
+                    my_delivery.portal_types.constructContent(type_name=delivery_line_type,
+                        container=applied_rule,
+                        id=new_id,
+                        delivery_value = delivery_line_object
+                    )
+                    # Source, Destination, Quantity, Date, etc. are
+                    # acquired from the delivery line and need not to be copied.
+            except AttributeError:
+              LOG('ERP5: WARNING', 0, 'AttributeError during expand on delivery line %s'
+                                                      % delivery_line_object.absolute_url())
+
+      # Pass to base class
+      Rule.expand(self, applied_rule)
+
+    security.declareProtected(Permissions.ModifyPortalContent, 'solve')
+    def solve(self, applied_rule, solution_list):
+      """
+        Solve inconsitency according to a certain number of solutions
+        templates. This updates the
+
+        -> new status -> solved
+
+        This applies a solution to an applied rule. Once
+        the solution is applied, the parent movement is checked.
+        If it does not diverge, the rule is reexpanded. If not,
+        diverge is called on the parent movement.
+      """
+
+    security.declareProtected(Permissions.ModifyPortalContent, 'diverge')
+    def diverge(self, applied_rule):
+      """
+        -> new status -> diverged
+
+        This basically sets the rule to "diverged"
+        and blocks expansion process
+      """
+
+    # Solvers
+    security.declareProtected(Permissions.View, 'isDivergent')
+    def isDivergent(self, applied_rule):
+      """
+        Returns 1 if divergent rule
+      """
+
+    security.declareProtected(Permissions.View, 'getDivergenceList')
+    def getDivergenceList(self, applied_rule):
+      """
+        Returns a list Divergence descriptors
+      """
+
+    security.declareProtected(Permissions.View, 'getSolverList')
+    def getSolverList(self, applied_rule):
+      """
+        Returns a list Divergence solvers
+      """
diff --git a/product/ERP5/Document/Order.py b/product/ERP5/Document/Order.py
index 09a450cc0cb9803d4869e6037df019f5b052d016..cc0b60451d29fe1164f77e0a3366600ebe1ace48 100755
--- a/product/ERP5/Document/Order.py
+++ b/product/ERP5/Document/Order.py
@@ -115,7 +115,6 @@ An order..."""
         )
       }
 
-
     security.declarePrivate( '_edit' )
     def _edit(self, REQUEST=None, force_update = 0, **kw):
       Delivery._edit(self, REQUEST=REQUEST, force_update = force_update, **kw)