-
Notifications
You must be signed in to change notification settings - Fork 41
Processes Configuration
The engine creates processes using process templates. Process template holds information about manager class, process class, and process template. Process template also has operations templates and their dependencies.
The Manager class is responsible for building process, starting it. It also gets notified when some operation completed. When some operation is completed, the manager class responsible for completing a process. It also should be extended to some other features like messaging between processes etc.
Process holds status information, responsible for processing and dependencies resolution. The process can start if has some operations. The process starts by starting not yet started operations. The process can be completed if has no incomplete operations and unresolved errors. When a process completes, it should notify parent operation (if there is any) - this way parent process will be notified that child process was completed.
When some operation is completed, process class checks process template to find operations which depend on completed operation. Process class creates and starts new operations. This is a good point to customize building and starting new operations when some operation is completed.
Process template has information about operation templates and their dependencies. A process template is responsible for building process - by default, it creates process context and independent operations. It also responsible for building operations using operation templates.
An operation has operation template same way as a process has process template.
I scaffolded Product, Order, and OrderLine for that demo application. I am not trying to create real world application so those scaffolds are just to allow us to configure and create processes. I will not describe product and order in details – you can check commit 'Order and Product scaffolds' if you need to check anything but this is just dummy models to demonstrate workflow.
Create few products so that customers will be able to create orders. You just need to specify the name, stock quantity and minimum quantity for any product. We will use minimum quantity later when we will add stock provisioning process. Or you can use dump I added to commit.
When everything is installed, navigate to http://localhost:3000/workflow/ and click ‘Processes’. You should see something like that:
Click ‘Configuration’ You will see the empty list of process templates.
Click on ‘Add Process Template’ and create the new one. The only thing you need to fill in ‘New Process Template’ form is its title. Fill it with ‘Processing Order’ and click ‘Save’.
When the customer creates the new order, the system starts the process for it. First, it validates order positions to check if they are valid (since this is example application we will fail that validation). If order validation fails, the system creates user operation for a sales team to correct order (for example contact customer and update order lines).
Click ‘Add Operation’ and select ‘Default Operation’ from operations list. Fill new operation title with ‘Order Validation’ and set operation class to ‘OrderValidation’.
Each operation has a context which we can use to store and propagate some variables between operations. I will describe a context in details on a separate page but here just let’s say that we added orderValid = false to operation context. We will use it in next operation.
def update
update! do |success, failure|
success.html do
if current_operation && (params['commit'] == 'Complete')
current_operation.complete
end
redirect_to root_path
end
end
end
The only thing this operation doing is setting orderValid to false.
Later we will modify this process and create a child process for postponed order if some order positions are absent in stock right now. When they will be delivered to a warehouse that postponed orders will start. But right now there will be simple user operation to correct order information.
On process template click ‘Add Operation’ and select ‘Operation for User by Role’. Set Role to ‘Sales Team’, title to ‘Correct Invalid Order Information’, operation class to ‘ProcessInvalidOrder’ and operation template class to ‘ProcessInvalidOrderTemplate’.
In dependencies check “Order Validation” and “Done” state. This way when “Order Validation” will be completed and get “Done” state ProcessInvalidOrderTemplate’s resolve_dependency will get that operation to calculate if it needs to create a new operation.
class ProcessInvalidOrderTemplate < RailsWorkflow::OperationTemplate
...
def resolve_dependency operation
!operation.data[:orderValid]
end
end
We always set orderValid to false – resolve_dependency will always return true and the process will create ‘Correct Invalid Order Information’. Set User instructions with ‘This order has some invalid positions!’. We will show that instruction when user will pick up that operation.
Click ‘Save’.
Next operation will reserve ordered positions in the stock. Add another default operation to our process: ‘Reserve Stock Positions’. Important to note that you need to check ‘Order Validation’ with ‘Done’ status and ‘Correct Order Information’ with ‘Done’ status.
When any of these operations completed with ‘Done’ status – ResolveStockPositionsTemplate will check if context orderValid = true and create a new operation. If the first operation had orderValid = false and second operation completed having orderValid = true – then this operation will be created after the second operation.
Dependency resolution is very simple in Rails Workflow engine: when the operation is completed process searches for operation templates, depending on this operation and it’s current state. For every found operation (template) it checks if resolve_dependency returns true – and creates a new operation in that case. This way you can add any conditions you may need including some database records validation, operations context and any other conditions you may need.
Right now your process template looks like this:
Now update OrdersController’s create method:
def create
@order = Order.create(
order_params.merge(customer: current_user)
)
create! do |success, failure|
success.html do
process_template_id = 1
RailsWorkflow::ProcessManager.start_process(
process_template_id , { resource: resource }
)
redirect_to orders_path
end
end
end
Now we can test everything. If you used dump.sql then you already have all users you need. Otherwise, you can run rake seed takes to create all necessary users:
bundle exec rake db:seed
Click on ‘Create New Order’, fill order lines with some numbers and click ‘Create Order’. This will create a new order and start a new process.
Navigate to http://localhost:3000/workflow/processes – you will see process ‘Processing Order’ with status ‘In Progress’. Also here you can see that we have open user operation ‘Correct Invalid Order Information’. The open operation means that it is not yet assigned to any specific user.
On a process page, you can see process context, process operation, not yet created operations from process template – they all shown with their dependencies and other information. Here you can track your process.
Login as ‘[email protected]’ (password ‘Qwerty123’) and navigate to ‘User Operations’ – here you will see the new operation in ‘Available’ section. As you will start operation – it will be assigned to you. Available means that you can pickup that operation as it not yet assigned to the specific user. Click on ‘Start’ button. You will be redirected to order show page. You will see operation title and instructions we added on operation template.
If you open views/orders/edit.html.slim – you will see that I am using current_operation helper to get current user operation. I will describe user operations in details in one of next posts. Here I want to note following things:
class ProcessInvalidOrderTemplate < Workflow::OperationTemplate
def build_operation operation
resource = operation.data[:resource]
operation.title += " ##{resource.id}"
operation.context.data.merge!({
url_path: :edit_order_path,
url_params: [resource]
})
end
...
end
As you can see we need to add url_path and url_params to user operation so that when the user starts operation engine redirect that user to some resource.
In OrdersController’s update method we check if ‘Complete’ button was clicked and completing the operation.
def update
update! do |success, failure|
success.html do
if current_operation && (params['commit'] == 'Complete')
current_operation.complete
end
redirect_to root_path
end
end
end