In commerce implementation project , sometimes there is a need to customize workflow to achieve and fulfil business requirement . This blog is to talk about specific use case where order approval workflow is customised to achieve two level of approval .
Context
Let say , B2B customer places an order on B2B Portal and does not have specific role to get his order auto-approved . And business wants such order to go through the 2 level of approvals . Every level has more than one authorised users assigned .
Below flowchart gives better idea to describe the requirement .
Pre-requisite :
Following example is set based upon ,
-
- SAP Commerce 2211
-
- SAP Commerce Integration pack 2211
-
- Basic understanding of workflow
Solution :
-
-
-
- As per standard flow , order approval process has one of the action called “startworkflow”
-
- Reference of “startworkflow” has intern logic to execute <>WorkflowTemplateStrategy . This strategy file has the logic to create workflow and its steps on fly.
-
- We will introduce new strategy for this use case.
-
-
Step-1 : Extend enumType WorkflowTemplateType and add new template type let say “DOUBLE_ORDER_APPROVAL”
<enumtype code="WorkflowTemplateType" autocreate="false" generate="false" dynamic="true">
<value code="DOUBLE_ORDER_APPROVAL"/>
</enumtype>
Step-2 :Create new strategy let say DoubleOrderApprovalWorkflowTemplateStrategy
public class DoubleOrderApprovalWorkflowTemplateStrategy extends AbstractWorkflowTemplateStrategy
{
....
}
Step-3 : Override method createWorkflowTemplate() . Write custom logic to create workflow and steps within .
@Override
public WorkflowTemplateModel createWorkflowTemplate(final List<? extends UserModel> users, final String code,
final String description)
{
//2nd level of approvers
List<UserModel> secondLevelofApprovers = filterSecondLevelofApprovers(users);
//1st level of approvers
List<UserModel> firstLevelofApprovers = filterFirstLevelofApprovers(users);
return (WorkflowTemplateModel) getSessionService().executeInLocalView(new SessionExecutionBody()
{
@Override
public Object execute()
{
final UserModel admin = getUserService().getAdminUser();
getUserService().setCurrentUser(admin);
if (LOG.isDebugEnabled())
{
LOG.debug(String.format("Creating WorkflowTemplate for code %s with description %s", code, description));
}
final WorkflowTemplateModel workflowTemplateModel = createBlankWorkflowTemplate(code, description, admin);
final AutomatedWorkflowActionTemplateModel wakeUpProcessEngineAutomatedAction = createAutomatedWorkflowActionTemplate(
code, B2BWorkflowIntegrationService.ACTIONCODES.BACK_TO_PROCESSENGINE.name(), WorkflowActionType.NORMAL, admin,
workflowTemplateModel, null, "b2bAfterWorkflowAction");
final AutomatedWorkflowActionTemplateModel afterApproveOrderWorkflowDecisionAction = createAutomatedWorkflowActionTemplate(
code, B2BWorkflowIntegrationService.ACTIONCODES.APPROVED.name() + "_END", WorkflowActionType.NORMAL, admin,
workflowTemplateModel, null, "afterApproveOrderWorkflowDecisionAction");
final AutomatedWorkflowActionTemplateModel afterRejectOrderWorkflowDecisionAction = createAutomatedWorkflowActionTemplate(
code, B2BWorkflowIntegrationService.ACTIONCODES.REJECTED.name() + "_END", WorkflowActionType.NORMAL, admin,
workflowTemplateModel, null, "afterRejectOrderWorkflowDecisionAction");
final AutomatedWorkflowActionTemplateModel adminApproveDecisionAutomatedAction = createAutomatedWorkflowActionTemplate(
code, B2BWorkflowIntegrationService.ACTIONCODES.APPROVED.name(), WorkflowActionType.NORMAL, admin,
workflowTemplateModel, null, "approveDecisionAutomatedAction");
for (final UserModel approver : firstLevelofApprovers)
{
final AutomatedWorkflowActionTemplateModel approveDecisionAutomatedAction = createAutomatedWorkflowActionTemplate(
code, B2BWorkflowIntegrationService.ACTIONCODES.APPROVED.name(), WorkflowActionType.NORMAL, approver,
workflowTemplateModel, null, "approveDecisionAutomatedAction");
final AutomatedWorkflowActionTemplateModel rejectDecisionAutomatedAction = createAutomatedWorkflowActionTemplate(
code, B2BWorkflowIntegrationService.ACTIONCODES.REJECTED.name(), WorkflowActionType.NORMAL, approver,
workflowTemplateModel, null, "rejectDecisionAutomatedAction");
final WorkflowActionTemplateModel action = createWorkflowActionTemplateModel(code,
B2BWorkflowIntegrationService.ACTIONCODES.APPROVAL.name(), WorkflowActionType.START, approver,
workflowTemplateModel);
// approve decision links and proceed to next level incase any of the approver approves
createLink(action, approveDecisionAutomatedAction, B2BWorkflowIntegrationService.DECISIONCODES.APPROVE.name(),
Boolean.FALSE);
createLink(approveDecisionAutomatedAction, adminApproveDecisionAutomatedAction,
B2BWorkflowIntegrationService.DECISIONCODES.APPROVE.name() + "_END", Boolean.FALSE);
// reject decision links
createLink(action, rejectDecisionAutomatedAction, B2BWorkflowIntegrationService.DECISIONCODES.REJECT.name(),
Boolean.FALSE);
createLink(rejectDecisionAutomatedAction, afterRejectOrderWorkflowDecisionAction,
B2BWorkflowIntegrationService.DECISIONCODES.REJECT.name() + "_END", Boolean.FALSE);
createLink(afterRejectOrderWorkflowDecisionAction, wakeUpProcessEngineAutomatedAction,
B2BWorkflowIntegrationService.ACTIONCODES.BACK_TO_PROCESSENGINE.name(), Boolean.FALSE);
}
List<WorkflowActionTemplateModel> toActions = new ArrayList<>();
WorkflowActionType workflowActionType = WorkflowActionType.NORMAL;
if(firstLevelofApprovers.isEmpty()){
workflowActionType = WorkflowActionType.START;
}
for (final UserModel approver : secondLevelofApprovers)
{
final AutomatedWorkflowActionTemplateModel salesApproveDecisionAutomatedAction = createAutomatedWorkflowActionTemplate(
code, B2BWorkflowIntegrationService.ACTIONCODES.APPROVED.name(), WorkflowActionType.NORMAL, approver,
workflowTemplateModel, null, "approveDecisionAutomatedAction");
final AutomatedWorkflowActionTemplateModel salesRejectDecisionAutomatedAction = createAutomatedWorkflowActionTemplate(
code, B2BWorkflowIntegrationService.ACTIONCODES.REJECTED.name(), WorkflowActionType.NORMAL, approver,
workflowTemplateModel, null, "rejectDecisionAutomatedAction");
final WorkflowActionTemplateModel salesAction = createWorkflowActionTemplateModel(code,
B2BWorkflowIntegrationService.ACTIONCODES.APPROVAL.name(), workflowActionType, approver,
workflowTemplateModel);
if(!firstLevelofApprovers.isEmpty()){
toActions.add(salesAction);
}
// approve decision links
createLink(salesAction, salesApproveDecisionAutomatedAction, B2BWorkflowIntegrationService.DECISIONCODES.APPROVE.name(),
Boolean.FALSE);
createLink(salesApproveDecisionAutomatedAction, afterApproveOrderWorkflowDecisionAction,
B2BWorkflowIntegrationService.DECISIONCODES.APPROVE.name() + "_END", Boolean.FALSE);
createLink(afterApproveOrderWorkflowDecisionAction, wakeUpProcessEngineAutomatedAction,
B2BWorkflowIntegrationService.ACTIONCODES.BACK_TO_PROCESSENGINE.name(), Boolean.FALSE);
// reject decision links
createLink(salesAction, salesRejectDecisionAutomatedAction, B2BWorkflowIntegrationService.DECISIONCODES.REJECT.name(),
Boolean.FALSE);
createLink(salesRejectDecisionAutomatedAction, afterRejectOrderWorkflowDecisionAction,
B2BWorkflowIntegrationService.DECISIONCODES.REJECT.name() + "_END", Boolean.FALSE);
createLink(afterRejectOrderWorkflowDecisionAction, wakeUpProcessEngineAutomatedAction,
B2BWorkflowIntegrationService.ACTIONCODES.BACK_TO_PROCESSENGINE.name(), Boolean.FALSE);
}
if(!firstLevelofApprovers.isEmpty())
{
createManyToActionsLink(adminApproveDecisionAutomatedAction, toActions,
B2BWorkflowIntegrationService.ACTIONCODES.APPROVED.name(), Boolean.FALSE);
}
//end finishAction to end workflow
final WorkflowActionTemplateModel finishAction = createWorkflowActionTemplateModel(code,
B2BWorkflowIntegrationService.ACTIONCODES.END.name(), WorkflowActionType.END, admin, workflowTemplateModel);
createLink(wakeUpProcessEngineAutomatedAction, finishAction, "WORKFLOW_FINISHED", Boolean.FALSE);
return workflowTemplateModel;
}
});
}
/**
* To link one fromActionTemplate to list of toTemplateActions(target)
* @param fromAction
* @param toActions
* @param qualifier
* @param isAndConnection
*/
private void createManyToActionsLink(final WorkflowActionTemplateModel fromAction, final Collection<WorkflowActionTemplateModel> toActions,
final String qualifier, final Boolean isAndConnection)
{
final WorkflowDecisionTemplateModel workflowDecisionTemplate = getModelService()
.create(WorkflowDecisionTemplateModel.class);
workflowDecisionTemplate.setName(qualifier, Locale.ENGLISH);
workflowDecisionTemplate.setCode(fromAction.getCode());
workflowDecisionTemplate.setQualifier(qualifier);
workflowDecisionTemplate.setActionTemplate(fromAction);
workflowDecisionTemplate.setToTemplateActions(toActions);
getModelService().save(workflowDecisionTemplate);
final Collection<WorkflowDecisionTemplateModel> decisionTemplates = new ArrayList<WorkflowDecisionTemplateModel>(
fromAction.getDecisionTemplates());
decisionTemplates.add(workflowDecisionTemplate);
fromAction.setDecisionTemplates(decisionTemplates);
this.getModelService().save(fromAction);
setConnectionBetweenActionAndDecision(null, isAndConnection, workflowDecisionTemplate);
}
Step-4 : Override Method getWorkflowTemplateType() . provide the templateCode to refer further.
@Override
public String getWorkflowTemplateType()
{
return WorkflowTemplateType.DOUBLE_ORDER_APPROVAL.getCode();
}
Step-5 : Create the bean reference in *-spring.xml file
<bean id="doubleOrderApprovalWorkflowStrategy" class="<replace_with_currect_package>.DoubleOrderApprovalWorkflowTemplateStrategy" parent="abstractWorkflowTemplateStrategy"/>
Step-6 : Refer below code to refer custom template and strategy in StartWorkFlow .
WorkflowTemplateType templateType = WorkflowTemplateType.DOUBLE_ORDER_APPROVAL;
final WorkflowTemplateModel workflowTemplate = b2bWorkflowIntegrationService.createWorkflowTemplate(approvers,workflowTemplateCode, "Generated B2B Double Order Approval Workflow", templateType);
final WorkflowModel workflow = workflowService.createWorkflow(workflowTemplate.getName(), workflowTemplate,Collections.singletonList(aprovalProcess), workflowTemplate.getOwner());
workflowProcessingService.startWorkflow(workflow);
this.modelService.saveAll();
order.setWorkflow(workflow);
order.setStatus(OrderStatus.PENDING_APPROVAL);
this.modelService.save(order);
Conclusion
This post describes very specific use case to give a flavour and thought process to customize Hybris workflow . This can further customised or modified as per need and business ask .
Finally, share your feedback. This would encourage me to add more content in the coming days.