Set Up Order Approval Workflows
Why approval workflows matter
Section titled “Why approval workflows matter”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.
What you will do
Section titled “What you will do”- Define an approval chain with budget thresholds
- Create the cart as a requisition (pending approval)
- Implement approval and rejection logic
- Configure delegate rules for absent approvers
Define approval thresholds
Section titled “Define approval thresholds”Set up the rules that determine when an order requires approval and who can approve it.
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
}
} Create a requisition
Section titled “Create a requisition”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.
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; }
} Implement approval and rejection
Section titled “Implement approval and rejection”Approvers review pending requisitions and either approve (converting to a purchase order) or reject them.
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);
}
} Configure delegate rules
Section titled “Configure delegate rules”When an approver is unavailable (vacation, leave), delegate their authority to another user to prevent bottlenecks.
| Delegate scenario | Implementation |
|---|---|
| Temporary delegate | Set a date range during which a backup approver handles requests |
| Permanent alternate | Assign a secondary approver who can always approve in parallel |
| Escalation timeout | Auto-escalate to the next approval tier after a configurable number of days |
Common issues
Section titled “Common issues”| Issue | Cause | Fix |
|---|---|---|
| All orders bypass approval | Threshold set higher than typical order values | Lower the approval threshold to match your spending policies |
| Approver cannot see pending requisitions | Approver not in the correct role for the organization | Verify the approver’s role matches the RequiredApproverRole |
| Requisition stuck in pending | No delegate configured and approver is unavailable | Set up a delegate rule or escalation timeout |
| Budget resets unexpectedly | Budget period not aligned with fiscal calendar | Verify budget period start/end dates match your organization’s fiscal year |