Architectural options & step-by-step implementation guide for enforcing weekly capacity limits by Work Type or Service Resource.
When enforcing volume limits (such as restricting a Work Type or Service Resource to maximum 3 appointments per calendar week), system architects face a core trade-off between implementation effort and user experience (UX).
| Approach | Validation Stage | User Experience Impact | Complexity |
|---|---|---|---|
| Post-Selection Validation Before-Save Flow / Trigger |
At Record Save (after slot selection) | User selects slot, fills details, and clicks Save. System blocks save and throws error if cap is reached. User must go back and select another time. | Low to Medium |
| Proactive UI Filtering Invocable Apex + LWC / Flow |
During Slot Rendering (pre-selection) | Fully seamless. Fully booked slots are filtered out prior to display or marked unavailable. Database backstop ensures consistency. | Medium to High |
Best for quick setup and low maintenance without custom Apex code.
In Setup → Flows → New Flow (Record-Triggered Flow on ServiceAppointment), add these formula resources:
Data Type: DateTime. Calculates midnight on Sunday starting the appointment's week (UTC).
DATETIMEVALUE(
TEXT(
DATEVALUE({!$Record.SchedStartTime})
- MOD(WEEKDAY(DATEVALUE({!$Record.SchedStartTime})) - 1, 7)
) & " 00:00:00"
)
Data Type: DateTime. Calculates midnight on the following Sunday (exclusive boundary).
DATETIMEVALUE(
TEXT(
DATEVALUE({!$Record.SchedStartTime})
- MOD(WEEKDAY(DATEVALUE({!$Record.SchedStartTime})) - 1, 7) + 7
) & " 00:00:00"
)
Trigger Conditions: Fast Field Updates (Before Save) when SchedStartTime Is Null = False.
Get Records: Retrieve ServiceAppointment records where WorkTypeId (or Resource) matches current record, starting between {!WeekStartDateTime} and {!WeekEndDateTime}, excluding cancelled statuses.
Decision & Custom Error: Count retrieved records. If count ≥ limit, route to a Custom Error element displaying a friendly message to the user.
Enterprise pattern combining proactive UI pre-checking for LWC/Flows with an unbreakable Apex database backstop.
| Field Label | API Name | Type | Description |
|---|---|---|---|
| Work Type Id | WorkTypeId__c | Text (18) | 18-char ID (Leave blank for global wildcard rule) |
| Service Resource Id | ServiceResourceId__c | Text (18) | Optional ID for resource-level capping |
| Weekly Limit | WeeklyLimit__c | Number (3,0) | Maximum appointments allowed per week |
| Excluded Statuses | ExcludedStatuses__c | Text (255) | Comma-separated list (e.g. Cancelled,Declined) |
| Timezone Key | TimezoneKey__c | Text (50) | Java timezone ID (e.g. America/New_York) |
public without sharing class ServiceAppointmentConstraintHandler {
private static final String DEFAULT_TZ = 'America/New_York';
private static final String DEFAULT_EXCLUDED = 'Cancelled';
public static void validate(List<ServiceAppointment> newList, Map<Id, ServiceAppointment> oldMap) {
Map<String, AppointmentConstraint__mdt> cfgByWorkType = loadMetadata();
AppointmentConstraint__mdt wildcardCfg = cfgByWorkType.get('WILDCARD');
List<ServiceAppointment> toCheck = new List<ServiceAppointment>();
for (ServiceAppointment sa : newList) {
if (sa.SchedStartTime == null || sa.WorkTypeId == null) continue;
if (oldMap == null || sa.SchedStartTime != oldMap.get(sa.Id).SchedStartTime) {
toCheck.add(sa);
}
}
if (toCheck.isEmpty()) return;
for (ServiceAppointment sa : toCheck) {
AppointmentConstraint__mdt cfg = cfgByWorkType.containsKey(sa.WorkTypeId) ? cfgByWorkType.get(sa.WorkTypeId) : wildcardCfg;
if (cfg == null) continue;
Integer count = getExistingCount(sa.WorkTypeId, sa.SchedStartTime);
if (count >= cfg.WeeklyLimit__c) {
sa.addError('Weekly appointment cap of ' + cfg.WeeklyLimit__c + ' reached for this service.');
}
}
}
public static Integer getExistingCount(Id workTypeId, Datetime schedTime) {
Datetime weekStart = getWeekStart(schedTime);
Datetime weekEnd = weekStart.addDays(7);
return [SELECT COUNT() FROM ServiceAppointment
WHERE WorkTypeId = :workTypeId
AND SchedStartTime >= :weekStart
AND SchedStartTime < :weekEnd
AND Status != 'Cancelled'];
}
private static Datetime getWeekStart(Datetime dt) {
Date d = dt.dateGmt();
Date sunday = d.toStartOfWeek();
return Datetime.newInstanceGmt(sunday, Time.newInstance(0,0,0,0));
}
private static Map<String, AppointmentConstraint__mdt> loadMetadata() {
Map<String, AppointmentConstraint__mdt> mapResult = new Map<String, AppointmentConstraint__mdt>();
for (AppointmentConstraint__mdt c : [SELECT WorkTypeId__c, WeeklyLimit__c, ExcludedStatuses__c, TimezoneKey__c FROM AppointmentConstraint__mdt]) {
if (String.isBlank(c.WorkTypeId__c)) mapResult.put('WILDCARD', c);
else mapResult.put(c.WorkTypeId__c, c);
}
return mapResult;
}
}
public class AppointmentConstraintInvocable {
public class SlotCheckRequest {
@InvocableVariable(required=true) public Id workTypeId;
@InvocableVariable(required=true) public Datetime slotStartTime;
}
public class SlotCheckResult {
@InvocableVariable public Boolean isAvailable;
@InvocableVariable public Integer currentCount;
}
@InvocableMethod(label='Pre-Check Slot Volume Constraint' description='Checks whether a given slot exceeds weekly volume limits.')
public static List<SlotCheckResult> checkSlot(List<SlotCheckRequest> requests) {
List<SlotCheckResult> results = new List<SlotCheckResult>();
for (SlotCheckRequest req : requests) {
SlotCheckResult res = new SlotCheckResult();
Integer count = ServiceAppointmentConstraintHandler.getExistingCount(req.workTypeId, req.slotStartTime);
res.currentCount = count;
res.isAvailable = (count < 3); // Evaluate against configured MDT limit
results.add(res);
}
return results;
}
}
trigger ServiceAppointmentConstraint on ServiceAppointment (before insert, before update) {
ServiceAppointmentConstraintHandler.validate(Trigger.new, Trigger.oldMap);
}