Skip to Content
Technical Articles
Author's profile photo Priti Mittal

Order Approval Workflow customisation

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 ,

  1. SAP Commerce 2211
  2. SAP Commerce Integration pack 2211
  3. 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.

 

Thank You

 

Assigned Tags

      1 Comment
      You must be Logged on to comment or reply to a post.
      Author's profile photo Yashwanth Kumar Koratikere
      Yashwanth Kumar Koratikere

      Thank you for the article, Priti Mittal. It will be helpful in future projects.