
Building Omnistudio Callable Apex
- 1.9k installs
- 763 repo stars
- Updated July 24, 2026
- forcedotcom/sf-skills
building-omnistudio-callable-apex is an agent skill that Salesforce Industries Common Core (OmniStudio/Vlocity) Apex callable generation and review skill with 120-point scoring.
About
building omnistudio callable apex Callable Apex for Salesforce Industries Common Core Specialist for Salesforce Industries Common Core callable Apex implementations Produce secure deterministic and configurable Apex that cleanly integrates with OmniStudio and Industries extension points In scope Creating System Callable classes for Industries extension points reviewing callable implementations for correctness and risks migrating VlocityOpenInterface VlocityOpenInterface2 to System Callable 120 point scoring and validation Out of scope Generic Apex classes without callable interface use generating apex building Integration Procedures use building omnistudio integration procedure authoring OmniScripts use building omnistudio omniscript deploying Apex classes use deploying metadata 1 Callable Generation Build System Callable classes with safe action dispatch 2 Callable Review Audit existing callable implementations for correctness and risks 3 Validation Scoring Evaluate against the 120 point rubric 4 Industries Fit Ensure compatibility with OmniStudio Industries extension points Ask for Entry point OmniScript Integration Procedure DataRaptor or other Industries hook Action names stri.
- name: building-omnistudio-callable-apex
- description: "Salesforce Industries Common Core (OmniStudio/Vlocity) Apex callable generation and review skill with 120-
- Specialist for Salesforce Industries Common Core callable Apex implementations. Produce secure,
- Follow building-omnistudio-callable-apex SKILL.md steps and documented constraints.
- Follow building-omnistudio-callable-apex SKILL.md steps and documented constraints.
Building Omnistudio Callable Apex by the numbers
- 1,928 all-time installs (skills.sh)
- +6 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #631 of 16,659 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
building-omnistudio-callable-apex capabilities & compatibility
- Capabilities
- name: building omnistudio callable apex · description: "salesforce industries common core · specialist for salesforce industries common core · follow building omnistudio callable apex skill.m
- Use cases
- orchestration
What building-omnistudio-callable-apex says it does
name: building-omnistudio-callable-apex
description: "Salesforce Industries Common Core (OmniStudio/Vlocity) Apex callable generation and review skill with 120-point scoring. Use when creating, reviewing, or migrating Industries callable Ap
Specialist for Salesforce Industries Common Core callable Apex implementations. Produce secure,
npx skills add https://github.com/forcedotcom/sf-skills --skill building-omnistudio-callable-apexAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.9k |
|---|---|
| repo stars | ★ 763 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 24, 2026 |
| Repository | forcedotcom/sf-skills ↗ |
When should an agent use building-omnistudio-callable-apex and what problem does it solve?
Salesforce Industries Common Core (OmniStudio/Vlocity) Apex callable generation and review skill with 120-point scoring. Use when creating, reviewing, or migrating Industries callable Apex implementat
Who is it for?
Developers invoking building-omnistudio-callable-apex as documented in the skill source.
Skip if: Skip when requirements fall outside building-omnistudio-callable-apex documented scope.
When should I use this skill?
Salesforce Industries Common Core (OmniStudio/Vlocity) Apex callable generation and review skill with 120-point scoring. Use when creating, reviewing, or migrating Industries callable Apex implementat
What you get
Outputs aligned with the building-omnistudio-callable-apex SKILL.md workflow and stated deliverables.
- Callable Apex class skeleton
Files
building-omnistudio-callable-apex: Callable Apex for Salesforce Industries Common Core
Specialist for Salesforce Industries Common Core callable Apex implementations. Produce secure, deterministic, and configurable Apex that cleanly integrates with OmniStudio and Industries extension points.
Scope
- In scope: Creating
System.Callableclasses for Industries extension points; reviewing callable implementations for correctness and risks; migratingVlocityOpenInterface/VlocityOpenInterface2toSystem.Callable; 120-point scoring and validation - Out of scope: Generic Apex classes without callable interface (use
generating-apex); building Integration Procedures (usebuilding-omnistudio-integration-procedure); authoring OmniScripts (usebuilding-omnistudio-omniscript); deploying Apex classes (usedeploying-metadata)
---
Core Responsibilities
1. Callable Generation: Build System.Callable classes with safe action dispatch 2. Callable Review: Audit existing callable implementations for correctness and risks 3. Validation & Scoring: Evaluate against the 120-point rubric 4. Industries Fit: Ensure compatibility with OmniStudio/Industries extension points
---
Workflow (4-Phase Pattern)
Phase 1: Requirements Gathering
Ask for:
- Entry point (OmniScript, Integration Procedure, DataRaptor, or other Industries hook)
- Action names (strings passed into
call) - Input/output contract (required keys, types, and response shape)
- Data access needs (objects/fields, CRUD/FLS (Create/Read/Update/Delete and Field-Level Security) rules)
- Side effects (DML, callouts, async requirements)
Then: 1. Scan for existing callable classes: Glob: **/*Callable*.cls 2. Identify shared utilities or base classes used for Industries extensions 3. Create a task list
---
Phase 2: Design & Contract Definition
Define the callable contract:
- Action list (explicit, versioned strings)
- Input schema (required keys + types)
- Output schema (consistent response envelope)
Recommended response envelope:
{
"success": true|false,
"data": {...},
"errors": [ { "code": "...", "message": "..." } ]
}Action dispatch rules:
- Use
switch on action - Default case throws a typed exception
- No dynamic method invocation or reflection
VlocityOpenInterface / VlocityOpenInterface2 contract mapping:
When designing for legacy Open Interface extensions (or dual Callable + Open Interface support), map the signature:
invokeMethod(String methodName, Map<String, Object> inputMap, Map<String, Object> outputMap, Map<String, Object> options)| Parameter | Role | Callable equivalent |
|---|---|---|
methodName | Action selector (same semantics as action) | action in call(action, args) |
inputMap | Primary input data (required keys, types) | args.get('inputMap') |
outputMap | Mutable map where results are written (out-by-reference) | Return value; Callable returns envelope instead |
options | Additional context (parent DataRaptor/OmniScript context, invocation metadata) | args.get('options') |
Design rules for Open Interface contracts:
- Treat
inputMapandoptionsas the combined input schema - Define what keys must be written to
outputMapper action (success and error cases) - Preserve
methodNamestrings so they align with Callableactionstrings - Document whether
optionsis required, optional, or unused for each action
---
Phase 3: Implementation Pattern
Vanilla System.Callable (flat args, no Open Interface coupling):
Read `assets/pattern_callable_vanilla.cls` before generating — use when callers pass flat args and no VlocityOpenInterface integration is required.
Callable skeleton (same inputs as VlocityOpenInterface):
Read `assets/pattern_callable_openinterface.cls` before generating — use inputMap and options keys in args when integrating with Open Interface or when callers pass that structure.
Input format: Callers pass args as { 'inputMap' => Map<String, Object>, 'options' => Map<String, Object> }. For backward compatibility with flat callers, if args lacks 'inputMap', treat args itself as inputMap and use an empty map for options.
Implementation rules: 1. Keep call() thin; delegate to private methods or service classes 2. Validate and coerce input types early (null-safe) 3. Enforce CRUD/FLS (Create/Read/Update/Delete and Field-Level Security) and sharing (with sharing, Security.stripInaccessible()) 4. Bulkify when args include record collections 5. Use WITH USER_MODE for SOQL when appropriate 6. Namespace handling: System.Callable is a standard interface (no namespace prefix required); omnistudio.VlocityOpenInterface2 uses the managed omnistudio package namespace — always qualify it. If the callable class will be deployed into a namespaced managed package, ask the user for the namespace prefix and apply it to custom class names (e.g., myns__Industries_XxxCallable)
VlocityOpenInterface / VlocityOpenInterface2 implementation:
When implementing omnistudio.VlocityOpenInterface or omnistudio.VlocityOpenInterface2, use the signature:
global Boolean invokeMethod(String methodName, Map<String, Object> inputMap,
Map<String, Object> outputMap, Map<String, Object> options)Read `assets/pattern_openinterface.cls` before generating — complete VlocityOpenInterface2 skeleton with switch on dispatch and outputMap contract.
Open Interface implementation rules:
- Write results into
outputMapviaputAll()or individualput()calls; do not return the envelope frominvokeMethod - Return
truefor success,falsefor unsupported or failed actions - Use the same internal private methods as the Callable (same
inputMapandoptionsparameters); only the entry point differs - Populate
outputMapwith the same envelope shape (success,data,errors) for consistency
Both Callable and Open Interface accept the same inputs (inputMap, options) and delegate to identical private method signatures for shared logic.
---
Phase 4: Testing & Validation
Minimum tests:
- Positive: Supported action executes successfully
- Negative: Unsupported action throws expected exception
- Contract: Missing/invalid inputs return error envelope
- Bulk: Handles list inputs without hitting limits
Read `assets/pattern_test_class.cls` — complete test class skeleton (positive, negative, contract, bulk, and null-args cases) before generating tests.
---
Migration: VlocityOpenInterface to System.Callable
When modernizing Industries extensions, move VlocityOpenInterface or VlocityOpenInterface2 implementations to System.Callable and keep the action contract stable.
Guidance:
- Preserve action names (
methodName) asactionstrings incall() - Pass
inputMapandoptionsas keys inargs:{ 'inputMap' => inputMap, 'options' => options } - Return a consistent response envelope instead of mutating
outMap - Keep
call()thin; delegate to the same internal methods with(inputMap, options)signature - Add tests for each action and unsupported action
Read `assets/pattern_migration.cls` — annotated before/after migration example (VlocityOpenInterface2 → System.Callable) before starting migration work.
---
Best Practices (120-Point Scoring)
| Category | Points | Key Rules |
|---|---|---|
| Contract & Dispatch | 20 | Explicit action list; switch on; versioned action strings |
| Input Validation | 20 | Required keys validated; types coerced safely; null guards |
| Security | 20 | with sharing; CRUD/FLS checks; Security.stripInaccessible() |
| Error Handling | 15 | Typed exceptions; consistent error envelope; no empty catch |
| Bulkification & Limits | 20 | No SOQL/DML in loops; supports list inputs |
| Testing | 15 | Positive/negative/contract/bulk tests |
| Documentation | 10 | ApexDoc (/** ... */ block comments — Salesforce Apex documentation standard) for class and action methods |
Thresholds: ✅ 90+ (Ready) | ⚠️ 70-89 (Review) | ❌ <70 (Block)
---
⛔ Guardrails (Mandatory)
Stop and ask the user if any of these would be introduced:
- Dynamic method execution based on user input (no reflection)
- SOQL/DML inside loops
without sharingon callable classes- Silent failures (empty catch, swallowed exceptions)
- Inconsistent response shapes across actions
---
Gotchas
| Issue | Resolution |
|---|---|
Caller passes flat args but code expects inputMap key | Guard defensively: if args lacks 'inputMap' key, treat args itself as the input map |
call() receives null for args | Always null-check args before accessing keys; initialize to empty map if null |
Test class uses (Map<String, Object>) svc.call(...) but call returns a wrong type | Ensure every action returns the same envelope type (Map<String, Object>) — mixed return types break callers |
VlocityOpenInterface2 migration breaks callers that read outputMap by reference | After migrating to Callable, callers must read the return value instead of reading outputMap — update all callers |
IndustriesCallableException class missing in project | This custom exception must be deployed alongside the callable class — include it in every deployment package |
| Org has both legacy Open Interface and new Callable wired to same action | Only one entry point should be active at a time; disable the old interface after confirming the callable works |
---
Common Anti-Patterns
call()contains business logic instead of delegating- Action names are unversioned or not documented
- Input maps assumed to have keys without checks
- Mixed response types (sometimes Map, sometimes String)
- No tests for unsupported actions
---
Cross-Skill Integration
| Skill | When to Use | Example |
|---|---|---|
| generating-apex | General Apex work beyond callable implementations | "Create trigger for Account" |
| generating-custom-object / generating-custom-field | Verify object/field availability before coding | "Describe Product2 fields" |
| deploying-metadata | Validate/deploy callable classes | "Deploy to sandbox" |
---
Reference Skill
Use the core Apex standards, testing patterns, and guardrails in:
- skills/generating-apex/SKILL.md
---
Bundled Examples
- examples/Test_QuoteByProductCallable/ — read-only query example with
WITH USER_MODE - examples/Test_VlocityOpenInterfaceConversion/ — migration from legacy
VlocityOpenInterface - examples/Test_VlocityOpenInterface2Conversion/ — migration from
VlocityOpenInterface2
Output Expectations
Deliverables produced by this skill:
<ClassName>.cls— Callable class implementingSystem.Callablewithswitch on actiondispatch<ClassName>Test.cls— Test class with positive, negative, contract, and bulk test methodsIndustriesCallableException.cls— Custom exception class (if not already present in the project)
---
Notes
- Prefer deterministic, side-effect-aware callable actions
- Keep action contracts stable; introduce new actions for breaking changes
- Avoid long-running work in synchronous callables; use async when needed
---
Reference File Index
| File | When to read |
|---|---|
assets/pattern_callable_vanilla.cls | Phase 3 — vanilla System.Callable skeleton (flat args, no Open Interface coupling) |
assets/pattern_callable_openinterface.cls | Phase 3 — System.Callable skeleton with inputMap/options args (Open Interface-compatible) |
assets/pattern_openinterface.cls | Phase 3 — VlocityOpenInterface2 skeleton with switch on dispatch and outputMap contract |
assets/pattern_test_class.cls | Phase 4 — test class skeleton (positive, negative, contract, bulk, and null-args cases) |
assets/pattern_migration.cls | Migration — annotated before/after migration pattern (VlocityOpenInterface2 → System.Callable) |
examples/Test_QuoteByProductCallable/Industries_QuoteByProductCallable.cls | Phase 3 — complete callable implementation with WITH USER_MODE SOQL and error envelope |
examples/Test_QuoteByProductCallable/Industries_QuoteByProductCallableTest.cls | Phase 4 — full test class covering positive, contract, and unsupported-action cases |
examples/Test_QuoteByProductCallable/IndustriesCallableException.cls | Phase 3 — custom exception pattern for unsupported actions |
examples/Test_QuoteByProductCallable/TRANSCRIPT.md | Reference — reasoning transcript for the Quote-by-Product callable example |
examples/Test_VlocityOpenInterfaceConversion/MyCustomCallable.cls | Phase 3 — migration pattern from legacy VlocityOpenInterface |
examples/Test_VlocityOpenInterfaceConversion/MyCustomCallableTest.cls | Phase 4 — test class for VlocityOpenInterface migration example |
examples/Test_VlocityOpenInterfaceConversion/IndustriesCallableException.cls | Phase 3 — custom exception class deployed alongside VlocityOpenInterface conversion |
examples/Test_VlocityOpenInterfaceConversion/MyCustomVlocityOpenInterface2.cls | Phase 3 — the original legacy VlocityOpenInterface2 class before migration |
examples/Test_VlocityOpenInterfaceConversion/TRANSCRIPT.md | Reference — reasoning transcript for VlocityOpenInterface conversion |
examples/Test_VlocityOpenInterface2Conversion/MyCustomCallable.cls | Phase 3 — migration pattern from VlocityOpenInterface2 |
examples/Test_VlocityOpenInterface2Conversion/MyCustomCallableTest.cls | Phase 4 — test class for VlocityOpenInterface2 migration example |
examples/Test_VlocityOpenInterface2Conversion/IndustriesCallableException.cls | Phase 3 — custom exception class deployed alongside VlocityOpenInterface2 conversion |
examples/Test_VlocityOpenInterface2Conversion/MyCustomRemoteClass.cls | Phase 3 — remote class used by the VlocityOpenInterface2 migration example |
examples/Test_VlocityOpenInterface2Conversion/TRANSCRIPT.md | Reference — reasoning transcript for VlocityOpenInterface2 conversion |
/**
* System.Callable skeleton — same inputMap/options structure as VlocityOpenInterface.
* Use when integrating with Open Interface callers or when callers pass { inputMap, options }.
* Backward-compatible: if args lacks 'inputMap', treats args itself as inputMap.
*
* Replace Industries_XxxCallable and actionOne with the actual class/action names.
*/
public with sharing class Industries_XxxCallable implements System.Callable {
public Object call(String action, Map<String, Object> args) {
Map<String, Object> inputMap = (args != null && args.containsKey('inputMap'))
? (Map<String, Object>) args.get('inputMap')
: (args != null ? args : new Map<String, Object>());
Map<String, Object> options = (args != null && args.containsKey('options'))
? (Map<String, Object>) args.get('options')
: new Map<String, Object>();
if (inputMap == null) { inputMap = new Map<String, Object>(); }
if (options == null) { options = new Map<String, Object>(); }
switch on action {
when 'actionOne' {
return actionOne(inputMap, options);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
private Map<String, Object> actionOne(Map<String, Object> inputMap, Map<String, Object> options) {
// 1. Validate required keys from inputMap
// 2. Coerce types safely
// 3. Run business logic (SOQL with WITH USER_MODE, DML with sharing)
// 4. Return response envelope
return new Map<String, Object>{
'success' => true,
'data' => new Map<String, Object>()
};
}
}
/**
* Vanilla System.Callable skeleton — flat args, no Open Interface coupling.
* Use when callers pass flat args and no VlocityOpenInterface integration is required.
*
* Replace Industries_XxxCallable and actionOne with the actual class/action names.
*/
public with sharing class Industries_XxxCallable implements System.Callable {
public Object call(String action, Map<String, Object> args) {
Map<String, Object> input = args != null ? args : new Map<String, Object>();
switch on action {
when 'actionOne' {
return actionOne(input);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
private Map<String, Object> actionOne(Map<String, Object> args) {
// 1. Validate required keys (e.g. args.get('requiredKey'))
// 2. Coerce types safely
// 3. Run business logic (SOQL with WITH USER_MODE, DML with sharing)
// 4. Return response envelope
return new Map<String, Object>{
'success' => true,
'data' => new Map<String, Object>()
};
}
}
// ─────────────────────────────────────────────────────────────────────────────
// BEFORE: VlocityOpenInterface2
// Results written into outputMap by reference; return value is Boolean success flag.
// ─────────────────────────────────────────────────────────────────────────────
// global class OrderOpenInterface implements omnistudio.VlocityOpenInterface2 {
// global Boolean invokeMethod(String methodName, Map<String, Object> input,
// Map<String, Object> output,
// Map<String, Object> options) {
// if (methodName == 'createOrder') {
// output.putAll(createOrder(input, options));
// return true;
// }
// return false;
// }
//
// private Map<String, Object> createOrder(Map<String, Object> inputMap,
// Map<String, Object> options) {
// return new Map<String, Object>{ 'success' => true };
// }
// }
// ─────────────────────────────────────────────────────────────────────────────
// AFTER: System.Callable
// Same inputs (inputMap, options); return value replaces outputMap mutation.
// Action strings mirror the original methodName values to keep callers stable.
// ─────────────────────────────────────────────────────────────────────────────
public with sharing class Industries_XxxCallable implements System.Callable {
public Object call(String action, Map<String, Object> args) {
Map<String, Object> inputMap = (args != null && args.containsKey('inputMap'))
? (Map<String, Object>) args.get('inputMap')
: (args != null ? args : new Map<String, Object>());
Map<String, Object> options = (args != null && args.containsKey('options'))
? (Map<String, Object>) args.get('options')
: new Map<String, Object>();
if (inputMap == null) { inputMap = new Map<String, Object>(); }
if (options == null) { options = new Map<String, Object>(); }
switch on action {
when 'createOrder' {
return createOrder(inputMap, options);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
private Map<String, Object> createOrder(Map<String, Object> inputMap,
Map<String, Object> options) {
// Validate input, run business logic, return response envelope
return new Map<String, Object>{ 'success' => true, 'data' => new Map<String, Object>() };
}
}
/**
* VlocityOpenInterface2 implementation skeleton.
* Use when the Industries extension point requires the legacy Open Interface contract.
*
* Key rules:
* - Write results into outputMap via putAll(); do NOT return the envelope from invokeMethod
* - Return true for success, false for unsupported/failed actions
* - Delegate to the same private methods as the Callable (same inputMap, options signature)
* - Populate outputMap with the same envelope shape (success, data, errors)
*
* Replace Industries_XxxOpenInterface and actionOne with the actual class/action names.
*/
global with sharing class Industries_XxxOpenInterface implements omnistudio.VlocityOpenInterface2 {
global Boolean invokeMethod(String methodName, Map<String, Object> inputMap,
Map<String, Object> outputMap, Map<String, Object> options) {
switch on methodName {
when 'actionOne' {
Map<String, Object> result = actionOne(inputMap, options);
outputMap.putAll(result);
return true;
}
when else {
outputMap.put('success', false);
outputMap.put('errors', new List<Map<String, Object>>{
new Map<String, Object>{
'code' => 'UNSUPPORTED_ACTION',
'message' => 'Unsupported action: ' + methodName
}
});
return false;
}
}
}
private Map<String, Object> actionOne(Map<String, Object> inputMap, Map<String, Object> options) {
// 1. Validate required keys from inputMap
// 2. Run business logic (SOQL with WITH USER_MODE, DML with sharing)
// 3. Return response envelope (written into outputMap by the caller above)
return new Map<String, Object>{
'success' => true,
'data' => new Map<String, Object>()
};
}
}
/**
* Test class skeleton for System.Callable implementations.
* Covers: positive, negative (missing input), unsupported action, and null-args cases.
*
* Replace Industries_XxxCallable, Industries_XxxCallableTest, and actionOne
* with the actual class/action names. Add @TestSetup data for your specific SObjects.
*/
@IsTest
private class Industries_XxxCallableTest {
@TestSetup
static void setup() {
// Insert test SObject records needed by the callable here.
// Example:
// Account acc = new Account(Name = 'Test Account');
// insert acc;
}
@IsTest
static void testActionOne_success() {
System.Callable svc = new Industries_XxxCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{ 'requiredKey' => 'requiredValue' },
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('actionOne', args);
Assert.isTrue((Boolean) result.get('success'), 'Expected success=true');
Assert.isNotNull(result.get('data'), 'Expected data to be present');
}
@IsTest
static void testActionOne_missingInput() {
System.Callable svc = new Industries_XxxCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>(),
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('actionOne', args);
Assert.isFalse((Boolean) result.get('success'), 'Expected success=false for missing input');
}
@IsTest
static void testUnsupportedAction() {
try {
System.Callable svc = new Industries_XxxCallable();
svc.call('unknownAction', new Map<String, Object>());
Assert.fail('Expected IndustriesCallableException');
} catch (IndustriesCallableException e) {
Assert.isTrue(e.getMessage().contains('Unsupported action'),
'Expected unsupported action message');
}
}
@IsTest
static void testNullArgs() {
System.Callable svc = new Industries_XxxCallable();
// Null args must be handled defensively; must not throw NullPointerException
Map<String, Object> result = (Map<String, Object>) svc.call('actionOne', null);
Assert.isNotNull(result, 'Result must not be null even with null args');
}
}
Credits
This skill draws on established Salesforce Industries patterns for callable Apex implementations, including the System.Callable interface, VlocityOpenInterface, and VlocityOpenInterface2 extension points used in OmniStudio and Industries integrations.
The guidance, migration patterns, and bundled examples reflect best practices for secure, deterministic callable implementations across Industries extension points such as OmniScripts, Integration Procedures, and DataRaptors.
/**
* @description Industries callable that finds Quotes on an Account that have at least one
* QuoteLineItem with a specified Product2.ProductCode.
* Integrates with OmniStudio/Industries extension points.
* @author building-omnistudio-callable-apex
*
* Actions:
* - findQuotesByProductCode: Returns Quotes for an Account with QuoteLineItems matching productCode
*
* Input (findQuotesByProductCode):
* - accountId (String, required): Id of the Account
* - productCode (String, required): Product2.ProductCode to match on QuoteLineItems
*
* Output envelope:
* - success (Boolean)
* - data (Map): quotes (List<Map> with Id, Name, QuoteNumber)
* - errors (List<Map>)
*/
public with sharing class Industries_QuoteByProductCallable implements System.Callable {
private static final String ACTION_FIND_QUOTES = 'findQuotesByProductCode';
/**
* @description Dispatches to the appropriate action handler.
* @param action Action name (e.g. 'findQuotesByProductCode')
* @param args Input map with accountId and productCode
* @return Response envelope: success, data, errors
*/
public Object call(String action, Map<String, Object> args) {
switch on action {
when ACTION_FIND_QUOTES {
return findQuotesByProductCode(args);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
/**
* @description Finds all Quotes on an Account that have at least one QuoteLineItem
* with the specified Product2.ProductCode.
* @param args Map with keys: accountId (String), productCode (String)
* @return Response envelope with quotes list or errors
*/
private Map<String, Object> findQuotesByProductCode(Map<String, Object> args) {
List<Map<String, Object>> errors = new List<Map<String, Object>>();
Object accountIdObj = args != null ? args.get('accountId') : null;
Object productCodeObj = args != null ? args.get('productCode') : null;
String accountId = accountIdObj != null ? String.valueOf(accountIdObj).trim() : null;
String productCode = productCodeObj != null ? String.valueOf(productCodeObj).trim() : null;
if (String.isBlank(accountId)) {
errors.add(new Map<String, Object>{
'code' => 'MISSING_ACCOUNT_ID',
'message' => 'accountId is required'
});
}
if (String.isBlank(productCode)) {
errors.add(new Map<String, Object>{
'code' => 'MISSING_PRODUCT_CODE',
'message' => 'productCode is required'
});
}
if (!errors.isEmpty()) {
return new Map<String, Object>{
'success' => false,
'data' => new Map<String, Object>{ 'quotes' => new List<Map<String, Object>>() },
'errors' => errors
};
}
try {
List<Quote> quotes = [
SELECT Id, Name, QuoteNumber, OpportunityId
FROM Quote
WITH USER_MODE
WHERE Opportunity.AccountId = :accountId
AND Id IN (
SELECT QuoteId
FROM QuoteLineItem
WHERE Product2.ProductCode = :productCode
)
];
List<Map<String, Object>> quoteMaps = new List<Map<String, Object>>();
for (Quote q : quotes) {
quoteMaps.add(new Map<String, Object>{
'Id' => q.Id,
'Name' => q.Name,
'QuoteNumber' => q.QuoteNumber
});
}
return new Map<String, Object>{
'success' => true,
'data' => new Map<String, Object>{ 'quotes' => quoteMaps },
'errors' => new List<Map<String, Object>>()
};
} catch (Exception e) {
return new Map<String, Object>{
'success' => false,
'data' => new Map<String, Object>{ 'quotes' => new List<Map<String, Object>>() },
'errors' => new List<Map<String, Object>>{
new Map<String, Object>{
'code' => 'QUERY_ERROR',
'message' => e.getMessage()
}
}
};
}
}
}
/**
* @description Test class for Industries_QuoteByProductCallable.
* Covers positive, negative, contract, and unsupported action scenarios.
* @author building-omnistudio-callable-apex
*/
@IsTest
private class Industries_QuoteByProductCallableTest {
// ═══════════════════════════════════════════════════════════════════════════
// TEST DATA SETUP
// ═══════════════════════════════════════════════════════════════════════════
@TestSetup
static void setupTestData() {
Account acc = new Account(Name = 'Test Account');
insert acc;
Pricebook2 pb = [SELECT Id FROM Pricebook2 WHERE IsStandard = true LIMIT 1];
Opportunity opp = new Opportunity(
Name = 'Test Opp',
AccountId = acc.Id,
Pricebook2Id = pb.Id,
StageName = 'Prospecting',
CloseDate = Date.today().addMonths(1)
);
insert opp;
Product2 prod = new Product2(
Name = 'Test Product',
ProductCode = 'PROD-001',
IsActive = true
);
insert prod;
PricebookEntry pbe = new PricebookEntry(
Pricebook2Id = pb.Id,
Product2Id = prod.Id,
UnitPrice = 100,
IsActive = true
);
insert pbe;
Quote q = new Quote(
Name = 'Test Quote',
OpportunityId = opp.Id,
Pricebook2Id = pb.Id
);
insert q;
QuoteLineItem qli = new QuoteLineItem(
QuoteId = q.Id,
PricebookEntryId = pbe.Id,
Quantity = 1,
UnitPrice = 100
);
insert qli;
}
// ═══════════════════════════════════════════════════════════════════════════
// POSITIVE TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests findQuotesByProductCode returns Quotes with matching productCode
*/
@IsTest
static void testFindQuotesByProductCode_Success() {
Account acc = [SELECT Id FROM Account LIMIT 1];
System.Callable svc = new Industries_QuoteByProductCallable();
Map<String, Object> args = new Map<String, Object>{
'accountId' => acc.Id,
'productCode' => 'PROD-001'
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('findQuotesByProductCode', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
List<Object> quotes = (List<Object>) data.get('quotes');
Assert.isNotNull(quotes, 'quotes list should not be null');
Assert.areEqual(1, quotes.size(), 'Should return 1 Quote');
}
/**
* @description Tests findQuotesByProductCode returns empty list when no match
*/
@IsTest
static void testFindQuotesByProductCode_NoMatch_ReturnsEmpty() {
Account acc = [SELECT Id FROM Account LIMIT 1];
System.Callable svc = new Industries_QuoteByProductCallable();
Map<String, Object> args = new Map<String, Object>{
'accountId' => acc.Id,
'productCode' => 'NONEXISTENT'
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('findQuotesByProductCode', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed with empty result');
Map<String, Object> data = (Map<String, Object>) result.get('data');
List<Object> quotes = (List<Object>) data.get('quotes');
Assert.areEqual(0, quotes.size(), 'Should return 0 Quotes');
}
// ═══════════════════════════════════════════════════════════════════════════
// CONTRACT / INPUT VALIDATION TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests missing accountId returns error envelope
*/
@IsTest
static void testFindQuotesByProductCode_MissingAccountId_ReturnsError() {
Map<String, Object> args = new Map<String, Object>{
'productCode' => 'PROD-001'
};
System.Callable svc = new Industries_QuoteByProductCallable();
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('findQuotesByProductCode', args);
Test.stopTest();
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
List<Object> errors = (List<Object>) result.get('errors');
Assert.isTrue(errors.size() >= 1, 'Should have at least one error');
}
/**
* @description Tests missing productCode returns error envelope
*/
@IsTest
static void testFindQuotesByProductCode_MissingProductCode_ReturnsError() {
Account acc = [SELECT Id FROM Account LIMIT 1];
Map<String, Object> args = new Map<String, Object>{
'accountId' => acc.Id
};
System.Callable svc = new Industries_QuoteByProductCallable();
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('findQuotesByProductCode', args);
Test.stopTest();
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
List<Object> errors = (List<Object>) result.get('errors');
Assert.isTrue(errors.size() >= 1, 'Should have at least one error');
}
/**
* @description Tests null args returns error envelope
*/
@IsTest
static void testFindQuotesByProductCode_NullArgs_ReturnsError() {
System.Callable svc = new Industries_QuoteByProductCallable();
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('findQuotesByProductCode', null);
Test.stopTest();
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
}
// ═══════════════════════════════════════════════════════════════════════════
// NEGATIVE TESTS (Unsupported Action)
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests unsupported action throws IndustriesCallableException
*/
@IsTest
static void testUnsupportedAction_ThrowsException() {
System.Callable svc = new Industries_QuoteByProductCallable();
Test.startTest();
try {
svc.call('unknownAction', new Map<String, Object>());
Assert.fail('Expected IndustriesCallableException');
} catch (IndustriesCallableException e) {
Assert.isTrue(
e.getMessage().contains('Unsupported action'),
'Error message should mention unsupported action: ' + e.getMessage()
);
}
Test.stopTest();
}
}
/**
* @description Custom exception for Industries callable implementations.
* Thrown when an unsupported or invalid action is requested.
* @author building-omnistudio-callable-apex
*/
public class IndustriesCallableException extends Exception {
}
Reasoning Transcript: Industries Quote-by-Product Callable
This document records the reasoning process and skills used to generate Industries_QuoteByProductCallable.cls and Industries_QuoteByProductCallableTest.cls.
---
Task Summary
Create a callable Apex class that finds all Quotes on an Account that have at least one QuoteLineItem with a specified Product2.ProductCode, plus a supporting test class.
---
Skills Applied
1. building-omnistudio-callable-apex (Primary)
Path: ~/.claude/skills/building-omnistudio-callable-apex/SKILL.md Trigger: Callable implementations, OmniStudio, Vlocity, Industries Apex extensions
Application:
- Phase 1 – Requirements Gathering:
Entry point: Integration Procedure / OmniScript. Action: findQuotesByProductCode. Inputs: accountId (String), productCode (String). Data: Quote, QuoteLineItem, Product2. No side effects (read-only).
- Phase 2 – Contract Definition:
Response envelope: { success, data: { quotes }, errors }. Action dispatch via switch on action with typed exception for unsupported actions.
- Phase 3 – Implementation Pattern:
call()kept thin; logic delegated tofindQuotesByProductCode(args)- Input validation for
accountIdandproductCode(blank checks) with sharingon the callable class- SOQL with
WITH USER_MODEfor CRUD/FLS - No SOQL/DML in loops
- Consistent envelope shape
- Phase 4 – Testing:
Positive, negative, contract, and unsupported-action tests per skill requirements.
- Exception Class:
IndustriesCallableException used per skill pattern for unsupported actions.
---
2. generating-apex (Reference)
Path: ~/.claude/skills/generating-apex/SKILL.md Trigger: Apex classes, code quality
Application:
- ApexDoc on the class and action method
- Naming:
Industries_QuoteByProductCallable(callable pattern) - Null-safe input handling
- Clear separation of concerns
---
3. running-apex-tests (Reference)
Path: ~/.claude/skills/running-apex-tests/SKILL.md Trigger: Apex tests
Application:
@TestSetupfor shared data (Account, Opportunity, Product2, PricebookEntry, Quote, QuoteLineItem)- Positive: success case and empty-result case
- Contract: missing
accountId, missingproductCode, null args - Negative: unsupported action throws
IndustriesCallableException - GIVEN/WHEN/THEN comments (adapted from basic-test template)
---
4. querying-soql / handling-sf-data (Reference)
Paths: skills/handling-sf-data/assets/soql/, skills/generating-mermaid-diagrams/assets/datamodel/ Trigger: SOQL and data model
Application:
- Quote–Opportunity–Account:
Quote.Opportunity.AccountId - QuoteLineItem–Product2:
QuoteLineItem.Product2.ProductCode - Subquery:
Id IN (SELECT QuoteId FROM QuoteLineItem WHERE Product2.ProductCode = :productCode) - Confirmation that Product2.ProductCode is queryable from QuoteLineItem
---
Design Decisions
| Decision | Rationale |
|---|---|
| Response envelope | Aligns with Industries pattern: success, data, errors for consistent handling by Integration Procedures |
| Error envelope for validation | Return structured errors instead of throwing; callers can handle without try/catch |
| Exception for unsupported action | Follows skill pattern; IndustriesCallableException makes misuse explicit |
| `IndustriesCallableException` in separate class | Reusable across callable classes, matches skill examples |
| `WITH USER_MODE` on SOQL | Enforces CRUD/FLS; requires API 59+ |
| Quote ↔ Account path | Standard model: Quote.Opportunity.AccountId (no direct Quote → Account) |
| Product code on Product2 | Standard Product2.ProductCode via QuoteLineItem.Product2 relationship |
---
Artifacts Produced
1. IndustriesCallableException.cls – Custom exception for unsupported actions 2. Industries_QuoteByProductCallable.cls – Callable implementation 3. Industries_QuoteByProductCallableTest.cls – Test class (6 methods) 4. TRANSCRIPT.md – This reasoning transcript
---
Deployment Notes
Copy the .cls files into your Salesforce project under force-app/main/default/classes/ and add the corresponding -meta.xml files with apiVersion 59.0 or higher (for WITH USER_MODE). If your org uses an older API, remove WITH USER_MODE from the SOQL query.
/**
* @description Custom exception for Industries callable implementations.
* Thrown when an unsupported or invalid action is requested.
* @author building-omnistudio-callable-apex
*/
public class IndustriesCallableException extends Exception {
}
/**
* @description Callable implementation for calculateTotal (converted from VlocityOpenInterface2).
* Accepts price and quantity from inputMap, returns total in response envelope.
* Original: MyCustomRemoteClass implements omnistudio.VlocityOpenInterface2.
* @author building-omnistudio-callable-apex
*/
public with sharing class MyCustomCallable implements System.Callable {
public Object call(String action, Map<String, Object> args) {
Map<String, Object> inputMap = (args != null && args.containsKey('inputMap'))
? (Map<String, Object>) args.get('inputMap') : (args != null ? args : new Map<String, Object>());
Map<String, Object> options = (args != null && args.containsKey('options'))
? (Map<String, Object>) args.get('options') : new Map<String, Object>();
if (inputMap == null) { inputMap = new Map<String, Object>(); }
if (options == null) { options = new Map<String, Object>(); }
switch on action {
when 'calculateTotal' {
return calculateTotal(inputMap, options);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
private Map<String, Object> calculateTotal(Map<String, Object> inputMap, Map<String, Object> options) {
Decimal price = toDecimal(inputMap.get('price'));
Integer qty = toInteger(inputMap.get('quantity'));
if (price == null || qty == null) {
return new Map<String, Object>{
'success' => false,
'data' => new Map<String, Object>(),
'errors' => new List<Map<String, Object>>{
new Map<String, Object>{
'code' => 'INVALID_INPUT',
'message' => 'price and quantity are required and must be numeric'
}
}
};
}
Decimal total = price * qty;
return new Map<String, Object>{
'success' => true,
'data' => new Map<String, Object>{ 'total' => total },
'errors' => new List<Map<String, Object>>()
};
}
private Decimal toDecimal(Object o) {
if (o == null) return null;
if (o instanceof Decimal) return (Decimal) o;
if (o instanceof Integer) return Decimal.valueOf((Integer) o);
if (o instanceof Double) return Decimal.valueOf((Double) o);
if (o instanceof String) {
try { return Decimal.valueOf((String) o); } catch (TypeException e) { return null; }
}
return null;
}
private Integer toInteger(Object o) {
if (o == null) return null;
if (o instanceof Integer) return (Integer) o;
if (o instanceof Long) return ((Long) o).intValue();
if (o instanceof Decimal) return ((Decimal) o).intValue();
if (o instanceof String) {
try { return Integer.valueOf((String) o); } catch (TypeException e) { return null; }
}
return null;
}
}
/**
* @description Test class for MyCustomCallable (converted from MyCustomRemoteClass VlocityOpenInterface2).
* Covers positive, negative, contract, and unsupported action scenarios.
* @author building-omnistudio-callable-apex
*/
@IsTest
private class MyCustomCallableTest {
// ═══════════════════════════════════════════════════════════════════════════
// POSITIVE TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests calculateTotal returns total with inputMap/options format
*/
@IsTest
static void testCalculateTotal_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{
'price' => 10.50,
'quantity' => 3
},
'options' => new Map<String, Object>()
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(31.50, (Decimal) data.get('total'), 'total should be 10.50 * 3');
}
/**
* @description Tests calculateTotal with flat args (backward compatibility)
*/
@IsTest
static void testCalculateTotal_FlatArgs_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'price' => 100,
'quantity' => 5
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(500, (Decimal) data.get('total'), 'total should be 100 * 5');
}
/**
* @description Tests calculateTotal with string inputs (type coercion from OmniScript)
*/
@IsTest
static void testCalculateTotal_StringInputs_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{
'price' => '25.00',
'quantity' => '4'
},
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(100, (Decimal) data.get('total'), 'total should be 25 * 4');
}
// ═══════════════════════════════════════════════════════════════════════════
// NEGATIVE / CONTRACT TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests unsupported action throws IndustriesCallableException
*/
@IsTest
static void testUnsupportedAction_Throws() {
try {
System.Callable svc = new MyCustomCallable();
svc.call('unknownAction', new Map<String, Object>{
'inputMap' => new Map<String, Object>(),
'options' => new Map<String, Object>()
});
Assert.fail('Expected IndustriesCallableException');
} catch (IndustriesCallableException e) {
Assert.isTrue(e.getMessage().contains('Unsupported action'), 'Message should mention unsupported action');
}
}
/**
* @description Tests missing price returns error envelope
*/
@IsTest
static void testCalculateTotal_MissingPrice_ReturnsError() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{ 'quantity' => 2 },
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
List<Object> errors = (List<Object>) result.get('errors');
Assert.isNotNull(errors, 'errors should not be null');
Assert.areEqual(1, errors.size(), 'Should have one error');
}
/**
* @description Tests missing quantity returns error envelope
*/
@IsTest
static void testCalculateTotal_MissingQuantity_ReturnsError() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{ 'price' => 50 },
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
List<Object> errors = (List<Object>) result.get('errors');
Assert.isNotNull(errors, 'errors should not be null');
}
/**
* @description Tests null args handled gracefully
*/
@IsTest
static void testCalculateTotal_NullArgs_ReturnsError() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', null);
Assert.isFalse((Boolean) result.get('success'), 'Should fail with null inputMap');
}
}
global with sharing class MyCustomRemoteClass implements omnistudio.VlocityOpenInterface2 {
global Boolean invokeMethod(String methodName, Map<String,Object> inputMap, Map<String,Object> outputMap, Map<String,Object> options) {
if (methodName.equals('calculateTotal')) {
calculateTotal(inputMap, outputMap);
return true;
}
return false;
}
private void calculateTotal(Map<String,Object> inputMap, Map<String,Object> outputMap) {
Decimal price = (Decimal)inputMap.get('price');
Decimal qty = (Decimal)inputMap.get('quantity');
outputMap.put('total', price * qty);
}
}Reasoning Transcript: VlocityOpenInterface2 → Callable Conversion (MyCustomRemoteClass)
This document records the reasoning process and skills used to convert MyCustomRemoteClass.cls (VlocityOpenInterface2) to MyCustomCallable.cls and create MyCustomCallableTest.cls.
---
Task Summary
Convert a VlocityOpenInterface2 implementation (MyCustomRemoteClass) to System.Callable, add a test class, and document reasoning and skills used.
---
Source Analysis
Original class: MyCustomRemoteClass.cls
- Implements:
omnistudio.VlocityOpenInterface2 - Action:
calculateTotal - Input contract:
inputMapwith keysprice(Decimal),quantity(Decimal) - Output contract:
outputMap.put('total', price * qty) - Behavior: Single action; returns
trueon success,falsefor unsupported methods
---
Skills Applied
1. building-omnistudio-callable-apex (Primary)
Path: skills/building-omnistudio-callable-apex/SKILL.md Trigger: Callable implementations, VlocityOpenInterface, migration to System.Callable
Application:
- Migration guidance (SKILL § Migration: VlocityOpenInterface to System.Callable):
- Preserved action name:
calculateTotal→actionincall() - Pass
inputMapandoptionsas keys inargs:{ 'inputMap' => inputMap, 'options' => options } - Return response envelope instead of mutating
outputMap - Keep
call()thin; delegate tocalculateTotal(inputMap, options) - Backward compatibility: if
argslacksinputMap, treatargsasinputMap
- Callable skeleton (SKILL § Phase 3 – Callable skeleton):
- Extracted
inputMapandoptionsfromargswith null guards switch on actionwithwhen elsethrowingIndustriesCallableException
- Response envelope:
{ success, data: { total }, errors }- Input validation: Added
toDecimal()andtoInteger()type coercion (OmniScript often passes strings); return error envelope whenpriceorquantityis null or invalid.
- Contract & Dispatch: Explicit action list; typed exception for unsupported actions.
---
2. generating-apex (Reference)
Path: ~/.claude/skills/generating-apex/SKILL.md Trigger: Apex classes, code quality
Application:
- ApexDoc on class and key methods
with sharingon the callable class- Null-safe input handling; no direct casts without validation
- Naming:
MyCustomCallable(callable pattern; preserves "MyCustom" from source)
---
3. running-apex-tests (Reference)
Path: ~/.claude/skills/running-apex-tests/SKILL.md Trigger: Apex tests
Application:
- Positive: Success with
inputMap/options, flat args, and string inputs (type coercion) - Contract: Missing
price, missingquantity, null args → error envelope - Negative: Unsupported action throws
IndustriesCallableException - GIVEN/WHEN/THEN implied via clear assertions and method names
---
Design Decisions
| Decision | Rationale |
|---|---|
Preserve lowercase keys (price, quantity, total) | Original MyCustomRemoteClass used lowercase; maintains contract for existing Integration Procedures / OmniScripts |
| Response envelope | Aligns with Industries pattern; callers get consistent success, data, errors shape |
| Error envelope for validation | Return structured errors instead of throwing; callers can handle without try/catch |
| Exception for unsupported action | Skill pattern; IndustriesCallableException makes misuse explicit |
| Type coercion helpers | OmniScript/Integration Procedures often pass strings; toDecimal/toInteger allow '25.00', '4' etc. |
| Flat args fallback | When args lacks inputMap, treat args as inputMap for backward compatibility |
---
Artifacts Produced
1. IndustriesCallableException.cls – Custom exception for unsupported actions 2. MyCustomCallable.cls – Callable implementation (converted from MyCustomRemoteClass) 3. MyCustomCallableTest.cls – Test class (7 methods) 4. TRANSCRIPT.md – This reasoning transcript
---
Contract Mapping (Before → After)
| VlocityOpenInterface2 | System.Callable |
|---|---|
methodName = 'calculateTotal' | action = 'calculateTotal' |
inputMap.get('price'), inputMap.get('quantity') | Same keys in args.inputMap or flat args |
outputMap.put('total', ...) | return { success => true, data => { total => ... } } |
return false for unsupported | throw IndustriesCallableException |
options (unused in original) | Passed through for future use |
---
Deployment Notes
Copy the .cls files into your Salesforce project under force-app/main/default/classes/ and add the corresponding -meta.xml files. No SOQL/DML; no special API version requirements.
/**
* @description Custom exception for Industries callable implementations.
* Thrown when an unsupported or invalid action is requested.
* @author building-omnistudio-callable-apex
*/
public class IndustriesCallableException extends Exception {
}
/**
* @description Callable implementation for calculateTotal (converted from VlocityOpenInterface).
* Accepts Price and Quantity, returns TotalAmount in response envelope.
* @author building-omnistudio-callable-apex
*/
public with sharing class MyCustomCallable implements System.Callable {
public Object call(String action, Map<String, Object> args) {
Map<String, Object> inputMap = (args != null && args.containsKey('inputMap'))
? (Map<String, Object>) args.get('inputMap') : (args != null ? args : new Map<String, Object>());
Map<String, Object> options = (args != null && args.containsKey('options'))
? (Map<String, Object>) args.get('options') : new Map<String, Object>();
if (inputMap == null) { inputMap = new Map<String, Object>(); }
if (options == null) { options = new Map<String, Object>(); }
switch on action {
when 'calculateTotal' {
return calculateTotal(inputMap, options);
}
when else {
throw new IndustriesCallableException('Unsupported action: ' + action);
}
}
}
private Map<String, Object> calculateTotal(Map<String, Object> inputMap, Map<String, Object> options) {
Decimal price = toDecimal(inputMap.get('Price'));
Integer qty = toInteger(inputMap.get('Quantity'));
if (price == null || qty == null) {
return new Map<String, Object>{
'success' => false,
'data' => new Map<String, Object>(),
'errors' => new List<Map<String, Object>>{
new Map<String, Object>{
'code' => 'INVALID_INPUT',
'message' => 'Price and Quantity are required and must be numeric'
}
}
};
}
Decimal total = price * qty;
return new Map<String, Object>{
'success' => true,
'data' => new Map<String, Object>{ 'TotalAmount' => total },
'errors' => new List<Map<String, Object>>()
};
}
private Decimal toDecimal(Object o) {
if (o == null) return null;
if (o instanceof Decimal) return (Decimal) o;
if (o instanceof Integer) return Decimal.valueOf((Integer) o);
if (o instanceof Double) return Decimal.valueOf((Double) o);
if (o instanceof String) {
try { return Decimal.valueOf((String) o); } catch (TypeException e) { return null; }
}
return null;
}
private Integer toInteger(Object o) {
if (o == null) return null;
if (o instanceof Integer) return (Integer) o;
if (o instanceof Long) return ((Long) o).intValue();
if (o instanceof Decimal) return ((Decimal) o).intValue();
if (o instanceof String) {
try { return Integer.valueOf((String) o); } catch (TypeException e) { return null; }
}
return null;
}
}
/**
* @description Test class for MyCustomCallable.
* Covers positive, negative, contract, and unsupported action scenarios.
* @author building-omnistudio-callable-apex
*/
@IsTest
private class MyCustomCallableTest {
// ═══════════════════════════════════════════════════════════════════════════
// POSITIVE TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests calculateTotal returns TotalAmount with inputMap/options format
*/
@IsTest
static void testCalculateTotal_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{
'Price' => 10.50,
'Quantity' => 3
},
'options' => new Map<String, Object>()
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(31.50, (Decimal) data.get('TotalAmount'), 'TotalAmount should be 10.50 * 3');
}
/**
* @description Tests calculateTotal with flat args (backward compatibility)
*/
@IsTest
static void testCalculateTotal_FlatArgs_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'Price' => 100,
'Quantity' => 5
};
Test.startTest();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Test.stopTest();
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(500, (Decimal) data.get('TotalAmount'), 'TotalAmount should be 100 * 5');
}
/**
* @description Tests calculateTotal with string inputs (type coercion from OmniScript)
*/
@IsTest
static void testCalculateTotal_StringInputs_Success() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{
'Price' => '25.00',
'Quantity' => '4'
},
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Assert.isTrue((Boolean) result.get('success'), 'Should succeed');
Map<String, Object> data = (Map<String, Object>) result.get('data');
Assert.areEqual(100, (Decimal) data.get('TotalAmount'), 'TotalAmount should be 25 * 4');
}
// ═══════════════════════════════════════════════════════════════════════════
// NEGATIVE / CONTRACT TESTS
// ═══════════════════════════════════════════════════════════════════════════
/**
* @description Tests unsupported action throws IndustriesCallableException
*/
@IsTest
static void testUnsupportedAction_Throws() {
try {
System.Callable svc = new MyCustomCallable();
svc.call('unknownAction', new Map<String, Object>{
'inputMap' => new Map<String, Object>(),
'options' => new Map<String, Object>()
});
Assert.fail('Expected IndustriesCallableException');
} catch (IndustriesCallableException e) {
Assert.isTrue(e.getMessage().contains('Unsupported action'), 'Message should mention unsupported action');
}
}
/**
* @description Tests missing Price returns error envelope
*/
@IsTest
static void testCalculateTotal_MissingPrice_ReturnsError() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> args = new Map<String, Object>{
'inputMap' => new Map<String, Object>{ 'Quantity' => 2 },
'options' => new Map<String, Object>()
};
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', args);
Assert.isFalse((Boolean) result.get('success'), 'Should fail');
List<Object> errors = (List<Object>) result.get('errors');
Assert.isNotNull(errors, 'errors should not be null');
Assert.areEqual(1, errors.size(), 'Should have one error');
}
/**
* @description Tests null args handled gracefully
*/
@IsTest
static void testCalculateTotal_NullArgs_ReturnsError() {
System.Callable svc = new MyCustomCallable();
Map<String, Object> result = (Map<String, Object>) svc.call('calculateTotal', null);
Assert.isFalse((Boolean) result.get('success'), 'Should fail with null inputMap');
}
}
global class MyCustomClass implements vlocity_cmt.VlocityOpenInterface {
global Boolean invokeMethod(String methodName, Map<String, Object> inputs,
Map<String, Object> output, Map<String, Object> options) {
if (methodName == 'calculateTotal') {
calculateTotal(inputs, output);
}
return true;
}
private void calculateTotal(Map<String, Object> inputs, Map<String, Object> output) {
// Retrieve inputs from OmniScript/IP
Decimal price = (Decimal)inputs.get('Price');
Integer qty = (Integer)inputs.get('Quantity');
// Perform calculation
Decimal total = price * qty;
// Return result to JSON
output.put('TotalAmount', total);
}
}TRANSCRIPT: VlocityOpenInterface → System.Callable Conversion
Task
Convert MyCustomVlocityOpenInterface2.cls (VlocityOpenInterface) to System.Callable and create a test class. Keep a transcript of reasoning and skills used.
---
Skills Used
| Skill | Purpose |
|---|---|
| building-omnistudio-callable-apex | Migration pattern, response envelope, action dispatch, inputMap/options contract, test patterns |
| generating-apex (implicit) | ApexDoc, type coercion, null safety, exception handling |
---
Reasoning
1. Source Analysis
Original class: MyCustomClass implements vlocity_cmt.VlocityOpenInterface
- Action:
calculateTotal - Inputs:
Price(Decimal),Quantity(Integer) frominputsmap - Output: Mutates
outputmap withTotalAmount - Observation: Original returns
truefor all method names (no unsupported-action handling)
2. Design Decisions
| Decision | Rationale |
|---|---|
| Open Interface–compatible args | Use inputMap and options keys per skill Phase 3 so OmniScript/IP callers can pass the same structure |
| Flat args fallback | Skill allows flat args when inputMap is absent; supports both calling styles |
| Response envelope | Return { success, data, errors } instead of mutating outputMap per migration guidance |
| Input validation | Skill requires null-safe type coercion; OmniScript can send numbers as String |
| Unsupported action | Skill: use switch on action and throw IndustriesCallableException |
| `with sharing` | Skill guardrail: enforce sharing on callable classes |
3. Implementation Pattern
- Entry point: Extract
inputMapandoptionsfromargs(or treat flatargsasinputMap) - Action dispatch:
switch on actionwithwhen 'calculateTotal'andwhen elsethrowing - Business logic:
calculateTotal(inputMap, options)returns envelope - Type coercion:
toDecimal()andtoInteger()handle Decimal, Integer, Long, String (OmniScript casts) - Error envelope: Missing/invalid inputs →
success: falsewithINVALID_INPUTerror code
4. Test Coverage (Phase 4)
| Test | Category | Purpose |
|---|---|---|
testCalculateTotal_Success | Positive | InputMap/options format, validates TotalAmount |
testCalculateTotal_FlatArgs_Success | Positive | Flat args backward compatibility |
testCalculateTotal_StringInputs_Success | Contract | String coercion from OmniScript |
testUnsupportedAction_Throws | Negative | Unsupported action throws IndustriesCallableException |
testCalculateTotal_MissingPrice_ReturnsError | Contract | Missing required key returns error envelope |
testCalculateTotal_NullArgs_ReturnsError | Contract | Null args handled without NPE |
5. Files Produced
| File | Role |
|---|---|
MyCustomCallable.cls | Converted System.Callable |
MyCustomCallableTest.cls | Test class |
IndustriesCallableException.cls | Custom exception (skill pattern) |
TRANSCRIPT.md | This transcript |
---
Skill References
- Phase 2 (Design): Contract mapping (
methodName→action,inputMap/options→args) - Phase 3 (Implementation): Callable skeleton (inputMap/options), implementation rules (validate early, null-safe)
- Phase 4 (Testing): Positive, negative, contract tests
- Migration: Preserve action names, return envelope instead of mutating output
building-omnistudio-callable-apex
Generates and reviews Salesforce Industries Common Core (OmniStudio/Vlocity) Apex callable implementations. Build secure, deterministic System.Callable classes with a 120-point scoring rubric and migration guidance from legacy VlocityOpenInterface implementations.
Features
- Callable Generation: Create
System.Callableclasses with safe action dispatch - Callable Review: Analyze existing callable implementations for risks and fixes
- 120-Point Scoring: Validation across 7 callable-specific categories
- VlocityOpenInterface / VlocityOpenInterface2 Support: Phase 2 contract mapping and Phase 3 implementation patterns for
invokeMethod(String methodName, Map<String, Object> inputMap, Map<String, Object> outputMap, Map<String, Object> options) - Migration Guidance: Patterns for moving from
VlocityOpenInterface/VlocityOpenInterface2toSystem.Callable - Testing Examples: Test class patterns for actions, errors, and bulk inputs
Quick Start
1. Invoke the skill
Skill: building-omnistudio-callable-apex
Request: "Create a callable implementation for Order actions with createOrder and cancelOrder"2. Answer requirements questions
The skill will ask about:
- Industries entry point (OmniScript, Integration Procedure, DataRaptor)
- Action names (strings passed into
call) - Input/output contract (required keys, response shape)
- Data access needs and security expectations
3. Review generated code
The skill generates:
- Callable class with explicit
switch on action - Consistent response envelope
- Test class examples for action coverage and error paths
Bundled Examples
- examples/Test_QuoteByProductCallable/ — read-only callable example with SOQL and test coverage
- examples/Test_VlocityOpenInterfaceConversion/ — migration pattern from legacy
VlocityOpenInterface - examples/Test_VlocityOpenInterface2Conversion/ — migration pattern from
VlocityOpenInterface2
Scoring System (120 Points)
| Category | Points | Focus |
|---|---|---|
| Contract & Dispatch | 20 | Explicit actions, switch on, versioned strings |
| Input Validation | 20 | Required keys, type coercion, null guards |
| Security | 20 | CRUD/FLS checks, with sharing, stripInaccessible |
| Error Handling | 15 | Typed exceptions, consistent errors |
| Bulkification & Limits | 20 | No SOQL/DML in loops, list inputs |
| Testing | 15 | Positive/negative/contract/bulk tests |
| Documentation | 10 | ApexDoc for class and action methods |
Thresholds: ✅ 90+ (Ready) | ⚠️ 70-89 (Review) | ❌ <70 (Block)
Cross-Skill Integration
| Related Skill | When to Use |
|---|---|
| generating-apex | General Apex work beyond callable implementations |
| generating-custom-object / generating-custom-field | Verify object/field availability before coding |
| running-apex-tests | Run tests and analyze coverage |
| deploying-metadata | Deploy callable classes to an org |
Documentation
- Skill Instructions
VlocityOpenInterface / VlocityOpenInterface2
The skill includes design (Phase 2) and implementation (Phase 3) guidance for the Open Interface signature invokeMethod(String methodName, Map<String, Object> inputMap, Map<String, Object> outputMap, Map<String, Object> options). Use this when extending legacy OmniStudio/Vlocity integration points or building dual Callable + Open Interface implementations.
Requirements
- sf CLI v2
- Target Salesforce org
Related skills
Forks & variants (1)
Building Omnistudio Callable Apex has 1 known copy in the catalog totaling 509 installs. They canonicalize to this original listing.
- forcedotcom - 509 installs
FAQ
What is building-omnistudio-callable-apex?
Salesforce Industries Common Core (OmniStudio/Vlocity) Apex callable generation and review skill with 120-point scoring. Use when creating, reviewing, or migrating Industries calla
When should I use building-omnistudio-callable-apex?
Salesforce Industries Common Core (OmniStudio/Vlocity) Apex callable generation and review skill with 120-point scoring. Use when creating, reviewing, or migrating Industries calla
Is building-omnistudio-callable-apex safe to install?
Review the Security Audits panel on this page before production use.