Skip to content

Set Up Order Approval Workflows

⏱ 25 minutes intermediate
📜Corecommerce

In B2B commerce, not every buyer has unlimited purchasing authority. A junior procurement specialist might need manager approval for orders over $500. A department might have a monthly budget cap. Approval workflows enforce these rules automatically, preventing unauthorized spending while keeping the purchasing process moving.

This guide covers building approval chains, setting budget thresholds, and configuring delegate rules for when approvers are unavailable.

  1. Define an approval chain with budget thresholds
  2. Create the cart as a requisition (pending approval)
  3. Implement approval and rejection logic
  4. Configure delegate rules for absent approvers

Set up the rules that determine when an order requires approval and who can approve it.

Budget-based approval rules
csharp
public class ApprovalRule
{
    public string OrganizationId { get; set; }
    public decimal Threshold { get; set; }
    public string ApproverRole { get; set; }
}

public class ApprovalRuleService
{
    public IEnumerable<ApprovalRule> GetRulesForOrg(
        string organizationId)
    {
        return new List<ApprovalRule>
        {
            new ApprovalRule
            {
                OrganizationId = organizationId,
                Threshold = 500m,
                ApproverRole = "Manager"
            },
            new ApprovalRule
            {
                OrganizationId = organizationId,
                Threshold = 5000m,
                ApproverRole = "Director"
            },
            new ApprovalRule
            {
                OrganizationId = organizationId,
                Threshold = 25000m,
                ApproverRole = "VP"
            }
        };
    }

    public string GetRequiredApproverRole(
        string organizationId, decimal orderTotal)
    {
        var rules = GetRulesForOrg(organizationId)
            .OrderBy(r => r.Threshold);

        foreach (var rule in rules)
        {
            if (orderTotal >= rule.Threshold)
                return rule.ApproverRole;
        }

        return null; // No approval required
    }
}

When a buyer submits a cart that exceeds their spending authority, convert it to a requisition instead of a purchase order. The requisition waits for approval before processing.

Submit cart for approval
csharp
using EPiServer.Commerce.Order;

public class RequisitionService
{
    private readonly IOrderRepository _orderRepo;
    private readonly ApprovalRuleService _approvalRules;

    public RequisitionService(
        IOrderRepository orderRepo,
        ApprovalRuleService approvalRules)
    {
        _orderRepo = orderRepo;
        _approvalRules = approvalRules;
    }

    public OrderSubmitResult SubmitForApproval(
        ICart cart, string organizationId)
    {
        var total = cart.GetTotal().Amount;
        var approverRole = _approvalRules
            .GetRequiredApproverRole(
                organizationId, total);

        if (approverRole == null)
        {
            // Under threshold, process directly
            var orderRef = _orderRepo
                .SaveAsPurchaseOrder(cart);
            _orderRepo.Delete(cart.OrderLink);
            return new OrderSubmitResult
            {
                RequiresApproval = false,
                OrderId = orderRef.OrderGroupId
            };
        }

        // Over threshold, save as requisition
        cart.Properties["ApprovalStatus"] = "Pending";
        cart.Properties["RequiredApproverRole"]
            = approverRole;
        cart.Properties["SubmittedDate"]
            = DateTime.UtcNow;
        _orderRepo.Save(cart);

        return new OrderSubmitResult
        {
            RequiresApproval = true,
            RequiredApprover = approverRole
        };
    }
}

public class OrderSubmitResult
{
    public bool RequiresApproval { get; set; }
    public string RequiredApprover { get; set; }
    public int OrderId { get; set; }
}

Approvers review pending requisitions and either approve (converting to a purchase order) or reject them.

Approve or reject a requisition
csharp
public class ApprovalService
{
    private readonly IOrderRepository _orderRepo;

    public ApprovalService(IOrderRepository orderRepo)
    {
        _orderRepo = orderRepo;
    }

    public IPurchaseOrder Approve(ICart requisition,
        string approverId)
    {
        requisition.Properties["ApprovalStatus"]
            = "Approved";
        requisition.Properties["ApprovedBy"]
            = approverId;
        requisition.Properties["ApprovedDate"]
            = DateTime.UtcNow;

        var orderRef = _orderRepo
            .SaveAsPurchaseOrder(requisition);
        var order = _orderRepo
            .Load<IPurchaseOrder>(
                orderRef.OrderGroupId);

        _orderRepo.Delete(requisition.OrderLink);
        return order;
    }

    public void Reject(ICart requisition,
        string approverId, string reason)
    {
        requisition.Properties["ApprovalStatus"]
            = "Rejected";
        requisition.Properties["RejectedBy"]
            = approverId;
        requisition.Properties["RejectionReason"]
            = reason;

        _orderRepo.Save(requisition);
    }
}

When an approver is unavailable (vacation, leave), delegate their authority to another user to prevent bottlenecks.

Delegate scenarioImplementation
Temporary delegateSet a date range during which a backup approver handles requests
Permanent alternateAssign a secondary approver who can always approve in parallel
Escalation timeoutAuto-escalate to the next approval tier after a configurable number of days
IssueCauseFix
All orders bypass approvalThreshold set higher than typical order valuesLower the approval threshold to match your spending policies
Approver cannot see pending requisitionsApprover not in the correct role for the organizationVerify the approver’s role matches the RequiredApproverRole
Requisition stuck in pendingNo delegate configured and approver is unavailableSet up a delegate rule or escalation timeout
Budget resets unexpectedlyBudget period not aligned with fiscal calendarVerify budget period start/end dates match your organization’s fiscal year