This how to will show how to create a simple version of ERP5 Trade from scratch.
Table of Contents
What is ERP5 Trade ?¶
ERP5 Trade is a Business Template used to manage customer's and supplier's orders.
What is the purpose of this HowTo ?¶
This HowTo will describe how to create a basic version of ERP5 Trade from scratch, and how we use the simulation in this case. We will assume that user is used to ERP5, and masters other HowTos, like
Warning¶
This HowTo is only for a training purpose. If you want to use ERP5 Trade on a production site, don't follow this HowTo and download the erp5_trade Business Template.
Step One: Prepare the site¶
Install ERP5¶
Download ERP5 and install it.
Create the Nodes¶
Install the business template erp5_base, which allows us to create node objects in ERP5.
Create 2 organisations,
- one called OurOrganisation representing us
- one called TheCustomer representing the customer
Create a product¶
Install the business template erp5_pdm, which allows us to create resource objects in ERP5.
Now, we can create a product with the title AGreatProduct in the Products Module.
Step Two: create an order¶
Create the Order Module¶
Create the module where the orders will be entered. Give those parameters to the module creation script:
- Module ID: order_module
- Module Portal Type: Order Module
- Module Title: Orders
- Object Portal Type: Order
- Object Title: Order
- Portal Skins Folder: basic_trade
Configure Order Portal Type¶
Order will represent the contract between us and the customer. In the ERP5 Model, Order will be considered as Movement, so, we need to modify the factory method. We will use the Document Order, which does not catalog his content's movement in the stock table. Go in portal_types tool, and modify the portal type Order like this:
- Product meta type: ERP5 Order
- Product factory method: addOrder
- In 'Groups' select 'order'
When creating, we will add the Portal Type Order to the Portal Method getPortalOrderTypeList by adding order to the Groups attribute on the portal type level. This configuration permits to make acquistion between line and order.
Configure Order Module¶
We can now filter the content types in the Order Module. Check Filter content types?, and select Order in Allowed content types on the Order Module's Portal Type.
Create Order Workflow¶
First of all, associate edit_workflow to the Order Portal Type.
Then, we need to create manually the order_workflow. This workflow will be used to generated the simulation related to the order, and to build the related packing list. Add it in portal_workflow. As we will create movement, use the state variable name: simulation_state. Now, it's time to create our states: draft, planned, confirmed. Create the transitions:
- plan from draft to planned with its trigger type as 'Initiated by WorkflowMethod'
- confirm from planned to confirmed with its trigger type as 'Initiated by WorkflowMethod'
- plan_action with destination as `(Remain in State)', trigger type as 'Initiated by User Action', name as 'Plan Order' and URL as '%(content_url)s/!BaseWorkflow_viewWorkflowActionDialog?workflow_action=plan_action'
- confirm_action with destination as `(Remain in State)', trigger type as 'Initiated by User Action', name as 'Confirm Order' and URL as '%(content_url)s/!BaseWorkflow_viewWorkflowActionDialog?workflow_action=confirm_action'
Finally, associate the order_workflow to the portal type Order.
As we need to generate the workflow methods, the easiest way currently is to restart the site.
Display the Order¶
Modify Order_view, in order to display the attributes:
- title, which is the reference of the order:
- add StringField with Id "my_title"
- source, where the products come from (an organisation representing ourself)
- add RelationStringField with Id "my_source_title", and:
- set 'Base Category' to "source"
- set 'Portal Type' to "Organisation"
- set 'Catalog Index' to "title"
- destination, where the products are going to (an organisation representing the customer)
- add RelationStringField with Id "my_destination_title", and:
- set 'Base Category' to "destination"
- set 'Portal Type' to "Organisation"
- set 'Catalog Index' to "title"
- start_date, when we send the products:
- add DateTimeField with Id "my_start_date"
- set 'Display date only'
- set 'Input style' to "list"
- stop_date, when the products arrived to the customer
- add DateTimeField with Id "my_stop_date"
- set 'Display date only'
- set 'Input style' to "list"
- simulation state
- add StringField with Id "my_simulation_state"
- unset 'Editable'
- in settings set 'Form action' to 'Base_edit'
Note: You migth have to restart zope before continuing.
Create the order object¶
At this point, we can create an Order in the Order Module, with those attribute's values:
- title: 0000001
- source: OurOrganisation
- destination: TheCustomer
- start_date: 2006/05/09
- stop_date: 2006/05/19
Create the Order Line portal type¶
In order to enter the products we are selling, we need to create the portal type Order Line, with default type information: ERP5Type: ERP5 Order Line. When creating, add order_movement to the Groups, in order to make acquisition between line and simulation movement.
Add Order Line as available content type in Order¶
Add Order Line in Allowed content types on Order.
Create OrderLine_view¶
Modify OrderLine_view, in order to display the attributes:
- resource, which is the product we are selling (the object in the Products module):
- add 'RelationStringField' with Id "my_resource_title"
- set 'Base Category' to "resource"
- set 'Portal Type' to "Product"
- set 'Catalog Index' to "title"
- quantity, as an integer:
- add 'IntegerField' with Id "my_quantity"
- in Settings set 'Form action' to "Base_edit"
Create an OrderLine in the order 000001¶
At this point, we can create an OrderLine in our Order, with those attribute's values:
- resource: AGreatProduct
- quantity, 10
Modify Order_view, in order to view the lines¶
Add a listboxin Order_view, in order to display the order lines on the order level.
- in 'Columns' put lines:
- id
- resource_title
- quantity
- set 'List Method' to "contentValues"
Step Three: Generate the simulation¶
Create the OrderRule portal type¶
The simulation rule representing the order/packing list is called Order Rule in ERP5. If we want to use it, we have to add a new portal type Order Rule, with the default type information set to ERP5Type: ERP5 Order Rule.
Create an OrderRule instance¶
All rule's instances are saved in portal_rules. Add Order Rule as allowed content type in Rule Tool, and create a Order Rule instance via the default view of portal_rules.
Note: Do not worry, when you see Error Type: KeyError Error Value: 'OrderRule_view', simply change the form on the "view" action = Rule_view in your portal type "Order Rule".
Configure order_workflow¶¶
Time to configure the order_workflow, in order to make it create the simulation as soon as we change the state to planned. Add a workflow script called Order_createAppliedRule in order_workflow.
Give to this script the parameters: state_change, **kw.
Then, add this code:
Toggle line numbers
1 order = state_change['object']
2 # The ID of the rule in portal_rules we want to use.
3 order_rule_id = '1'
4 # Create the applied rule in activity.
5 tag = order.getPath() + '_firstUpdateAppliedRule'
6 activate_kw = {'tag':tag, 'priority':3}
7 order.activate(tag=tag).updateAppliedRule(
8 rule_id=order_rule_id,
9 activate_kw=activate_kw)
Toggle line numbers
order = state_change['object']
# The ID of the rule in portal_rules we want to use.
order_rule_id = '1'
# Create the applied rule in activity.
tag = order.getPath() + '_firstUpdateAppliedRule'
activate_kw = {'tag':tag, 'priority':3}
order.activate(tag=tag).updateAppliedRule(
rule_id=order_rule_id,
activate_kw=activate_kw)
Finally, set Order_createAppliedRule as the Script (after) of the plan transition.
Change the state of order 000001¶¶
Modify the state from draft to planned. After a few seconds, click on the Metadata tab of the Order. You will see /erp5/portal_simulation/1 as a related object of the order.
Check the simulation¶
In portal simulation, there is one Applied Rule in relation with the 'Order Rule' and our order.
It contains one simulation related to the order line. The simulation movement copied his attributes from the order line (currently, the screenshot is outdated).
Step Four: Generate the Packing List¶
Create the Packing List Module¶
Create the module where the packing list will be generated. Give those parameters to the module creation script:
- Module ID: packing_list_module
- Module Portal Type: Packing List Module
- Module Title: Packing Lists
- Object Portal Type: Packing List
- Object Title: Packing List
- Portal Skins Folder: basic_trade
packing_module_creation.png
Configure the Packing List¶
Go in portal_types tool, and modify the portal type Packing List like this:
- Product meta type: ERP5 Delivery
- Product factory method: addDelivery
When creating, add delivery to the Groups, in order to make acquisition between line and order.
Configure Packing List Module¶
We can now filter the content types in the Packing List Module. Check Filter content types?, and select Packing List in Allowed content types on the Packing List Module's Portal Type.
Create the Packing List Line portal type¶
Create the portal type Packing List Line, with default type information: ERP5Type: ERP5 Delivery Line. When creating, add delivery_movement to the Groups, in order to make acquisition between line and simulation movement.
packing_list_line_creation.png
Configure the forms¶
As for the Order and Order Line, create identical PackingList_view and PackingListLine_view, and associate them as the default view of there related Portal Type.
On PackingList_view, you just need to modify:
- my_translated_simulation_state_title to my_translated_causality_state_title
Add packing_list_causality_workflow¶
First of all, associate edit_workflow to the Packing List Portal Type.
Then, we need to create manually the packing_list_causality_workflow. This workflow will be used to detect inconsistency between prevision and decision on the Packing List level. Add it in portal_workflow. Use the state variable name: causality_state. Currently, set the default state to solved.
Configure the Packing List¶ Builder
In order to create automatically Packing List from the simulation, we need to create and configure a Delivery Builder. Go to portal_deliveries, and create a new Delivery Builder, with those attributes:
- ID: packing_list_builder
- title: Packing List Builder
- delivery module: Packing Lists
- delivery portal type: Packing List
- delivery line portal type: Packing List Line
- delivery cell portal type: Packing List Cell
Configure the Simulation Select Method¶
Create the script which will get the unassociated Simulation Movement. Add a python script 'Order_selectMovement' (with the parameter **kw) in the skin folder basic_trade.
Toggle line numbers
1 # This script is only for tutorial purpose and must not be used
2 # on a production system
3 result_list = []
4 # Get applied rule related to the order rule 1
5 applied_rule_list = [x for x in \
6 context.portal_simulation.contentValues() \
7 if x.getSpecialise() == 'portal_rules/1']
8 # Find simulation movement not associated to a packing list
9 for applied_rule in applied_rule_list:
10 for simulation_movement in applied_rule.contentValues():
11 if (simulation_movement.getPortalType() == \
12 'Simulation Movement') and \
13 (simulation_movement.getSimulationState() == 'confirmed') and \
14 (simulation_movement.getDelivery() == None):
15 # We found a matching movement
16 result_list.append(simulation_movement)
17
18 return result_list
Toggle line numbers
# This script is only for tutorial purpose and must not be used
# on a production system
result_list = []
# Get applied rule related to the order rule 1
applied_rule_list = [x for x in \
context.portal_simulation.contentValues() \
if x.getSpecialise() == 'portal_rules/1']
# Find simulation movement not associated to a packing list
for applied_rule in applied_rule_list:
for simulation_movement in applied_rule.contentValues():
if (simulation_movement.getPortalType() == \
'Simulation Movement') and \
(simulation_movement.getSimulationState() == 'confirmed') and \
(simulation_movement.getDelivery() == None):
# We found a matching movement
result_list.append(simulation_movement)
return result_list
When created, set Order_selectMovement as Simulation Select Method on the delivery builder.
Configure the Order Groups¶
After getting the simulation movements that we need to aggregate, we have to group them by attribute values.
First of all, we will define which kind of movement we want to group on the same packing list. In Delivery Collect Order Groups, add those values:
- OrderMovementGroup, which group the movements generated by the same Order, and set the causality on the Packing List created.
- DateMovementGroup, which sets the dates on the Packing List.
- PathMovementGroup, which sets the source and destination on the Packing List
Then, we will define how we group movements on the same line. Define in Delivery Line Collect Order Groups those values:
- ResourceMovementGroup, which group the movements which have the same Resource.
Finally, define also ResourceMovementGroup in Delivery Cell Collect Order Groups (but we will not generate cells in our example).
Configure order_workflow¶¶
Time to configure the order_workflow, in order to make it create the packing list as soon as we change the state to confirmed. Add a workflow script called Order_createPackingList in order_workflow.
Give to this script the parameters: state_change, **kw.
Then, add this code:
Toggle line numbers
1 order = state_change['object']
2 # The ID of the rule in portal_rules we want to use.
3 order_rule_id = '1'
4 # Update the applied rule in activity.
5 tag = order.getPath() + '_firstUpdateAppliedRule'
6 activate_kw = {'tag':tag, 'priority':3}
7 order.activate(tag=tag).updateAppliedRule(rule_id = order_rule_id,
8 activate_kw=activate_kw)
9
10 # Call build on the delivery builder
11 delivery_builder = order.portal_deliveries.packing_list_builder
12 delivery_builder.activate(activity='SQLQueue',
13 after_tag=tag).build()
Toggle line numbers
order = state_change['object']
# The ID of the rule in portal_rules we want to use.
order_rule_id = '1'
# Update the applied rule in activity.
tag = order.getPath() + '_firstUpdateAppliedRule'
activate_kw = {'tag':tag, 'priority':3}
order.activate(tag=tag).updateAppliedRule(rule_id = order_rule_id,
activate_kw=activate_kw)
# Call build on the delivery builder
delivery_builder = order.portal_deliveries.packing_list_builder
delivery_builder.activate(activity='SQLQueue',
after_tag=tag).build()
Finally, set Order_createPackingList as the Script (after) of the confirm transition.
Change the state of order 000001¶¶
Modify the state from planned to confirmed. After a few seconds, click on the Metadata tab of the Order. You will see /erp5/packing_list_module/1 as a related object of the order.
Check the Packing List¶
In packing_list_module, you can see the created Packing List, with the same values as the Order. The causality state is solved.
Step Five: Dealing with the Causality (outdated)¶
It's time for us to improve our causality workflow, in order to detect modification made by the user on the packing list, and to help the user to solve the problems.
Improve packing_list_causality_workflow¶
We will design the causality workflow in a simple way. It will be a 2 states workflow:
- solved, when Packing List and Simulation are identical
- diverged, when there is a difference
Add 2 Workflow Methods in the workflow:
- converge, which modify the state to solved
- diverge, which modify the state to diverged
Both of them can be called from solved and diverged states.
Picture Missing: packing_list_causality_workflow_version1.png
Create packing_list_interaction_workflow¶
In order to call the workflow methods converge and diverge automatically when we modify the packing list, we need to create an interaction workflow.
In portal_workflow, add a new interaction_workflow with the name packing_list_interaction_workflow, and associate it to the portal types Packing List and Packing List Line.
In packing_list_interaction_workflow, create 2 python scripts with state_change and **kw as arguments.
Then, we will add 2 interactions with the trigger method id edit and _edit:
- PackingList_edit, filtered with Packing List, calling PackingList_modifyCausality as Script (after)
- PackingListLine_edit, filtered with Packing List Line, calling PackingListLine_modifyCausality as Script (after)
Now, restart the site in order to generate all workflow methods on the Portal Type.
Adding a Divergence Tester¶
In order to precise on which property(ies) controls are done, you will have to add a divergence tester on your Order Rule. To achieve this, add by instance an Allowed Content Type = "Quantity Divergence Tester" for your portal type Order Rule if you want to test quantities on your Packing List Lines. Then go to your rule "portal_rules/1" and add an instance of this Quantity Divergence Tester.
Testing on our Packing List¶
It's time to test our workflows.
Go to the packing_list_module/1. Modify the quantity on the line (for example, change 10 to 9). You will see that causality state is now diverged.
If you change again the value to 10, the causality state will be back to Solved(converged).
Our system is now able to detect divergency, but, can not resolve it manually, when we want to impact the simulation with the values entered on the packing list.
Step Six: Solving the divergency¶
Modify packing_list_causality_workflow¶
In packing_list_causality_workflow, add a Python script called PackingList_acceptDecision, with those parameters: state_change, **kw.
Toggle line numbers
1 packing_list = state_change['object']
2
3 # Make sure applied rule is 100% indexed
4 # Here, applied rule must be already cleanly created
5
6 # We do not use DeliverySolver, because, by default,
7 # we keep delivery_ratio untouch
8
9 # CopyToTarget is a TargetSolver which copy
10 # the PackingListLine quantity to the Simulation Movement Quantity
11 packing_list.portal_simulation.solveDelivery(packing_list, None,
12 "CopyToTarget")
13
14 # Automatic workflow
15 packing_list.activate().updateCausalityState()
Toggle line numbers
packing_list = state_change['object']
# Make sure applied rule is 100% indexed
# Here, applied rule must be already cleanly created
# We do not use DeliverySolver, because, by default,
# we keep delivery_ratio untouch
# CopyToTarget is a TargetSolver which copy
# the PackingListLine quantity to the Simulation Movement Quantity
packing_list.portal_simulation.solveDelivery(packing_list, None,
"CopyToTarget")
# Automatic workflow
packing_list.activate().updateCausalityState()
Then, add 2 new transitions in this workflow:
- accept_decision, which is a WorkflowMethod, calling the Python Script PackingList_acceptDecision in the Script (after)
- accept_decision_action, which is a User action, calling accept_decision in the Script (after) with Name as Accept Decision and URL as and URL as '%(content_url)s/?BaseWorkflow_viewWorkflowActionDialog?workflow_action=accept_decision_action
Both of those transitions can be called when we are in the diverged state.
As usual, restart the site for generating the Workflow Methods.
Testing on the Packing List¶
On our packing list, modify the quantity line to 9. The causality state will be again diverged.
Now, you will see Accept Decision as a available workflow transition in the Action Menu.
Select this action, and after a few second, you will see that even if the quantity is still 9, the causality state of the packing list is now solved.
Checking the Simulation Movement¶
If you now look at the simulation movement portal_simulation/1/1, you will see that the quantity is now 9, with the action of our Solver.
Related Articles¶