Commit ec87d8a7 authored by Sebastien Robin's avatar Sebastien Robin Committed by Xiaowu Zhang

simulation: fully review the way to merge deliveries and enable merge delivery action

Fully rewrite portal_simulation.mergeDeliveryList to use builders to reconstruct new
merged delivery.

Add parameter "merge_delivery" to builder. This parameter is used when
merge should be done in such a way that movement group at delivery
level are ignored
parent 74cbad5a
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Delivery_mergeDeliveryList</string> </value> <value> <string>DeliveryModule_mergeDeliveryList</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Delivery_cleanDeliveryAfterMerge</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>merge_delivery_list</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Merge Internal Packing Lists</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/DeliveryModule_mergeDeliveryList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>merge_delivery_list</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Merge Purchase Packing Lists</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/DeliveryModule_mergeDeliveryList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>merge_delivery_list</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Merge Sale Packing Lists</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/DeliveryModule_mergeDeliveryList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Delivery_mergeDeliveryList</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list>
<string>listbox</string>
</list>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Delivery_viewMergedDeliveryList</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>Delivery_viewMergedDeliveryList</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_dialog</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Merge Deliveries</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>sort</string>
<string>title</string>
<string>lines</string>
<string>list_action</string>
<string>selection_name</string>
<string>columns</string>
<string>list_method</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>list_method</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>selection_name</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>columns</string> </key>
<value>
<list>
<tuple>
<string>id</string>
<string>ID</string>
</tuple>
<tuple>
<string>portal_type</string>
<string>Type</string>
</tuple>
<tuple>
<string>title</string>
<string>Title</string>
</tuple>
<tuple>
<string>relative_url</string>
<string>Path</string>
</tuple>
<tuple>
<string>description</string>
<string>Description</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_listbox</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewTradeFieldLibrary</string> </value>
</item>
<item>
<key> <string>lines</string> </key>
<value> <int>30</int> </value>
</item>
<item>
<key> <string>list_action</string> </key>
<value> <string>Folder_viewContentList</string> </value>
</item>
<item>
<key> <string>selection_name</string> </key>
<value> <string>delivery_selection</string> </value>
</item>
<item>
<key> <string>sort</string> </key>
<value>
<list>
<tuple>
<string>relative_url</string>
<string>relative_url</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Merged Deliveries</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python:None</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>here/REQUEST/form/selection_name</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
erp5_core (>= 5.4.3) erp5_core (>= 5.4.3)
erp5_base erp5_base
erp5_pdm erp5_pdm
\ No newline at end of file erp5_simulation
\ No newline at end of file
...@@ -35,6 +35,7 @@ Internal Packing List Line | quantity_view ...@@ -35,6 +35,7 @@ Internal Packing List Line | quantity_view
Internal Packing List Line | view Internal Packing List Line | view
Internal Packing List Line | view_price Internal Packing List Line | view_price
Internal Packing List Module | delivery_line_report Internal Packing List Module | delivery_line_report
Internal Packing List Module | merge_delivery_list
Internal Packing List Module | view Internal Packing List Module | view
Internal Packing List Module | workflow_report Internal Packing List Module | workflow_report
Internal Packing List | details Internal Packing List | details
...@@ -106,6 +107,7 @@ Purchase Packing List Line | quantity_view ...@@ -106,6 +107,7 @@ Purchase Packing List Line | quantity_view
Purchase Packing List Line | view Purchase Packing List Line | view
Purchase Packing List Line | view_price Purchase Packing List Line | view_price
Purchase Packing List Module | delivery_line_report Purchase Packing List Module | delivery_line_report
Purchase Packing List Module | merge_delivery_list
Purchase Packing List Module | packing_list_export Purchase Packing List Module | packing_list_export
Purchase Packing List Module | packing_list_report Purchase Packing List Module | packing_list_report
Purchase Packing List Module | view Purchase Packing List Module | view
...@@ -232,6 +234,7 @@ Sale Packing List Line | profile_view ...@@ -232,6 +234,7 @@ Sale Packing List Line | profile_view
Sale Packing List Line | quantity_view Sale Packing List Line | quantity_view
Sale Packing List Line | view Sale Packing List Line | view
Sale Packing List Module | delivery_line_report Sale Packing List Module | delivery_line_report
Sale Packing List Module | merge_delivery_list
Sale Packing List Module | packing_list_export Sale Packing List Module | packing_list_export
Sale Packing List Module | packing_list_report Sale Packing List Module | packing_list_report
Sale Packing List Module | shipment_report Sale Packing List Module | shipment_report
......
...@@ -41,7 +41,7 @@ class CausalityAssignmentMovementGroup(MovementGroup): ...@@ -41,7 +41,7 @@ class CausalityAssignmentMovementGroup(MovementGroup):
def _getPropertyDict(self, movement, **kw): def _getPropertyDict(self, movement, **kw):
return self._addCausalityToEdit(movement) return self._addCausalityToEdit(movement)
def _separate(self, movement_list): def _separate(self, movement_list, **kw):
if not movement_list: if not movement_list:
return [] return []
property_dict = {} property_dict = {}
......
...@@ -81,12 +81,19 @@ class MovementGroup(XMLObject): ...@@ -81,12 +81,19 @@ class MovementGroup(XMLObject):
# This method should be defined in sub classes. # This method should be defined in sub classes.
raise NotImplementedError raise NotImplementedError
def _separate(self, movement_list): def _separate(self, movement_list, merge_delivery=False, **kw):
# By default, we separate movements by _getPropertyDict() values. # By default, we separate movements by _getPropertyDict() values.
# You can override this method in each MovementGroup class. # You can override this method in each MovementGroup class.
tmp_dict = {} tmp_dict = {}
first_property_dict = None
collect_order_group_id = self.getCollectOrderGroupId()
for movement in movement_list: for movement in movement_list:
property_dict = self._getPropertyDict(movement) # We are in the case of merging of deliveries, thus if the movement
# is configured to not split, just take properties of the first movement
if merge_delivery and collect_order_group_id == "delivery":
property_dict = {}
else:
property_dict = self._getPropertyDict(movement, **kw)
# XXX it can be wrong. we need a good way to get hash value, or # XXX it can be wrong. we need a good way to get hash value, or
# we should compare for all pairs. # we should compare for all pairs.
key = repr(property_dict) key = repr(property_dict)
...@@ -96,11 +103,11 @@ class MovementGroup(XMLObject): ...@@ -96,11 +103,11 @@ class MovementGroup(XMLObject):
tmp_dict[key] = [[movement], property_dict] tmp_dict[key] = [[movement], property_dict]
return tmp_dict.values() return tmp_dict.values()
def separate(self, movement_list): def separate(self, movement_list, **kw):
# We sort group of simulation movements by their IDs. # We sort group of simulation movements by their IDs.
# DO NOT OVERRIDE THIS METHOD. Override _separate() instead. # DO NOT OVERRIDE THIS METHOD. Override _separate() instead.
return sorted([[sorted(x[0], key=lambda x: x.getId()), x[1]] \ return sorted([[sorted(x[0], key=lambda x: x.getId()), x[1]] \
for x in self._separate(movement_list)], for x in self._separate(movement_list, **kw)],
key=lambda x: x[0][0].getId()) key=lambda x: x[0][0].getId())
def isBranch(self): def isBranch(self):
......
...@@ -51,7 +51,7 @@ class PropertyAssignmentMovementGroup(MovementGroup): ...@@ -51,7 +51,7 @@ class PropertyAssignmentMovementGroup(MovementGroup):
# We can always update. # We can always update.
return True, property_dict return True, property_dict
def _separate(self, movement_list): def _separate(self, movement_list, **kw):
if not movement_list: if not movement_list:
return [] return []
......
...@@ -44,7 +44,7 @@ class PropertyGroupingMovementGroup(MovementGroup): ...@@ -44,7 +44,7 @@ class PropertyGroupingMovementGroup(MovementGroup):
return True, {} return True, {}
return False, {} return False, {}
def _separate(self, movement_list): def _separate(self, movement_list, **kw):
if not movement_list: if not movement_list:
return [] return []
......
...@@ -61,7 +61,7 @@ class QuantitySignMovementGroup(MovementGroup): ...@@ -61,7 +61,7 @@ class QuantitySignMovementGroup(MovementGroup):
property_dict['quantity_sign'] = cmp(quantity, 0) property_dict['quantity_sign'] = cmp(quantity, 0)
return property_dict return property_dict
def _separate(self, movement_list): def _separate(self, movement_list, **kw):
if not movement_list: if not movement_list:
return [] return []
......
...@@ -57,5 +57,5 @@ class SplitMovementGroup(MovementGroup): ...@@ -57,5 +57,5 @@ class SplitMovementGroup(MovementGroup):
def test(self, document, property_dict, **kw): def test(self, document, property_dict, **kw):
return True, property_dict return True, property_dict
def _separate(self, movement_list): def _separate(self, movement_list, **kw):
return [[[movement], {}] for movement in movement_list] return [[[movement], {}] for movement in movement_list]
...@@ -41,13 +41,15 @@ class MovementGroupNode: ...@@ -41,13 +41,15 @@ class MovementGroupNode:
# a separate method requests separating movements. # a separate method requests separating movements.
def __init__(self, movement_group_list=None, movement_list=None, def __init__(self, movement_group_list=None, movement_list=None,
last_line_movement_group=None, last_line_movement_group=None,
separate_method_name_list=[], movement_group=None): separate_method_name_list=[], movement_group=None,
merge_delivery=None):
self._movement_list = [] self._movement_list = []
self._group_list = [] self._group_list = []
self._movement_group = movement_group self._movement_group = movement_group
self._movement_group_list = movement_group_list self._movement_group_list = movement_group_list
self._last_line_movement_group = last_line_movement_group self._last_line_movement_group = last_line_movement_group
self._separate_method_name_list = separate_method_name_list self._separate_method_name_list = separate_method_name_list
self._merge_delivery = merge_delivery
if movement_list is not None : if movement_list is not None :
self.append(movement_list) self.append(movement_list)
...@@ -56,22 +58,24 @@ class MovementGroupNode: ...@@ -56,22 +58,24 @@ class MovementGroupNode:
movement_group=self._movement_group_list[0], movement_group=self._movement_group_list[0],
movement_group_list=self._movement_group_list[1:], movement_group_list=self._movement_group_list[1:],
last_line_movement_group=self._last_line_movement_group, last_line_movement_group=self._last_line_movement_group,
separate_method_name_list=self._separate_method_name_list) separate_method_name_list=self._separate_method_name_list,
merge_delivery=self._merge_delivery)
nested_instance.setGroupEdit(**property_dict) nested_instance.setGroupEdit(**property_dict)
split_movement_list = nested_instance.append(movement_list) split_movement_list = nested_instance.append(movement_list)
self._group_list.append(nested_instance) self._group_list.append(nested_instance)
return split_movement_list return split_movement_list
def append(self, movement_list): def append(self, movement_list, **kw):
all_split_movement_list = [] all_split_movement_list = []
if len(self._movement_group_list): if len(self._movement_group_list):
for separate_movement_list, property_dict in \ for separate_movement_list, property_dict in \
self._movement_group_list[0].separate(movement_list): self._movement_group_list[0].separate(movement_list,
merge_delivery=self._merge_delivery):
split_movement_list = self._appendGroup(separate_movement_list, split_movement_list = self._appendGroup(separate_movement_list,
property_dict) property_dict)
if len(split_movement_list): if len(split_movement_list):
if self._movement_group == self._last_line_movement_group: if self._movement_group == self._last_line_movement_group:
self.append(split_movement_list) self.append(split_movement_list, **kw)
else: else:
all_split_movement_list.extend(split_movement_list) all_split_movement_list.extend(split_movement_list)
else: else:
......
...@@ -33,6 +33,7 @@ from AccessControl import ClassSecurityInfo ...@@ -33,6 +33,7 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Globals import InitializeClass, DTMLFile from Products.ERP5Type.Globals import InitializeClass, DTMLFile
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ERP5Type.Tool.BaseTool import BaseTool from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
from Products.ERP5 import _dtmldir from Products.ERP5 import _dtmldir
...@@ -2653,8 +2654,20 @@ class SimulationTool(BaseTool): ...@@ -2653,8 +2654,20 @@ class SimulationTool(BaseTool):
return result return result
# Used for mergeDeliveryList. def _findBuilderForDelivery(self, delivery):
class MergeDeliveryListError(Exception): pass """
Find out the builder corresponding to a delivery by looking at the business process
"""
builder = None
portal_type = delivery.getPortalType()
for business_link in delivery.asComposedDocument().objectValues(portal_type="Business Link"):
for business_link_builder in business_link.getDeliveryBuilderValueList():
if business_link_builder.getDeliveryPortalType() == portal_type:
builder = business_link_builder
break
if builder is not None:
break
return builder
security.declareProtected( Permissions.ModifyPortalContent, 'mergeDeliveryList' ) security.declareProtected( Permissions.ModifyPortalContent, 'mergeDeliveryList' )
def mergeDeliveryList(self, delivery_list): def mergeDeliveryList(self, delivery_list):
...@@ -2663,246 +2676,102 @@ class SimulationTool(BaseTool): ...@@ -2663,246 +2676,102 @@ class SimulationTool(BaseTool):
All delivery lines are merged into the first one. All delivery lines are merged into the first one.
The first one is therefore called main_delivery here. The first one is therefore called main_delivery here.
The others are cancelled. The others are cancelled.
Return the main delivery.
""" """
# Sanity checks. # Sanity checks.
if len(delivery_list) == 0: if not(len(delivery_list) >=2):
raise self.MergeDeliveryListError, "No delivery is passed" raise ValueError("Please select at least 2 deliveries")
elif len(delivery_list) == 1: portal= self.getPortalObject()
raise self.MergeDeliveryListError, "Only one delivery is passed" translateString = portal.Base_translateString
error_list = []
main_delivery = delivery_list[0] if len(delivery_list) > 1:
delivery_list = delivery_list[1:] portal_type_set = set([x.getPortalType() for x in delivery_list])
if len(portal_type_set) != 1:
# Another sanity check. It is necessary for them to be identical in some attributes. error_list.append(translateString("Please select only deliveries of same type"))
for delivery in delivery_list: else:
for attr in ('portal_type', 'simulation_state', portal_type, = portal_type_set
'source', 'destination', allowed_state_set = set(portal.getPortalReservedInventoryStateList() + \
'source_section', 'destination_section', portal.getPortalFutureInventoryStateList())
'source_decision', 'destination_decision', found_state_set = set([x.getSimulationState() for x in delivery_list])
'source_administration', 'destination_administration', if found_state_set.difference(allowed_state_set):
'source_payment', 'destination_payment'): error_list.append(translateString("Found delivery having unexpected status for merge"))
main_value = main_delivery.getProperty(attr)
value = delivery.getProperty(attr)
if main_value != value:
raise self.MergeDeliveryListError, \
"%s is not the same between %s and %s (%s and %s)" % (attr, delivery.getId(), main_delivery.getId(), value, main_value)
# One more sanity check. Check if discounts are the same, if any.
main_discount_list = main_delivery.contentValues(filter = {'portal_type': self.getPortalDiscountTypeList()})
for delivery in delivery_list:
discount_list = delivery.contentValues(filter = {'portal_type': self.getPortalDiscountTypeList()})
if len(main_discount_list) != len(discount_list):
raise self.MergeDeliveryListError, "Discount is not the same between %s and %s" % (delivery.getId(), main_delivery.getId())
for discount in discount_list:
for main_discount in main_discount_list:
if discount.getDiscount() == main_discount.getDiscount() \
and discount.getDiscountRatio() == main_discount.getDiscountRatio() \
and discount.getDiscountType() == main_discount.getDiscountType() \
and discount.getImmediateDiscount() == main_discount.getImmediateDiscount():
break
else:
raise self.MergeDeliveryListError, "Discount is not the same between %s and %s" % (delivery.getId(), main_delivery.getId())
# One more sanity check. Check if payment conditions are the same, if any.
main_payment_condition_list = main_delivery.contentValues(filter = {'portal_type': self.getPortalPaymentConditionTypeList()})
for delivery in delivery_list:
payment_condition_list = delivery.contentValues(filter = {'portal_type': self.getPortalPaymentConditionTypeList()})
if len(main_payment_condition_list) != len(payment_condition_list):
raise self.MergeDeliveryListError, "Payment Condition is not the same between %s and %s" % (delivery.getId(), main_delivery.getId())
for condition in payment_condition_list:
for main_condition in main_payment_condition_list:
if condition.getPaymentMode() == main_condition.getPaymentMode() \
and condition.getPaymentAdditionalTerm() == main_condition.getPaymentAdditionalTerm() \
and condition.getPaymentAmount() == main_condition.getPaymentAmount() \
and condition.getPaymentEndOfMonth() == main_condition.getPaymentEndOfMonth() \
and condition.getPaymentRatio() == main_condition.getPaymentRatio() \
and condition.getPaymentTerm() == main_condition.getPaymentTerm():
break
else: else:
raise self.MergeDeliveryListError, "Payment Condition is not the same between %s and %s" % (delivery.getId(), main_delivery.getId()) # Allow to call a script to do custom checking conditions before merge
main_delivery = delivery_list[0]
# Make sure that all activities are flushed, to get simulation movements from delivery cells. check_merge_condition_method = main_delivery._getTypeBasedMethod("checkMergeConditionOnDeliveryList")
for delivery in delivery_list: if check_merge_condition_method is not None:
for order in delivery.getCausalityValueList(portal_type = self.getPortalOrderTypeList()): error_list.extend(check_merge_condition_method(delivery_list=delivery_list))
for applied_rule in order.getCausalityRelatedValueList(portal_type = 'Applied Rule'): if len(error_list) == 0:
applied_rule.flushActivity(invoke = 1) # so far so good
for causality_related_delivery in delivery.getCausalityValueList(portal_type = self.getPortalDeliveryTypeList()): # in delivery_list we have list of delivery to merge
for applied_rule in causality_related_delivery.getCausalityRelatedValueList(portal_type = 'Applied Rule'): simulation_movement_list = []
applied_rule.flushActivity(invoke = 1) to_copy_delivery_line_list = [] # for lines not coming from upper simulation, thus
# created by hand should be manually added to main
# Get a list of simulated movements and invoice movements. # delivery since they are not coming from builder
main_simulated_movement_list = main_delivery.getSimulatedMovementList() for delivery in delivery_list:
main_invoice_movement_list = main_delivery.getInvoiceMovementList() line_id_to_delete_list = []
simulated_movement_list = main_simulated_movement_list[:] for movement in delivery.getMovementList():
invoice_movement_list = main_invoice_movement_list[:] related_simulation_movement_list = movement.getDeliveryRelatedValueList()
for delivery in delivery_list: for simulation_movement in related_simulation_movement_list:
simulated_movement_list.extend(delivery.getSimulatedMovementList()) # if we are on a root applied rule directly, so in the case of
invoice_movement_list.extend(delivery.getInvoiceMovementList()) # a manually added line, we have to copy
# the simulation movement into to main delivery
#for movement in simulated_movement_list + invoice_movement_list: if simulation_movement.getParentValue().getParentValue().getId() == "portal_simulation":
# parent = movement.aq_parent # For manually added lines, make sure we have only one simulation movement
# LOG('mergeDeliveryList', 0, 'movement = %s, parent = %s, movement.getPortalType() = %s, parent.getPortalType() = %s' % (repr(movement), repr(parent), repr(movement.getPortalType()), repr(parent.getPortalType()))) assert len(related_simulation_movement_list) == 1
if not(delivery is main_delivery):
LOG('mergeDeliveryList', 0, 'simulated_movement_list = %s, invoice_movement_list = %s' % (str(simulated_movement_list), str(invoice_movement_list))) to_copy_delivery_line_list.append(movement)
for main_movement_list, movement_list in \ else:
((main_simulated_movement_list, simulated_movement_list), simulation_movement.setDeliveryValue(None)
(main_invoice_movement_list, invoice_movement_list)): simulation_movement_list.append(simulation_movement)
root_group = self.collectMovement(movement_list, # Since we keep the main delivery, we remove existing lines already
check_order = 0, # coming from builder to let builder recreate them in the same time
check_path = 0, # as other ones (to possibly merge lines also)
check_date = 0, movement_id = movement.getId()
check_criterion = 1, if delivery is main_delivery and not(movement_id in line_id_to_delete_list):
check_resource = 1, line_id_to_delete_list.append(movement.getId())
check_base_variant = 1, if line_id_to_delete_list:
check_variant = 1) delivery.manage_delObjects(ids=line_id_to_delete_list)
for criterion_group in root_group.group_list: # It is required to expand again simulation movement, because
LOG('mergeDeliveryList dump tree', 0, 'criterion = %s, movement_list = %s, group_list = %s' % (repr(criterion_group.criterion), repr(criterion_group.movement_list), repr(criterion_group.group_list))) # we unlinked them from delivery, so it is possible that some
for resource_group in criterion_group.group_list: # properties will change on simulation movement (mostly categories).
LOG('mergeDeliveryList dump tree', 0, 'resource = %s, movement_list = %s, group_list = %s' % (repr(resource_group.resource), repr(resource_group.movement_list), repr(resource_group.group_list))) # By expanding again, we will avoid having many deliveries instead
for base_variant_group in resource_group.group_list: # of one when doing "merge"
LOG('mergeDeliveryList dump tree', 0, 'base_category_list = %s, movement_list = %s, group_list = %s' % (repr(base_variant_group.base_category_list), repr(base_variant_group.movement_list), repr(base_variant_group.group_list))) for simulation_movement in simulation_movement_list:
for variant_group in base_variant_group.group_list: simulation_movement.expand(expand_policy='immediate')
LOG('mergeDeliveryList dump tree', 0, 'category_list = %s, movement_list = %s, group_list = %s' % (repr(variant_group.category_list), repr(variant_group.movement_list), repr(variant_group.group_list)))
# activate builder
for criterion_group in root_group.group_list: merged_builder = self._findBuilderForDelivery(main_delivery)
for resource_group in criterion_group.group_list: if merged_builder is None:
for base_variant_group in resource_group.group_list: error_list.append(translateString("Unable to find builder"))
# Get a list of categories. else:
category_dict = {} merged_builder.build(movement_relative_url_list=[q.getRelativeUrl() for q in \
for variant_group in base_variant_group.group_list: simulation_movement_list], merge_delivery=True,
for category in variant_group.category_list: delivery_relative_url_list=[main_delivery.getRelativeUrl()])
category_dict[category] = 1 # Finally, copy all lines that were created manually on all deliveries except
category_list = category_dict.keys() # the main one
@UnrestrictedMethod
# Try to find a delivery line. def setMainDeliveryModifiable(delivery):
delivery_line = None # set causality state in such way we can modify delivery
for movement in base_variant_group.movement_list: delivery.diverge()
if movement in main_movement_list: setMainDeliveryModifiable(main_delivery)
if movement.aq_parent.getPortalType() in self.getPortalSimulatedMovementTypeList() \ delivery_type_list = portal.getPortalDeliveryTypeList()
or movement.aq_parent.getPortalType() in self.getPortalInvoiceMovementTypeList(): for delivery_line in to_copy_delivery_line_list:
delivery_line = movement.aq_parent delivery = delivery_line.getParentValue()
else: if not(delivery.getPortalType() in delivery_type_list):
delivery_line = movement raise NotImplementedError("Merge of deliveries doe not yet handle case of cells")
LOG('mergeDeliveryList', 0, 'delivery_line %s is found: criterion = %s, resource = %s, base_category_list = %s' % (repr(delivery_line), repr(criterion_group.criterion), repr(resource_group.resource), repr(base_variant_group.base_category_list))) copy_data = delivery.manage_copyObjects(ids=[delivery_line.getId()])
break main_delivery.manage_pasteObjects(copy_data)
main_delivery.updateCausalityState()
if delivery_line is None:
# Not found. So create a new delivery line. # Finally do cleanup
movement = base_variant_group.movement_list[0] for delivery in delivery_list[1:]:
if movement.aq_parent.getPortalType() in self.getPortalSimulatedMovementTypeList() \ # cancel, delete - to disallow any user related operations on those deliveries
or movement.aq_parent.getPortalType() in self.getPortalInvoiceMovementTypeList(): after_merge_method = delivery._getTypeBasedMethod('cleanDeliveryAfterMerge')
delivery_line_type = movement.aq_parent.getPortalType() if after_merge_method is not None:
else: after_merge_method()
delivery_line_type = movement.getPortalType() else:
delivery_line = main_delivery.newContent(portal_type = delivery_line_type, error_list.append(translateString("Please select at least two deliveries"))
resource = resource_group.resource) return error_list
LOG('mergeDeliveryList', 0, 'New delivery_line %s is created: criterion = %s, resource = %s, base_category_list = %s' % (repr(delivery_line), repr(criterion_group.criterion), repr(resource_group.resource), repr(base_variant_group.base_category_list)))
# Update the base categories and categories.
#LOG('mergeDeliveryList', 0, 'base_category_list = %s, category_list = %s' % (repr(base_category_list), repr(category_list)))
delivery_line.setVariationBaseCategoryList(base_variant_group.base_category_list)
delivery_line.setVariationCategoryList(category_list)
object_to_update = None
for variant_group in base_variant_group.group_list:
if len(variant_group.category_list) == 0:
object_to_update = delivery_line
else:
for delivery_cell in delivery_line.contentValues():
predicate_value_list = delivery_cell.getPredicateValueList()
LOG('mergeDeliveryList', 0, 'delivery_cell = %s, predicate_value_list = %s, variant_group.category_list = %s' % (repr(delivery_cell), repr(predicate_value_list), repr(variant_group.category_list)))
if len(predicate_value_list) == len(variant_group.category_list):
for category in variant_group.category_list:
if category not in predicate_value_list:
break
else:
object_to_update = delivery_cell
break
#LOG('mergeDeliveryList', 0, 'object_to_update = %s' % repr(object_to_update))
if object_to_update is not None:
cell_price = object_to_update.getPrice() or 0.0
cell_quantity = object_to_update.getQuantity() or 0.0
cell_target_quantity = object_to_update.getNetConvertedTargetQuantity() or 0.0 # XXX What to do ?
cell_total_price = cell_target_quantity * cell_price
cell_category_list = list(object_to_update.getCategoryList())
for movement in variant_group.movement_list:
if movement in main_movement_list:
continue
LOG('mergeDeliveryList', 0, 'movement = %s' % repr(movement))
cell_quantity += movement.getQuantity()
cell_target_quantity += movement.getNetConvertedTargetQuantity()
try:
# XXX WARNING - ADD PRICED QUANTITY
cell_price = movement.getPrice()
cell_total_price += movement.getNetConvertedTargetQuantity() * cell_price
except TypeError:
cell_total_price = None
for category in movement.getCategoryList():
if category not in cell_category_list:
cell_category_list.append(category)
# Make sure that simulation movements point to an appropriate delivery line or
# delivery cell.
if hasattr(movement, 'getDeliveryRelatedValueList'):
for simulation_movement in \
movement.getDeliveryRelatedValueList(portal_type = 'Simulation Movement'):
simulation_movement.setDeliveryValue(object_to_update)
#simulation_movement.reindexObject()
if hasattr(movement, 'getOrderRelatedValueList'):
for simulation_movement in \
movement.getOrderRelatedValueList(portal_type = 'Simulation Movement'):
simulation_movement.setOrderValue(object_to_update)
#simulation_movement.reindexObject()
if cell_target_quantity != 0 and cell_total_price is not None:
average_price = cell_total_price / cell_target_quantity
else:
average_price = 0
LOG('mergeDeliveryList', 0, 'object_to_update = %s, cell_category_list = %s, cell_target_quantity = %s, cell_quantity = %s, average_price = %s' % (repr(object_to_update), repr(cell_category_list), repr(cell_target_quantity), repr(cell_quantity), repr(average_price)))
object_to_update.setCategoryList(cell_category_list)
if object_to_update.getPortalType() in self.getPortalSimulatedMovementTypeList():
object_to_update.edit(target_quantity = cell_target_quantity,
quantity = cell_quantity,
price = average_price,
)
elif object_to_update.getPortalType() in self.getPortalInvoiceMovementTypeList():
# Invoices do not have target quantities, and the price never change.
object_to_update.edit(quantity = cell_quantity,
price = cell_price,
)
else:
raise self.MergeDeliveryListError, "Unknown portal type %s" % str(object_to_update.getPortalType())
else:
raise self.MergeDeliveryListError, "No object to update"
# Merge containers. Just copy them from other deliveries into the main.
for delivery in delivery_list:
container_id_list = delivery.contentIds(filter = {'portal_type': self.getPortalContainerTypeList()})
if len(container_id_list) > 0:
copy_data = delivery.manage_copyObjects(ids = container_id_list)
new_id_list = main_delivery.manage_pasteObjects(copy_data)
# Unify the list of causality.
causality_list = main_delivery.getCausalityValueList()
for delivery in delivery_list:
for causality in delivery.getCausalityValueList():
if causality not in causality_list:
causality_list.append(causality)
LOG("mergeDeliveryList", 0, "causality_list = %s" % str(causality_list))
main_delivery.setCausalityValueList(causality_list)
# Cancel deliveries.
for delivery in delivery_list:
LOG("mergeDeliveryList", 0, "cancelling %s" % repr(delivery))
delivery.cancel()
# Reindex the main delivery.
main_delivery.reindexObject()
return main_delivery
####################################################### #######################################################
# Sequence # Sequence
......
...@@ -104,7 +104,8 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -104,7 +104,8 @@ class BuilderMixin(XMLObject, Amount, Predicate):
security.declarePublic('build') security.declarePublic('build')
def build(self, applied_rule_uid=None, movement_relative_url_list=None, def build(self, applied_rule_uid=None, movement_relative_url_list=None,
delivery_relative_url_list=None, movement_list=None, delivery_relative_url_list=None, movement_list=None,
explanation=None, business_link=None, activate_kw=None, **kw): explanation=None, business_link=None, activate_kw=None,
merge_delivery=None, **kw):
""" """
Build deliveries from a list of movements Build deliveries from a list of movements
...@@ -139,13 +140,13 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -139,13 +140,13 @@ class BuilderMixin(XMLObject, Amount, Predicate):
if not movement_list: if not movement_list:
return [] return []
# Collect # Collect
root_group_node = self.collectMovement(movement_list) root_group_node = self.collectMovement(movement_list, merge_delivery=merge_delivery)
# Build # Build
delivery_list = self.buildDeliveryList( delivery_list = self.buildDeliveryList(
root_group_node, root_group_node,
delivery_relative_url_list=delivery_relative_url_list, delivery_relative_url_list=delivery_relative_url_list,
movement_list=movement_list, activate_kw=activate_kw, movement_list=movement_list, activate_kw=activate_kw,
**kw) merge_delivery=merge_delivery, **kw)
# Call a script after building # Call a script after building
self.callAfterBuildingScript(delivery_list, movement_list, **kw) self.callAfterBuildingScript(delivery_list, movement_list, **kw)
return delivery_list return delivery_list
...@@ -545,7 +546,7 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -545,7 +546,7 @@ class BuilderMixin(XMLObject, Amount, Predicate):
searchMovementList = UnrestrictedMethod(_searchMovementList) searchMovementList = UnrestrictedMethod(_searchMovementList)
security.declarePrivate('collectMovement') security.declarePrivate('collectMovement')
def collectMovement(self, movement_list): def collectMovement(self, movement_list, merge_delivery=False):
""" """
group movements in the way we want. Thanks to this method, we are able group movements in the way we want. Thanks to this method, we are able
to retrieve movement classed by order, resource, criterion,.... to retrieve movement classed by order, resource, criterion,....
...@@ -562,7 +563,8 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -562,7 +563,8 @@ class BuilderMixin(XMLObject, Amount, Predicate):
root_group_node = MovementGroupNode( root_group_node = MovementGroupNode(
separate_method_name_list=separate_method_name_list, separate_method_name_list=separate_method_name_list,
movement_group_list=movement_group_list, movement_group_list=movement_group_list,
last_line_movement_group=last_line_movement_group) last_line_movement_group=last_line_movement_group,
merge_delivery=merge_delivery)
root_group_node.append(movement_list) root_group_node.append(movement_list)
return root_group_node return root_group_node
...@@ -644,14 +646,16 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -644,14 +646,16 @@ class BuilderMixin(XMLObject, Amount, Predicate):
if update: if update:
delivery_to_update_list = [portal.restrictedTraverse(relative_url) for \ delivery_to_update_list = [portal.restrictedTraverse(relative_url) for \
relative_url in delivery_relative_url_list] relative_url in delivery_relative_url_list]
# Deliveries we are trying to update # Only use select method when the list of delivery is not already provided
delivery_select_method_id = self.getDeliverySelectMethodId() if len(delivery_to_update_list) == 0:
if delivery_select_method_id not in ["", None]: # Deliveries we are trying to update
to_update_delivery_sql_list = getattr(self, delivery_select_method_id) \ delivery_select_method_id = self.getDeliverySelectMethodId()
(movement_list=movement_list) if delivery_select_method_id not in ["", None]:
delivery_to_update_list.extend([sql_delivery.getObject() \ to_update_delivery_sql_list = getattr(self, delivery_select_method_id) \
for sql_delivery \ (movement_list=movement_list)
in to_update_delivery_sql_list]) delivery_to_update_list.extend([sql_delivery.getObject() \
for sql_delivery \
in to_update_delivery_sql_list])
else: else:
delivery_to_update_list = [] delivery_to_update_list = []
# We do not want to update the same object more than twice in one # We do not want to update the same object more than twice in one
...@@ -682,7 +686,8 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -682,7 +686,8 @@ class BuilderMixin(XMLObject, Amount, Predicate):
collect_order_list, movement_group_node_list=None, collect_order_list, movement_group_node_list=None,
delivery_to_update_list=None, delivery_to_update_list=None,
divergence_list=None, divergence_list=None,
activate_kw=None, force_update=0, **kw): activate_kw=None, force_update=0,
merge_delivery=None, **kw):
""" """
Build delivery from a list of movement Build delivery from a list of movement
""" """
...@@ -708,7 +713,8 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -708,7 +713,8 @@ class BuilderMixin(XMLObject, Amount, Predicate):
delivery_to_update_list=delivery_to_update_list, delivery_to_update_list=delivery_to_update_list,
divergence_list=divergence_list, divergence_list=divergence_list,
activate_kw=activate_kw, activate_kw=activate_kw,
force_update=force_update) force_update=force_update,
merge_delivery=merge_delivery)
delivery_list.extend(new_delivery_list) delivery_list.extend(new_delivery_list)
force_update = 0 force_update = 0
else: else:
...@@ -718,9 +724,14 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -718,9 +724,14 @@ class BuilderMixin(XMLObject, Amount, Predicate):
x for x in delivery_to_update_list \ x for x in delivery_to_update_list \
if x.getPortalType() == self.getDeliveryPortalType() and \ if x.getPortalType() == self.getDeliveryPortalType() and \
not self._isUpdated(x, 'delivery')] not self._isUpdated(x, 'delivery')]
delivery, property_dict = self._findUpdatableObject( if merge_delivery:
delivery_to_update_list, movement_group_node_list, # We must have only one delivery to update in the case of merge
divergence_list) delivery, = delivery_to_update_list
property_dict = {}
else:
delivery, property_dict = self._findUpdatableObject(
delivery_to_update_list, movement_group_node_list,
divergence_list)
# if all deliveries are rejected in case of update, we update the # if all deliveries are rejected in case of update, we update the
# first one. # first one.
......
...@@ -1010,6 +1010,48 @@ class TestPackingListMixin(TestOrderMixin): ...@@ -1010,6 +1010,48 @@ class TestPackingListMixin(TestOrderMixin):
after_tag=after_tag, after_tag=after_tag,
).build(explanation_uid=packing_list.getCausalityValue().getUid()) ).build(explanation_uid=packing_list.getCausalityValue().getUid())
def stepMergeSplittedPackingList(self, sequence=None):
"""
Invoke the merge of the two sales packing list and check the merged packing list
Then also try to create a packing list not coming from order, and then
tro to merge it with the merged packing list
"""
# Merge the two existing packing list
packing_list1 = sequence.get('packing_list')
packing_list2 = sequence.get('new_packing_list')
self.portal.portal_simulation.mergeDeliveryList([packing_list1, packing_list2])
self.tic()
self.assertEqual('confirmed', packing_list1.getSimulationState())
self.assertEqual('cancelled', packing_list2.getSimulationState())
line, = packing_list1.objectValues(
portal_type= self.packing_list_line_portal_type)
self.assertEqual(self.default_quantity,line.getQuantity())
self.assertTrue(packing_list1.getStartDate() is not None)
self.assertTrue(packing_list1.getStopDate() is not None)
# Now clone the merged packing list, so that we will have :
# - one packing list coming from order (merged_packing_list)
# - one not coming from order (the cloned one)
cloned_packing_list = packing_list1.Base_createCloneDocument(batch_mode=True)
cloned_packing_list.setStartDate(cloned_packing_list.getStartDate() + 1)
cloned_packing_list.setStopDate(cloned_packing_list.getStopDate() + 1)
cloned_line, = cloned_packing_list.objectValues()
cloned_line.setQuantity(self.default_quantity+1)
self.portal.portal_workflow.doActionFor(cloned_packing_list, "confirm_action")
self.tic()
self.portal.portal_simulation.mergeDeliveryList([packing_list1, cloned_packing_list])
self.tic()
self.assertEqual('confirmed', packing_list1.getSimulationState())
self.assertEqual('cancelled', cloned_packing_list.getSimulationState())
resource = sequence.get('resource').getRelativeUrl()
def checkLineSet(delivery, expected_set):
line_list = delivery.getMovementList()
self.assertEqual(len(line_list), len(expected_set))
found_set = set([(x.getResource(), x.getQuantity(), x.getPrice()) for x in line_list])
expected_set = set([(resource, self.default_quantity, 555),
(resource, self.default_quantity+1, 555)])
checkLineSet(packing_list1, expected_set)
class TestPackingList(TestPackingListMixin, ERP5TypeTestCase) : class TestPackingList(TestPackingListMixin, ERP5TypeTestCase) :
run_all_test = 1 run_all_test = 1
...@@ -1020,6 +1062,8 @@ class TestPackingList(TestPackingListMixin, ERP5TypeTestCase) : ...@@ -1020,6 +1062,8 @@ class TestPackingList(TestPackingListMixin, ERP5TypeTestCase) :
Change the quantity on an delivery line, then Change the quantity on an delivery line, then
see if the packing list is divergent and then see if the packing list is divergent and then
split and defer the packing list split and defer the packing list
Finally, check we can merge if needed
""" """
if not run: return if not run: return
sequence_list = SequenceList() sequence_list = SequenceList()
...@@ -1033,6 +1077,7 @@ class TestPackingList(TestPackingListMixin, ERP5TypeTestCase) : ...@@ -1033,6 +1077,7 @@ class TestPackingList(TestPackingListMixin, ERP5TypeTestCase) :
Tic Tic
CheckPackingListIsSolved CheckPackingListIsSolved
CheckPackingListSplitted CheckPackingListSplitted
MergeSplittedPackingList
""" """
sequence_list.addSequenceString(sequence_string) sequence_list.addSequenceString(sequence_string)
......
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