Commit a39bcc3e authored by Julien Muchembled's avatar Julien Muchembled Committed by Sebastien Robin

Reimplement MRP for new simulation

MRP was broken and unused for a long time, since legacy simulation was dropped.
This commits resuscitates MRP, at least:
- expanding, for both operation and sourcing
- building of production reports & production packing lists

Business Processes replaces Supply Chains.
trade_phase replaces industrial_phase
industrial_phase is now used to variate partially produced resources.
parent 1b265a60
<?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_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</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>View</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}/Rule_view</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_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</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>View</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}/Rule_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
</item> </item>
<item> <item>
<key> <string>simulation_select_method_id</string> </key> <key> <string>simulation_select_method_id</string> </key>
<value> <string>TransformationSourcingRule_selectMovement</string> </value> <value> <string>ProductionPackingList_selectMovement</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
<string>destination_project</string> <string>destination_project</string>
<string>source_payment</string> <string>source_payment</string>
<string>destination_payment</string> <string>destination_payment</string>
<string>specialise</string>
</tuple> </tuple>
</value> </value>
</item> </item>
...@@ -52,6 +53,10 @@ ...@@ -52,6 +53,10 @@
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>category_movement_group_on_delivery</string> </value> <value> <string>category_movement_group_on_delivery</string> </value>
</item> </item>
<item>
<key> <string>update_always</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
......
...@@ -38,6 +38,10 @@ ...@@ -38,6 +38,10 @@
<value> <value>
<tuple> <tuple>
<string>resource</string> <string>resource</string>
<string>base_contribution</string>
<string>base_application</string>
<string>industrial_phase</string>
<string>quantity_unit</string>
</tuple> </tuple>
</value> </value>
</item> </item>
...@@ -45,6 +49,10 @@ ...@@ -45,6 +49,10 @@
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>category_movement_group_on_line</string> </value> <value> <string>category_movement_group_on_line</string> </value>
</item> </item>
<item>
<key> <string>update_always</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
......
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
</item> </item>
<item> <item>
<key> <string>simulation_select_method_id</string> </key> <key> <string>simulation_select_method_id</string> </key>
<value> <string>TransformationRule_selectMovement</string> </value> <value> <string>ProductionReport_selectMovement</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
<string>destination_project</string> <string>destination_project</string>
<string>source_payment</string> <string>source_payment</string>
<string>destination_payment</string> <string>destination_payment</string>
<string>specialise</string>
</tuple> </tuple>
</value> </value>
</item> </item>
...@@ -52,6 +53,10 @@ ...@@ -52,6 +53,10 @@
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>category_movement_group_on_delivery</string> </value> <value> <string>category_movement_group_on_delivery</string> </value>
</item> </item>
<item>
<key> <string>update_always</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
......
...@@ -38,6 +38,10 @@ ...@@ -38,6 +38,10 @@
<value> <value>
<tuple> <tuple>
<string>resource</string> <string>resource</string>
<string>base_contribution</string>
<string>base_application</string>
<string>industrial_phase</string>
<string>quantity_unit</string>
</tuple> </tuple>
</value> </value>
</item> </item>
...@@ -45,6 +49,10 @@ ...@@ -45,6 +49,10 @@
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>category_movement_group_on_line</string> </value> <value> <string>category_movement_group_on_line</string> </value>
</item> </item>
<item>
<key> <string>update_always</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
......
...@@ -27,6 +27,10 @@ ...@@ -27,6 +27,10 @@
<portal_type id="Production Report Module"> <portal_type id="Production Report Module">
<item>Production Report</item> <item>Production Report</item>
</portal_type> </portal_type>
<portal_type id="Rule Tool">
<item>Transformation Simulation Rule</item>
<item>Transformation Sourcing Simulation Rule</item>
</portal_type>
<portal_type id="Supply Chain"> <portal_type id="Supply Chain">
<item>Supply Link</item> <item>Supply Link</item>
<item>Supply Node</item> <item>Supply Node</item>
...@@ -34,4 +38,24 @@ ...@@ -34,4 +38,24 @@
<portal_type id="Supply Chain Module"> <portal_type id="Supply Chain Module">
<item>Supply Chain</item> <item>Supply Chain</item>
</portal_type> </portal_type>
<portal_type id="Transformation Simulation Rule">
<item>Category Membership Divergence Tester</item>
<item>DateTime Divergence Tester</item>
<item>Float Divergence Tester</item>
<item>Mapped Property</item>
<item>Net Converted Quantity Divergence Tester</item>
<item>Specialise Divergence Tester</item>
<item>String Divergence Tester</item>
<item>Variation Divergence Tester</item>
</portal_type>
<portal_type id="Transformation Sourcing Simulation Rule">
<item>Category Membership Divergence Tester</item>
<item>DateTime Divergence Tester</item>
<item>Float Divergence Tester</item>
<item>Mapped Property</item>
<item>Net Converted Quantity Divergence Tester</item>
<item>Specialise Divergence Tester</item>
<item>String Divergence Tester</item>
<item>Variation Divergence Tester</item>
</portal_type>
</allowed_content_type_list> </allowed_content_type_list>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Base Type" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_property_domain_dict</string> </key>
<value>
<dictionary>
<item>
<key> <string>short_title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>acquire_local_roles</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>content_icon</string> </key>
<value> <string>rule_icon.gif</string> </value>
</item>
<item>
<key> <string>content_meta_type</string> </key>
<value> <string>ERP5 Transformation Simulation Rule</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>filter_content_types</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<tuple>
<string>rule</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Transformation Simulation Rule</string> </value>
</item>
<item>
<key> <string>init_script</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>permission</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>TransformationSimulationRule</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_mixin</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>short_title</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<tuple>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>title</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Base Type" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_property_domain_dict</string> </key>
<value>
<dictionary>
<item>
<key> <string>short_title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>acquire_local_roles</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>content_icon</string> </key>
<value> <string>rule_icon.gif</string> </value>
</item>
<item>
<key> <string>content_meta_type</string> </key>
<value> <string>ERP5 Transformation Sourcing Simulation Rule</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>filter_content_types</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<tuple>
<string>rule</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Transformation Sourcing Simulation Rule</string> </value>
</item>
<item>
<key> <string>init_script</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>permission</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>TransformationSourcingSimulationRule</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_mixin</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>short_title</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<tuple>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
<tuple/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>title</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -47,4 +47,12 @@ ...@@ -47,4 +47,12 @@
<type>Supply Node</type> <type>Supply Node</type>
<workflow>edit_workflow</workflow> <workflow>edit_workflow</workflow>
</chain> </chain>
<chain>
<type>Transformation Simulation Rule</type>
<workflow>edit_workflow, rule_validation_workflow</workflow>
</chain>
<chain>
<type>Transformation Sourcing Simulation Rule</type>
<workflow>edit_workflow, rule_validation_workflow</workflow>
</chain>
</workflow_chain> </workflow_chain>
\ No newline at end of file
...@@ -50,26 +50,23 @@ ...@@ -50,26 +50,23 @@
</item> </item>
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>portal = context.getPortalObject()\n <value> <string>resource = context.getResourceValue()\n
from ZTUtils import LazyFilter\n
resource = context.getResourceValue()\n
\n \n
result = []\n result = []\n
\n
if include_empty:\n if include_empty:\n
result.append([\'\',\'\'])\n result.append((\'\',\'\'))\n
if resource is None:\n
return result\n
\n \n
# XXX: Is it possible to use cache? Hook it on resource?\n if resource is not None:\n
for transformation in LazyFilter(\n portal = context.getPortalObject()\n
resource.getResourceRelatedValueList(portal_type=portal.getPortalTransformationTypeList()),\n kw = {\'validation_state\': \'!=invalidated\'} if skip_invalidated else {}\n
skip=\'View\'\n result.extend((transformation.title, transformation.relative_url)\n
):\n for transformation in portal.portal_catalog(\n
if not skip_invalidated or transformation.getProperty(\'validation_state\',\'default\') != \'invalidated\':\n select_list=(\'title\', \'relative_url\'),\n
result.append( (transformation.getTitle(),transformation.getRelativeUrl()) )\n portal_type=portal.getPortalTransformationTypeList(),\n
strict_resource_uid=resource.getUid(),\n
sort_on=(\'title\', \'relative_url\'),\n
**kw))\n
\n \n
result.sort(key=lambda x: x[0])\n
return result\n return result\n
</string> </value> </string> </value>
</item> </item>
......
...@@ -50,25 +50,14 @@ ...@@ -50,25 +50,14 @@
</item> </item>
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>packing_list = context\n <value> <string>if context.getSimulationState() == \'draft\':\n
order = context.getCausalityValue()\n
context.edit(\n
comment=order.getComment(),\n
title=order.getTitle(),\n
)\n
\n \n
tag = packing_list.getPath() + \'_confirm\'\n context.Delivery_confirm()\n
\n
# First, copy Order properties\n
related_order = packing_list.getCausalityValue()\n
packing_list.edit(\n
comment=related_order.getComment(),\n
delivery_mode=related_order.getDeliveryMode(),\n
incoterm=related_order.getIncoterm(),\n
destination_administration_value=\\\n
related_order.getDestinationAdministrationValue(),\n
activate_kw={\'tag\':tag},\n
)\n
\n
packing_list.startBuilding()\n
packing_list.activate(after_tag=tag).updateCausalityState()\n
\n
packing_list.activate(after_tag=tag).ProductionDelivery_confirm()\n
</string> </value> </string> </value>
</item> </item>
<item> <item>
......
...@@ -50,48 +50,24 @@ ...@@ -50,48 +50,24 @@
</item> </item>
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>kw[\'explanation_portal_type\'] = \'Production Order\'\n <value> <string>return context.portal_catalog(\n
kw[\'parent_specialise_portal_type\'] = \'Transformation Rule\'\n explanation_portal_type="Production Order",\n
kw[\'grand_parent_simulation_state\'] = \'confirmed\'\n parent_specialise_portal_type=("Delivery Simulation Rule",\n
\n "Transformation Sourcing Simulation Rule"),\n
kw[\'delivery_uid\'] = None\n delivery_uid=None,\n
kw[\'left_join_list\'] = [\'delivery_uid\']\n left_join_list=("delivery_uid",),\n
kw[\'select_dict\'] = dict(delivery_uid=None)\n select_list=("delivery_uid",),\n
kw[\'group_by\'] = (\'uid\',)\n group_by=("uid",),\n
\n **kw)\n
kw[\'src__\'] = src__ \n
result = context.portal_catalog(**kw)\n
if src__:\n
result\n
\n
movement_list = []\n
for movement in result:\n
movement = movement.getObject()\n
root_movement = movement.getRootSimulationMovement()\n
root_rule = root_movement.getParentValue().getSpecialiseValue()\n
if root_rule.getPortalType() in ("Production Order Rule",\n
"Production Order Root Simulation Rule") \\\n
and root_movement.getSimulationState() == "confirmed":\n
movement_list.append(movement)\n
\n
return movement_list\n
</string> </value> </string> </value>
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>src__=0, **kw</string> </value> <value> <string>**kw</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>TransformationRule_selectMovement</string> </value> <value> <string>ProductionPackingList_selectMovement</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
......
...@@ -50,24 +50,27 @@ ...@@ -50,24 +50,27 @@
</item> </item>
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>from Products.ERP5Type.Message import Message\n <value> <string>return context.portal_catalog(\n
packing_list = context\n explanation_portal_type="Production Order",\n
\n parent_specialise_portal_type="Transformation Simulation Rule",\n
packing_list_state = packing_list.getSimulationState()\n delivery_uid=None,\n
if packing_list_state == "draft":\n left_join_list=("delivery_uid",),\n
packing_list.portal_workflow.doActionFor(\n select_list=("delivery_uid",),\n
packing_list,\n group_by=("uid",),\n
\'confirm_action\',\n **kw)\n
comment=Message(\'erp5_ui\', \'Initialised by Delivery Builder\'))\n
</string> </value> </string> </value>
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string></string> </value> <value> <string>**kw</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>ProductionDelivery_confirm</string> </value> <value> <string>ProductionReport_selectMovement</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -50,55 +50,24 @@ ...@@ -50,55 +50,24 @@
</item> </item>
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>kw[\'explanation_portal_type\'] = \'Production Order\'\n <value> <string>specialise_list = context.getSpecialiseValueList(portal_type="Transformation")\n
if (len(specialise_list) == 1 and\n
context.getResource() == specialise_list[0].getResource()):\n
parent = context.getParentValue()\n
if parent.getSpecialiseValue().getPortalType() == "Delivery Simulation Rule":\n
movement = context.getParentValue().getDeliveryValue()\n
\n \n
kw[\'delivery_uid\'] = None\n return movement is not None and movement.getPortalType() in (\n
kw[\'left_join_list\'] = [\'delivery_uid\']\n "Production Order Line", "Production Order Cell")\n
kw[\'select_dict\'] = dict(delivery_uid=None)\n
kw[\'group_by\'] = (\'uid\',)\n
\n
kw[\'src__\'] = src__ \n
result = context.portal_catalog(**kw)\n
if src__:\n
result\n
\n
movement_list = []\n
for movement in result:\n
movement = movement.getObject()\n
root_movement = movement.getRootSimulationMovement()\n
root_type = root_movement.getParentValue().getSpecialiseValue().getPortalType()\n
parent_type = movement.getParentValue().getSpecialiseValue().getPortalType()\n
if (root_type == "Production Order Rule" and\n
parent_type in ("Transformation Sourcing Rule",\n
"Production Order Rule") or\n
root_type == "Production Order Root Simulation Rule" and\n
parent_type in ("Transformation Sourcing Rule",\n
"Delivery Simulation Rule")) \\\n
and root_movement.getSimulationState() == "confirmed":\n
movement_list.append(movement)\n
\n
return movement_list\n
</string> </value> </string> </value>
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>src__=0, **kw</string> </value> <value> <string>rule</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>TransformationSourcingRule_selectMovement</string> </value> <value> <string>SimulationMovement_testTransformationSimulationRule</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -50,16 +50,18 @@ ...@@ -50,16 +50,18 @@
</item> </item>
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>return \'default_production_order_rule\'\n <value> <string>rule = context.getParentValue().getSpecialiseValue()\n
return rule.getPortalType() == "Transformation Simulation Rule" \\\n
and rule.testTransformationSourcing(context)\n
</string> </value> </string> </value>
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string></string> </value> <value> <string>rule</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>ProductionOrder_getRuleReference</string> </value> <value> <string>SimulationMovement_testTransformationSourcingSimulationRule</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -36,4 +36,6 @@ Supply Chain Module | view ...@@ -36,4 +36,6 @@ Supply Chain Module | view
Supply Chain | view Supply Chain | view
Supply Chain | view_supply_node Supply Chain | view_supply_node
Supply Link | view Supply Link | view
Supply Node | view Supply Node | view
\ No newline at end of file Transformation Simulation Rule | view
Transformation Sourcing Simulation Rule | view
\ No newline at end of file
...@@ -8,6 +8,24 @@ Production Packing List | Production Packing List Line ...@@ -8,6 +8,24 @@ Production Packing List | Production Packing List Line
Production Report Line | Production Report Cell Production Report Line | Production Report Cell
Production Report Module | Production Report Production Report Module | Production Report
Production Report | Production Report Line Production Report | Production Report Line
Rule Tool | Transformation Simulation Rule
Rule Tool | Transformation Sourcing Simulation Rule
Supply Chain Module | Supply Chain Supply Chain Module | Supply Chain
Supply Chain | Supply Link Supply Chain | Supply Link
Supply Chain | Supply Node Supply Chain | Supply Node
\ No newline at end of file Transformation Simulation Rule | Category Membership Divergence Tester
Transformation Simulation Rule | DateTime Divergence Tester
Transformation Simulation Rule | Float Divergence Tester
Transformation Simulation Rule | Mapped Property
Transformation Simulation Rule | Net Converted Quantity Divergence Tester
Transformation Simulation Rule | Specialise Divergence Tester
Transformation Simulation Rule | String Divergence Tester
Transformation Simulation Rule | Variation Divergence Tester
Transformation Sourcing Simulation Rule | Category Membership Divergence Tester
Transformation Sourcing Simulation Rule | DateTime Divergence Tester
Transformation Sourcing Simulation Rule | Float Divergence Tester
Transformation Sourcing Simulation Rule | Mapped Property
Transformation Sourcing Simulation Rule | Net Converted Quantity Divergence Tester
Transformation Sourcing Simulation Rule | Specialise Divergence Tester
Transformation Sourcing Simulation Rule | String Divergence Tester
Transformation Sourcing Simulation Rule | Variation Divergence Tester
\ No newline at end of file
...@@ -13,4 +13,6 @@ Production Report Module ...@@ -13,4 +13,6 @@ Production Report Module
Supply Chain Supply Chain
Supply Chain Module Supply Chain Module
Supply Link Supply Link
Supply Node Supply Node
\ No newline at end of file Transformation Simulation Rule
Transformation Sourcing Simulation Rule
\ No newline at end of file
...@@ -36,4 +36,8 @@ Production Report | production_packing_list_workflow ...@@ -36,4 +36,8 @@ Production Report | production_packing_list_workflow
Supply Chain | edit_workflow Supply Chain | edit_workflow
Supply Chain | validation_workflow Supply Chain | validation_workflow
Supply Link | edit_workflow Supply Link | edit_workflow
Supply Node | edit_workflow Supply Node | edit_workflow
\ No newline at end of file Transformation Simulation Rule | edit_workflow
Transformation Simulation Rule | rule_validation_workflow
Transformation Sourcing Simulation Rule | edit_workflow
Transformation Sourcing Simulation Rule | rule_validation_workflow
\ No newline at end of file
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
############################################################################## ##############################################################################
import zope.interface import zope.interface
from collections import defaultdict
from math import log from math import log
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5.mixin.variated import VariatedMixin from Products.ERP5.mixin.variated import VariatedMixin
...@@ -85,19 +86,15 @@ class Amount(Base, VariatedMixin): ...@@ -85,19 +86,15 @@ class Amount(Base, VariatedMixin):
" omit_option_base_category.", DeprecationWarning) " omit_option_base_category.", DeprecationWarning)
omit_optional_variation = omit_option_base_category omit_optional_variation = omit_option_base_category
result = []
resource = self.getDefaultResourceValue() resource = self.getDefaultResourceValue()
if resource is not None: if resource is None:
resource_variation_list = resource.getVariationBaseCategoryList( return []
omit_optional_variation=omit_optional_variation) variation_list = resource.getVariationBaseCategoryList(
if len(base_category_list) > 0 : omit_optional_variation=omit_optional_variation)
variation_list = filter(lambda x: x in base_category_list, variation_list.append('industrial_phase')
resource_variation_list) if base_category_list:
else : variation_list = filter(base_category_list.__contains__, variation_list)
variation_list = resource_variation_list return self.getAcquiredCategoryMembershipList(variation_list, base=1)
if len(variation_list) > 0:
result = self.getAcquiredCategoryMembershipList(variation_list, base=1)
return result
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getVariationCategoryItemList') 'getVariationCategoryItemList')
...@@ -110,43 +107,32 @@ class Amount(Base, VariatedMixin): ...@@ -110,43 +107,32 @@ class Amount(Base, VariatedMixin):
Result is left display. Result is left display.
""" """
variation_category_item_list = [] variation_category_item_list = []
if base_category_list == (): category_list = self.getVariationCategoryList()
base_category_list = self.getVariationRangeBaseCategoryList() if category_list:
variation_dict = defaultdict(lambda: ([], []))
for base_category in base_category_list: resolveCategory = self.getPortalObject().portal_categories.resolveCategory
variation_category_list = self.getVariationCategoryList( for category in category_list:
base_category_list=[base_category]) resource = resolveCategory(category)
variation_dict[category.split('/', 1)[0]] \
resource_list = [self.portal_categories.resolveCategory(x) for x in\ [resource.getPortalType() == 'Category'].append(resource)
variation_category_list]
category_list = [x for x in resource_list \ kw = dict(is_right_display=0, display_none_category=0, base=base,
if x.getPortalType() == 'Category'] current_category=current_category, **kw)
variation_category_item_list.extend(Renderer( render_category_list = Renderer(display_id=display_id, **kw).render
is_right_display=0, kw['display_id'] = 'title'
display_none_category=0, base=base, for base_category, (object_list,
current_category=current_category, category_list) in variation_dict.iteritems():
display_id=display_id, **kw).\ variation_category_item_list += render_category_list(category_list)
render(category_list)) variation_category_item_list += Renderer(base_category=base_category,
object_list = [x for x in resource_list \ **kw).render(object_list)
if x.getPortalType() != 'Category']
variation_category_item_list.extend(Renderer(
is_right_display=0,
base_category=base_category,
display_none_category=0, base=base,
current_category=current_category,
display_id='title', **kw).\
render(object_list))
return variation_category_item_list return variation_category_item_list
security.declareProtected(Permissions.ModifyPortalContent,
'_setVariationCategoryList')
def _setVariationCategoryList(self, value): def _setVariationCategoryList(self, value):
result = []
resource = self.getDefaultResourceValue() resource = self.getDefaultResourceValue()
if resource is not None: if resource is not None:
variation_list = resource.getVariationBaseCategoryList() variation_list = resource.getVariationBaseCategoryList()
if len(variation_list) > 0: variation_list.append('industrial_phase')
self._setCategoryMembership(variation_list, value, base = 1) self._setCategoryMembership(variation_list, value, base=1)
security.declareProtected(Permissions.ModifyPortalContent, security.declareProtected(Permissions.ModifyPortalContent,
'setVariationCategoryList') 'setVariationCategoryList')
...@@ -209,7 +195,6 @@ class Amount(Base, VariatedMixin): ...@@ -209,7 +195,6 @@ class Amount(Base, VariatedMixin):
""" """
return VariationValue(context = self) return VariationValue(context = self)
security.declareProtected(Permissions.ModifyPortalContent, '_setVariationValue')
def _setVariationValue(self, variation_value): def _setVariationValue(self, variation_value):
return variation_value.setVariationValue(self) return variation_value.setVariationValue(self)
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
# #
############################################################################## ##############################################################################
from collections import defaultdict
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, interfaces from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
...@@ -844,3 +845,41 @@ class BusinessProcess(Path, XMLObject): ...@@ -844,3 +845,41 @@ class BusinessProcess(Path, XMLObject):
""" """
for business_link in self.getBuildableBusinessLinkValueList(explanation): for business_link in self.getBuildableBusinessLinkValueList(explanation):
business_link.build(explanation=explanation) business_link.build(explanation=explanation)
security.declareProtected(Permissions.AccessContentsInformation,
'getPreviousTradePhaseDict')
def getPreviousTradePhaseDict(self, trade_phase_list=None):
"""Return a dict mapping each phase to a set of previous ones
If trade_phase_list is given, the return graph is reduced to only keep
phases in this list.
"""
state_dict = defaultdict(set)
phase_list = []
for link in self.getBusinessLinkValueList(sort_on=None):
phase, = link.getTradePhaseList() # BL must have exactly 1 TP
phase_list.append((phase, link.getPredecessor()))
state_dict[link.getSuccessor()].add(phase)
result = dict((phase, state_dict[state]) for phase, state in phase_list)
if trade_phase_list: # reduce graph
next_dict = defaultdict(set)
# build {phase: next_set} (i.e. reverse result)
for next, phase_set in result.iteritems():
for phase in phase_set:
next_dict[phase].add(next)
# for each phase to remove
for phase in set(result).difference(trade_phase_list):
# edit the graph like we would do for a doubly linked list
previous_set = result.pop(phase)
next_set = next_dict[phase]
# i.e. edit next phases to replace current phase by previous ones
for next in next_set:
phase_set = result[next]
phase_set.remove(phase)
phase_set |= previous_set
# and previous phases to replace current by next ones
for previous in previous_set:
phase_set = next_dict[previous]
phase_set.remove(phase)
phase_set |= next_set
return result
...@@ -86,13 +86,10 @@ class TradeModelRuleMovementGenerator(MovementGeneratorMixin): ...@@ -86,13 +86,10 @@ class TradeModelRuleMovementGenerator(MovementGeneratorMixin):
def _getInputMovementList(self, movement_list=None, rounding=False): def _getInputMovementList(self, movement_list=None, rounding=False):
simulation_movement = self._applied_rule.getParentValue() simulation_movement = self._applied_rule.getParentValue()
portal = self._applied_rule.getPortalObject() portal = self._applied_rule.getPortalObject()
# List of types passed to simulation_movemet.asComposedDocument()
# it needs to include portal types of all 'amount_generator*' groups:
composition_type_list = (portal.getPortalAmountGeneratorTypeList() +
portal.getPortalAmountGeneratorLineTypeList() +
portal.getPortalAmountGeneratorCellTypeList())
amount_list = simulation_movement.getAggregatedAmountList( amount_list = simulation_movement.getAggregatedAmountList(
amount_generator_type_list=composition_type_list) # List of types passed to simulation_movement.asComposedDocument()
# it needs to include portal types of all 'amount_generator*' groups:
amount_generator_type_list=portal.getPortalAmountGeneratorAllTypeList(0))
input_movement = aq_base(simulation_movement).__of__(self._applied_rule) input_movement = aq_base(simulation_movement).__of__(self._applied_rule)
for amount in amount_list: for amount in amount_list:
# Do not ignore amount with price = 0 (such behaviour can be obtained by # Do not ignore amount with price = 0 (such behaviour can be obtained by
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2014 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 Acquisition import aq_base
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.ERP5.mixin.rule import RuleMixin, MovementGeneratorMixin
from Products.ERP5.mixin.movement_collection_updater import \
MovementCollectionUpdaterMixin
class TransformationSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
"""
"""
# CMF Type Definition
meta_type = 'ERP5 Transformation Simulation Rule'
portal_type = 'Transformation Simulation Rule'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Default Properties
property_sheets = (
PropertySheet.Base,
PropertySheet.XMLObject,
PropertySheet.CategoryCore,
PropertySheet.DublinCore,
PropertySheet.Task,
PropertySheet.Predicate,
PropertySheet.Reference,
PropertySheet.Version,
PropertySheet.Rule
)
def _getMovementGenerator(self, context):
"""
Return the movement generator to use in the expand process
"""
return TransformationRuleMovementGenerator(applied_rule=context, rule=self)
def testTransformationSourcing(self, context):
if context.getReference().split('/', 1)[0] == 'pr':
return False
# context consumes a resource: maybe sourcing is required.
# Let's see if the business process defines any trade phase that:
# - is not yet expanded (well, we only checks parents and siblings)
# - and precedes a phase of the current transformation
phase = context.getTradePhase()
parent = context.getParentValue()
tv = getTransactionalVariable()
key = 'isSourcingNeeded', parent.getUid()
try:
needed_set = tv[key]
except KeyError:
phase_set = set(x.getTradePhase() for x in parent.objectValues())
phase_list = phase_set.copy()
movement = parent.getParentValue()
while movement.getPortalType() == 'Simulation Movement':
phase_set.add(movement.getTradePhase())
movement = movement.getParentValue().getParentValue()
previous_dict = context.asComposedDocument().getPreviousTradePhaseDict()
needed_set = tv[key] = frozenset(x for x in phase_list
if previous_dict[x] - phase_set)
return phase in needed_set
class TransformationRuleMovementGenerator(MovementGeneratorMixin):
def _getUpdatePropertyDict(self, input_movement):
return {}
def _getInputMovementList(self, movement_list=None, rounding=None):
parent_movement = self._applied_rule.getParentValue()
portal = self._applied_rule.getPortalObject()
amount_list = parent_movement.getAggregatedAmountList(
amount_generator_type_list=portal.getPortalAmountGeneratorAllTypeList(1))
arrow_list = ['destination' + x[6:]
for x in parent_movement.getCategoryMembershipList(
('source', 'source_section'), base=True)]
def newMovement(reference, kw={}):
movement = aq_base(parent_movement.asContext(**kw)).__of__(
self._applied_rule)
movement._setReference(reference)
movement._setCategoryMembership(('destination', 'source_section',
'destination_section', 'source'),
arrow_list, base=True)
return movement
phase_set = set()
for amount in amount_list:
# Do not ignore amount with price = 0 (such behaviour can be obtained by
# specifying a predicate on the amount generator line/cell).
if amount.getResource():
phase_set.add(amount.getTradePhase())
# FIXME: Is it the right way to have source/destination and other
# non-Amount properties set on the generated movement ?
movement = newMovement(amount.getCausality(), dict((k, v)
for k, v in amount.__dict__.iteritems()
if k[0] != '_' and k != 'categories'))
base_category_set = set(amount.getBaseCategoryList())
base_category_set.remove('price_currency') # XXX
movement._setCategoryMembership(base_category_set,
amount.getCategoryList(),
base=True)
movement.quantity = - movement.quantity
yield movement
phase_dict = parent_movement.asComposedDocument() \
.getPreviousTradePhaseDict(phase_set)
final_set = phase_set.copy()
previous_set = final_set.copy()
while previous_set:
phase_list = phase_dict[previous_set.pop()]
final_set.difference_update(phase_list)
previous_set.update(phase_list)
# We should not need an option not to generate movements for intermediate
# resources. This can be configured on Trade Model Paths by filtering out
# movement with an industrial_phase (other properties like reference
# starting with "pr/" is possible). The drawback with such filter is that
# Same Total Quantity check must be disabled.
if 1:
cr_quantity = - parent_movement.getQuantity()
def newIntermediateMovement(reference_prefix, industrial_phase, **kw):
movement = newMovement(reference_prefix + phase, kw)
movement._setTradePhase(phase)
movement._setIndustrialPhase('trade_phase/' + industrial_phase)
return movement
for phase in phase_set:
for previous in phase_dict[phase]:
yield newIntermediateMovement('cr/', previous, quantity=cr_quantity)
if phase not in final_set:
yield newIntermediateMovement('pr/', phase)
movement = newMovement('pr')
movement._setTradePhaseList(final_set)
yield movement
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2014 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 Acquisition import aq_base
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5.mixin.rule import RuleMixin, MovementGeneratorMixin
from Products.ERP5.mixin.movement_collection_updater import \
MovementCollectionUpdaterMixin
class TransformationSourcingSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
"""
Transformation Sourcing Rule makes sure
items required in a Transformation are sourced.
"""
# CMF Type Definition
meta_type = 'ERP5 Transformation Sourcing Simulation Rule'
portal_type = 'Transformation Sourcing Simulation Rule'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Default Properties
property_sheets = (
PropertySheet.Base,
PropertySheet.XMLObject,
PropertySheet.CategoryCore,
PropertySheet.DublinCore,
PropertySheet.Task,
PropertySheet.Predicate,
PropertySheet.Reference,
PropertySheet.Version,
PropertySheet.Rule
)
def _getMovementGenerator(self, context):
"""
Return the movement generator to use in the expand process
"""
return TransformationSourcingRuleMovementGenerator(applied_rule=context, rule=self)
class TransformationSourcingRuleMovementGenerator(MovementGeneratorMixin):
def _getUpdatePropertyDict(self, input_movement):
return {}
def _getInputMovementList(self, movement_list=None, rounding=None):
parent_movement = self._applied_rule.getParentValue()
phase_dict = parent_movement.asComposedDocument() \
.getPreviousTradePhaseDict()
movement = aq_base(parent_movement).__of__(self._applied_rule)
movement = movement.asContext(quantity=-movement.getQuantity())
movement._setReference(None)
movement._setTradePhaseList(phase_dict[parent_movement.getTradePhase()])
if parent_movement.getReference().startswith('cr/'):
# For partially produced resources, automatically guess source from other
# movements of the transformation. This avoids duplicate information
# on Trade Model Paths.
# 'here/getSource' condition can be used to match such movements.
# The opposite condition can be used to match raw materials.
reference = 'pr' + parent_movement.getIndustrialPhase()[11:]
for pr in parent_movement.getParentValue().objectValues():
if pr.getReference() == reference:
movement._setSource(pr.getDestination())
movement._setSourceSection(pr.getDestinationSection())
break
return movement,
...@@ -1419,6 +1419,20 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin): ...@@ -1419,6 +1419,20 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin):
""" """
return self._getPortalGroupedTypeList('amount_generator_cell') return self._getPortalGroupedTypeList('amount_generator_cell')
security.declareProtected(Permissions.AccessContentsInformation,
'getPortalAmountGeneratorAllTypeList')
def getPortalAmountGeneratorAllTypeList(self, transformation):
"""
Return amount generator types, including lines & cells,
but only or without those related to transformations.
"""
result = list(self.getPortalAmountGeneratorTypeList())
result += self.getPortalAmountGeneratorLineTypeList()
result += self.getPortalAmountGeneratorCellTypeList()
if transformation:
return tuple(x for x in result if x.startswith('Transformation'))
return tuple(x for x in result if not x.startswith('Transformation'))
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getPortalBusinessProcessTypeList') 'getPortalBusinessProcessTypeList')
def getPortalBusinessProcessTypeList(self): def getPortalBusinessProcessTypeList(self):
......
...@@ -27,90 +27,102 @@ ...@@ -27,90 +27,102 @@
############################################################################## ##############################################################################
import unittest import unittest
from unittest import skip
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from DateTime import DateTime from DateTime import DateTime
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.CMFCore.utils import getToolByName
from Products.ERP5Type.tests.utils import reindex
from Products.ERP5.tests.testBPMCore import TestBPMMixin from Products.ERP5.tests.testBPMCore import TestBPMMixin
from Products.ERP5.tests.utils import newSimulationExpectedFailure
class TestMRPMixin(TestBPMMixin): class TestMRPMixin(TestBPMMixin):
transformation_portal_type = 'Transformation'
transformed_resource_portal_type = 'Transformation Transformed Resource' def afterSetUp(self):
product_portal_type = 'Product' super(TestMRPMixin, self).afterSetUp()
organisation_portal_type = 'Organisation' self._createRule("Transformation Simulation Rule")
order_portal_type = 'Production Order' rule = self._createRule("Transformation Sourcing Simulation Rule")
order_line_portal_type = 'Production Order Line' rule._setSameTotalQuantity(False)
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
return TestBPMMixin.getBusinessTemplateList(self) + ('erp5_mrp', ) return TestBPMMixin.getBusinessTemplateList(self) + ('erp5_mrp', )
def invalidateRules(self): def _createRule(self, portal_type):
""" x = portal_type.replace(' Simulation ', ' ').replace(' ', '_').lower()
do reversely of validateRules reference = "default_" + x
""" id = "testMRP_" + x
rule_tool = self.getRuleTool() rule_tool = self.portal.portal_rules
for rule in rule_tool.contentValues( try:
portal_type=rule_tool.getPortalRuleTypeList()): rule = self.getRule(reference=reference)
if rule.getValidationState() == 'validated': self.assertEqual(rule.getId(), id)
rule.invalidate() except IndexError:
rule = rule_tool.newContent(id, portal_type,
reference=reference,
test_method_id="SimulationMovement_test" + portal_type.replace(' ', ''))
def newTester(p, t, **kw):
kw.setdefault("tested_property", (p,))
return rule.newContent(p + "_tester", t + " Divergence Tester",
title=p + " divergence tester", **kw)
for x in ("aggregate",
"base_application",
"base_contribution",
"destination_section",
"destination",
"price_currency",
"resource",
"source_section",
"source",
"use"):
newTester(x, "Category Membership")
for x in ("start_date", "stop_date"):
newTester(x, "DateTime")
newTester("price", "Float")
newTester("quantity", "Net Converted Quantity",
tested_property=("quantity", "quantity_unit"))
newTester("specialise", "Specialise")
newTester("variation", "Variation",
tested_property=("variation_category_list",
"variation_property_dict"))
newTester("reference", "String", matching_provider=1,
divergence_provider=0)
if rule.getValidationState() != 'validated':
rule.validate()
return rule
def _createDocument(self, portal_type, **kw): def _createDocument(self, portal_type, **kw):
module = self.portal.getDefaultModule( return self.portal.getDefaultModule(portal_type=portal_type).newContent(
portal_type=portal_type) portal_type=portal_type, **kw)
return self._createObject(module, portal_type, **kw)
def _createObject(self, parent, portal_type, id=None, **kw):
o = None
if id is not None:
o = parent.get(str(id), None)
if o is None:
o = parent.newContent(portal_type=portal_type)
o.edit(**kw)
return o
def createTransformation(self, **kw): def createTransformation(self, **kw):
return self._createDocument(self.transformation_portal_type, **kw) return self._createDocument('Transformation', **kw)
def createProduct(self, **kw): def createProduct(self, **kw):
return self._createDocument(self.product_portal_type, **kw) return self._createDocument('Product', **kw)
def createOrganisation(self, **kw): def createNode(self, **kw):
return self._createDocument(self.organisation_portal_type, **kw) return self._createDocument('Organisation', **kw)
def createOrder(self, **kw): def createOrder(self, **kw):
return self._createDocument(self.order_portal_type, **kw) return self._createDocument('Production Order', **kw)
def createOrderLine(self, order, **kw): def createOrderLine(self, order, **kw):
return self._createObject(order, self.order_line_portal_type, **kw) return order.newContent(portal_type=order.getPortalType() + ' Line', **kw)
def createTransformedResource(self, transformation, **kw): def createTransformedResource(self, transformation, **kw):
return self._createObject(transformation, self.transformed_resource_portal_type, **kw) return transformation.newContent(
portal_type='Transformation Transformed Resource', **kw)
@reindex
def createCategories(self): def createCategories(self):
category_tool = getToolByName(self.portal, 'portal_categories') category_tool = self.portal.portal_categories
self.createCategoriesInCategory(category_tool.base_amount, ['weight']) self.createCategoriesInCategory(category_tool.quantity_unit, ['weight'])
self.createCategoriesInCategory(category_tool.base_amount.weight, ['kg']) self.createCategoriesInCategory(category_tool.quantity_unit.weight, ['kg'])
self.createCategoriesInCategory(category_tool.trade_phase, ['mrp',]) self.createCategoriesInCategory(category_tool.trade_phase, ['mrp',])
self.createCategoriesInCategory(category_tool.trade_phase.mrp, self.createCategoriesInCategory(category_tool.trade_phase.mrp,
['p' + str(i) for i in range(5)]) # phase0 ~ 4 ('p' + str(i) for i in xrange(2)))
self.createCategoriesInCategory(category_tool.trade_phase.mrp,
('s' + str(i) for i in xrange(1)))
self.createCategoriesInCategory(category_tool.trade_state, self.createCategoriesInCategory(category_tool.trade_state,
('ready', 'partial', 'done')) ('s' + str(i) for i in xrange(5)))
@reindex def createDefaultOrder(self, business_process, transformation=None):
def createDefaultOrder(self, transformation=None, business_process=None):
if transformation is None: if transformation is None:
transformation = self.createDefaultTransformation() transformation = self.createDefaultTransformation()
if business_process is None:
business_process = self.createSimpleBusinessProcess()
base_date = DateTime() base_date = DateTime()
order = self.createOrder(specialise_value=business_process, order = self.createOrder(specialise_value=business_process,
start_date=base_date, start_date=base_date,
stop_date=base_date+3) stop_date=base_date+3)
...@@ -118,10 +130,8 @@ class TestMRPMixin(TestBPMMixin): ...@@ -118,10 +130,8 @@ class TestMRPMixin(TestBPMMixin):
quantity=10, quantity=10,
resource=transformation.getResource(), resource=transformation.getResource(),
specialise_value=transformation) specialise_value=transformation)
# XXX in some case, specialise_value is not related to order_line by edit,
# but by setSpecialise() is ok, Why?
order_line.setSpecialiseValue(transformation)
return order return order
<<<<<<< HEAD
@reindex @reindex
def createDefaultTransformation(self): def createDefaultTransformation(self):
...@@ -130,378 +140,167 @@ class TestMRPMixin(TestBPMMixin): ...@@ -130,378 +140,167 @@ class TestMRPMixin(TestBPMMixin):
resource3 = self.createProduct(id='3', quantity_unit_list=['weight/kg']) resource3 = self.createProduct(id='3', quantity_unit_list=['weight/kg'])
resource4 = self.createProduct(id='4', quantity_unit_list=['weight/kg']) resource4 = self.createProduct(id='4', quantity_unit_list=['weight/kg'])
resource5 = self.createProduct(id='5', quantity_unit_list=['weight/kg']) resource5 = self.createProduct(id='5', quantity_unit_list=['weight/kg'])
=======
>>>>>>> f2130ed... Reimplement MRP for new simulation
transformation = self.createTransformation(resource_value=resource5) def createDefaultTransformation(self):
resource = lambda: self.createProduct(quantity_unit_list=['weight/kg'])
transformation = self.createTransformation(resource_value=resource())
self.createTransformedResource(transformation=transformation, self.createTransformedResource(transformation=transformation,
resource_value=resource1, resource_value=resource(),
quantity=3, quantity=3,
quantity_unit_list=['weight/kg'], quantity_unit_list=['weight/kg'],
trade_phase='mrp/p2') trade_phase='mrp/p0')
self.createTransformedResource(transformation=transformation, self.createTransformedResource(transformation=transformation,
resource_value=resource2, resource_value=resource(),
quantity=1, quantity=1,
quantity_unit_list=['weight/kg'], quantity_unit_list=['weight/kg'],
trade_phase='mrp/p2') trade_phase='mrp/p0')
self.createTransformedResource(transformation=transformation, self.createTransformedResource(transformation=transformation,
resource_value=resource3, resource_value=resource(),
quantity=4, quantity=4,
quantity_unit_list=['weight/kg'], quantity_unit_list=['weight/kg'],
trade_phase='mrp/p3') trade_phase='mrp/p1')
self.createTransformedResource(transformation=transformation, self.createTransformedResource(transformation=transformation,
resource_value=resource4, resource_value=resource(),
quantity=1, quantity=1,
quantity_unit_list=['weight/kg'], quantity_unit_list=['weight/kg'],
trade_phase='mrp/p3') trade_phase='mrp/p1')
return transformation return transformation
@reindex def createBusinessProcess1(self, node_p0=None):
def createSimpleBusinessProcess(self): """ order p0 s0 p1 deliver
""" mrp/p2 mrp/3 ------- S0 ---- S1 ---- S2 ---- S3 ------- S4
ready -------- partial_produced ------- done PO PR PPL PR PPL
"""
# organisations
source_section = self.createOrganisation(title='source_section')
source = self.createOrganisation(title='source')
destination_section = self.createOrganisation(title='destination_section')
destination = self.createOrganisation(title='destination')
business_process = self.createBusinessProcess(referential_date='stop_date')
self.createBusinessLink(business_process,
id='p2',
predecessor='trade_state/ready',
successor='trade_state/partial',
quantity=1,
trade_phase=['mrp/p2'],
source_section_value=source_section,
source_value=source,
destination_section_value=destination_section,
destination_value=destination)
self.createBusinessLink(business_process,
id='p3',
predecessor='trade_state/partial',
successor='trade_state/done',
quantity=1,
deliverable=1, # root explanation
trade_phase=['mrp/p3'],
source_section_value=source_section,
source_value=source,
destination_section_value=destination_section,
destination_value=destination)
return business_process
@reindex
def createConcurrentBusinessProcess(self):
""" mrp/p2
ready ======== partial_produced
mrp/p3
""" """
# organisations business_process = self._createDocument("Business Process")
source_section = self.createOrganisation(title='source_section') builder = 'portal_deliveries/production_packing_list_builder'
source = self.createOrganisation(title='source') completed = 'delivered', 'started', 'stopped'
destination_section = self.createOrganisation(title='destination_section') phase_list = [('default/order', None, ('confirmed',)),
destination = self.createOrganisation(title='destination') ('default/delivery', builder, completed)]
phase_list[1:1] = [('mrp/p' + str(i),
business_process = self.createBusinessProcess(referential_date='stop_date') 'portal_deliveries/production_report_builder',
self.createBusinessLink(business_process, completed)
id='p2', for i in xrange(2)]
predecessor='trade_state/ready', if node_p0 is not None:
successor='trade_state/partial', phase_list.insert(2, ('mrp/s0', builder, completed))
quantity=1, predecessor = None
trade_phase=['mrp/p2'], for i, (phase, builder, completed) in enumerate(phase_list):
source_section_value=source_section, successor = 'trade_state/s' + str(i)
source_value=source, self.createBusinessLink(business_process,
destination_section_value=destination_section, completed_state=completed,
destination_value=destination) predecessor=predecessor,
self.createBusinessLink(business_process, successor=successor,
id='p3', trade_phase=phase,
predecessor='trade_state/ready', delivery_builder=builder)
successor='trade_state/partial', predecessor = successor
quantity=1, phase_list = [x[0] for x in phase_list]
deliverable=1, # root explanation if node_p0 is not None:
trade_phase=['mrp/p3'], self.createTradeModelPath(business_process,
source_section_value=source_section, destination_value=node_p0,
source_value=source, trade_phase=phase_list.pop(1))
destination_section_value=destination_section, self.createTradeModelPath(business_process,
destination_value=destination) test_tales_expression="here/getSource",
trade_phase=phase_list.pop(1))
self.createTradeModelPath(business_process, trade_phase_list=phase_list)
return business_process return business_process
def checkStock(self, resource, *node_variation_quantity):
if isinstance(resource, str):
resource = self.portal.unrestrictedTraverse(resource)
expected_dict = dict(((x[0].getUid(), x[1]), x[2])
for x in node_variation_quantity)
for r in resource.getCurrentInventoryList(group_by_node=1,
group_by_variation=1):
self.assertEqual(expected_dict.pop((r.node_uid, r.variation_text), 0),
r.inventory)
self.assertFalse(any(expected_dict.itervalues()), expected_dict)
class TestMRPImplementation(TestMRPMixin): class TestMRPImplementation(TestMRPMixin):
"""the test for implementation""" """the test for implementation"""
@skip('Unfinished experimental feature') def test(self):
def test_TransformationRule_getHeadProductionPathList(self): workshop = self.createNode(title='workshop')
rule = self.getRule(reference='default_transformation_model_rule') workshop2 = self.createNode(title='workshop2')
destination = self.createNode(title='destination')
transformation = self.createDefaultTransformation() business_process = self.createBusinessProcess1(workshop2)
order = self.createDefaultOrder(business_process)
business_process = self.createSimpleBusinessProcess() order_line, = order.objectValues()
self.assertEqual([business_process.p2], order._edit(source_value=workshop, destination_value=destination)
rule.getHeadProductionPathList(transformation, business_process))
business_process = self.createConcurrentBusinessProcess()
self.assertEqual(set([business_process.p2, business_process.p3]),
set(rule.getHeadProductionPathList(transformation, business_process)))
@newSimulationExpectedFailure
def test_TransformationRule_expand(self):
# mock order
order = self.createDefaultOrder()
order_line = order.objectValues()[0]
business_process = order.getSpecialiseValue()
# paths
path_p2 = '%s/p2' % business_process.getRelativeUrl()
path_p3 = '%s/p3' % business_process.getRelativeUrl()
# organisations
path = business_process.p2
source_section = path.getSourceSection()
source = path.getSource()
destination_section = path.getDestinationSection()
destination = path.getDestination()
consumed_organisations = (source_section, source, destination_section, None)
produced_organisations = (source_section, None, destination_section, destination)
# don't need another rules, just need TransformationRule for test
self.invalidateRules()
self.tic() self.tic()
# alter simulations of the order order.plan()
# root
applied_rule = self.portal.portal_simulation.newContent(portal_type='Applied Rule')
movement = applied_rule.newContent(portal_type='Simulation Movement')
applied_rule.edit(causality_value=order)
movement.edit(order_value=order_line,
quantity=order_line.getQuantity(),
resource=order_line.getResource())
# test mock
applied_rule = movement.newContent(potal_type='Applied Rule')
rule = self.getRule(reference='default_transformation_model_rule')
rule.expand(applied_rule)
# assertion
expected_value_set = set([
((path_p2,), 'product_module/5', produced_organisations, 'mrp/p3', -10),
((path_p2,), 'product_module/1', consumed_organisations, 'mrp/p2', 30),
((path_p2,), 'product_module/2', consumed_organisations, 'mrp/p2', 10),
((path_p3,), 'product_module/5', consumed_organisations, 'mrp/p3', 10),
((path_p3,), 'product_module/3', consumed_organisations, 'mrp/p3', 40),
((path_p3,), 'product_module/4', consumed_organisations, 'mrp/p3', 10),
((path_p3,), 'product_module/5', produced_organisations, None, -10)])
movement_list = applied_rule.objectValues()
self.assertEqual(len(expected_value_set), len(movement_list))
movement_value_set = set([])
for movement in movement_list:
movement_value_set |= set([(tuple(movement.getCausalityList()),
movement.getResource(),
(movement.getSourceSection(),
movement.getSource(),
movement.getDestinationSection(),
movement.getDestination(),), # organisations
movement.getTradePhase(),
movement.getQuantity())])
self.assertEqual(expected_value_set, movement_value_set)
@skip('Unfinished experimental feature')
def test_TransformationRule_expand_concurrent(self):
business_process = self.createConcurrentBusinessProcess()
# mock order
order = self.createDefaultOrder(business_process=business_process)
order_line = order.objectValues()[0]
# phases
phase_p2 = '%s/p2' % business_process.getRelativeUrl()
phase_p3 = '%s/p3' % business_process.getRelativeUrl()
# organisations
path = business_process.p2
source_section = path.getSourceSection()
source = path.getSource()
destination_section = path.getDestinationSection()
destination = path.getDestination()
organisations = (source_section, source, destination_section, destination)
consumed_organisations = (source_section, source, destination_section, None)
produced_organisations = (source_section, None, destination_section, destination)
# don't need another rules, just need TransformationRule for test
self.invalidateRules()
self.tic() self.tic()
# alter simulations of the order ar, = order.getCausalityRelatedValueList(portal_type="Applied Rule")
# root sm, = ar.objectValues() # order
applied_rule = self.portal.portal_simulation.newContent(portal_type='Applied Rule') ar, = sm.objectValues()
movement = applied_rule.newContent(portal_type='Simulation Movement') sm, = ar.objectValues() # deliver
applied_rule.edit(causality_value=order) ar, = sm.objectValues()
movement.edit(order_value=order_line,
quantity=order_line.getQuantity(), movement_list = []
resource=order_line.getResource()) resource = order_line.getResource()
# test mock for sm in ar.objectValues():
applied_rule = movement.newContent(potal_type='Applied Rule') self.assertEqual(sm.getSource(), None)
self.assertTrue(sm.getDestination())
rule = self.getRule(reference='default_transformation_model_rule') # Reference is used to match movements when reexpanding.
rule.expand(applied_rule) reference = sm.getReference()
if reference.split('/', 1)[0] in ('pr', 'cr'):
# assertion self.assertEqual(sm.getResource(), resource)
expected_value_set = set([ else:
((phase_p2,), 'product_module/1', consumed_organisations, 'mrp/p2', 30), cr = self.portal.unrestrictedTraverse(reference).getResource()
((phase_p2,), 'product_module/2', consumed_organisations, 'mrp/p2', 10), self.assertTrue(None != sm.getResource() == cr != resource)
((phase_p3,), 'product_module/3', consumed_organisations, 'mrp/p3', 40), reference = None
((phase_p3,), 'product_module/4', consumed_organisations, 'mrp/p3', 10), movement_list.append((sm.getTradePhase(), sm.getQuantity(),
((phase_p2, phase_p3), 'product_module/5', produced_organisations, None, -10)]) reference, sm.getIndustrialPhaseList()))
movement_list = applied_rule.objectValues() movement_list.sort()
self.assertEqual(len(expected_value_set), len(movement_list)) self.assertEqual(movement_list, sorted((
movement_value_set = set([]) ('mrp/p0', -10, None, []),
for movement in movement_list: ('mrp/p0', -30, None, []),
movement_value_set |= set([(tuple(movement.getCausalityList()), ('mrp/p0', 10, 'pr/mrp/p0', ['trade_phase/mrp/p0']),
movement.getResource(), ('mrp/p1', -10, 'cr/mrp/p1', ['trade_phase/mrp/p0']),
(movement.getSourceSection(), ('mrp/p1', -10, None, []),
movement.getSource(), ('mrp/p1', -40, None, []),
movement.getDestinationSection(), ('mrp/p1', 10, 'pr', []),
movement.getDestination(),), # organisations )))
movement.getTradePhase(),
movement.getQuantity())]) order.confirm()
self.assertEqual(expected_value_set, movement_value_set) order.localBuild()
@skip('Unfinished experimental feature')
def test_TransformationRule_expand_reexpand(self):
"""
test case of difference when any movement are frozen
by using above result
"""
self.test_TransformationRule_expand_concurrent()
self.tic() self.tic()
self.checkStock(resource)
applied_rule = self.portal.portal_simulation.objectValues()[0] def getRelatedDeliveryList(portal_type):
return order.getCausalityRelatedValueList(portal_type=portal_type)
business_process = applied_rule.getCausalityValue().getSpecialiseValue()
# phases
phase_p2 = '%s/p2' % business_process.getRelativeUrl()
phase_p3 = '%s/p3' % business_process.getRelativeUrl()
# organisations
path = business_process.p2
source_section = path.getSourceSection()
source = path.getSource()
destination_section = path.getDestinationSection()
destination = path.getDestination()
consumed_organisations = (source_section, source, destination_section, None)
produced_organisations = (source_section, None, destination_section, destination)
movement = applied_rule.objectValues()[0]
applied_rule = movement.objectValues()[0]
# these movements are made by transformation
for movement in applied_rule.objectValues():
movement.edit(quantity=1)
# set the state value of isFrozen to 1,
movement._baseSetFrozen(1)
# re-expand
rule = self.getRule(reference='default_transformation_model_rule')
rule.expand(applied_rule)
# assertion
expected_value_set = set([
((phase_p2,), 'product_module/1', consumed_organisations, 'mrp/p2', 1), # Frozen
((phase_p2,), 'product_module/1', consumed_organisations, 'mrp/p2', 29),
((phase_p2,), 'product_module/2', consumed_organisations, 'mrp/p2', 1), # Frozen
((phase_p2,), 'product_module/2', consumed_organisations, 'mrp/p2', 9),
((phase_p3,), 'product_module/3', consumed_organisations, 'mrp/p3', 1), # Frozen
((phase_p3,), 'product_module/3', consumed_organisations, 'mrp/p3', 39),
((phase_p3,), 'product_module/4', consumed_organisations, 'mrp/p3', 1), # Frozen
((phase_p3,), 'product_module/4', consumed_organisations, 'mrp/p3', 9),
((phase_p2, phase_p3), 'product_module/5', produced_organisations, None, 1), # Frozen
((phase_p2, phase_p3), 'product_module/5', produced_organisations, None, -11)])
movement_list = applied_rule.objectValues()
self.assertEqual(len(expected_value_set), len(movement_list))
movement_value_set = set([])
for movement in movement_list:
movement_value_set |= set([(tuple(movement.getCausalityList()),
movement.getResource(),
(movement.getSourceSection(),
movement.getSource(),
movement.getDestinationSection(),
movement.getDestination(),), # organisations
movement.getTradePhase(),
movement.getQuantity())])
self.assertEqual(expected_value_set, movement_value_set)
@skip('Unfinished experimental feature')
def test_TransformationSourcingRule_expand(self):
# mock order
order = self.createDefaultOrder()
order_line = order.objectValues()[0]
# don't need another rules, just need TransformationSourcingRule for test
self.invalidateRules()
pr1, = getRelatedDeliveryList("Production Report")
pr1.start()
pr1.deliver()
order.localBuild()
self.tic() self.tic()
variation = 'industrial_phase/trade_phase/mrp/p0'
self.checkStock(resource, (workshop2, variation, 10))
business_process = order.getSpecialiseValue() ppl1, = getRelatedDeliveryList("Production Packing List")
ppl1.start()
# get last path of a business process ppl1.deliver()
# in simple business path, the last is between "partial_produced" and "done" order.localBuild()
causality_path = None
for state in business_process.objectValues(
portal_type=self.portal.getPortalBusinessStateTypeList()):
if len(state.getRemainingTradePhaseList(self.portal)) == 0:
causality_path = state.getSuccessorRelatedValue()
# phases
phase_p2 = '%s/p2' % business_process.getRelativeUrl()
# organisations
source_section = causality_path.getSourceSection()
source = causality_path.getSource()
destination_section = causality_path.getDestinationSection()
destination = causality_path.getDestination()
organisations = (source_section, source, destination_section, destination)
# sourcing resource
sourcing_resource = order_line.getResource()
# alter simulations of the order
# root
applied_rule = self.portal.portal_simulation.newContent(portal_type='Applied Rule')
movement = applied_rule.newContent(portal_type='Simulation Movement')
applied_rule.edit(causality_value=order)
movement.edit(order_value=order_line,
causality_value=causality_path,
quantity=order_line.getQuantity(),
resource=sourcing_resource,
)
self.tic() self.tic()
self.checkStock(resource, (workshop, variation, 10))
# test mock pr2, = (x for x in getRelatedDeliveryList("Production Report")
applied_rule = movement.newContent(potal_type='Applied Rule') if x.aq_base is not pr1.aq_base)
pr2.start()
rule = self.getRule(reference='default_transformation_sourcing_model_rule') pr2.deliver()
rule.expand(applied_rule) order.localBuild()
self.tic()
self.checkStock(resource, (workshop, '', 10))
# assertion ppl2, = (x for x in getRelatedDeliveryList("Production Packing List")
expected_value_set = set([ if x.aq_base is not ppl1.aq_base)
((phase_p2,), sourcing_resource, organisations, 10)]) ppl2.start()
movement_list = applied_rule.objectValues() ppl2.deliver()
self.assertEqual(len(expected_value_set), len(movement_list)) self.tic()
movement_value_set = set([]) self.checkStock(resource, (destination, '', 10))
for movement in movement_list:
movement_value_set |= set([(tuple(movement.getCausalityList()),
movement.getResource(),
(movement.getSourceSection(),
movement.getSource(),
movement.getDestinationSection(),
movement.getDestination(),), # organisations
movement.getQuantity())])
self.assertEqual(expected_value_set, movement_value_set)
def test_suite(): def test_suite():
......
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