1. Overview
The QuickBooks Automation Service is the entry-point layer for all Salesforce Flow / Process Builder-triggered synchronisation with QuickBooks Online (QBO). It exposes a single Invocable Method (callQBService) that Salesforce Automations can call to push records — Customers, Products, or Invoices — into QuickBooks without requiring custom code from the admin configuring the flow.
The service intelligently routes work between:
-
Synchronous bulk processing (multiple records → staged in Salesforce with
Process_via_Batch__c = truefor batch pickup) -
Asynchronous real-time processing (single record → enqueued via
QueueableAutomateQBServicefor an immediate API callout to QBO)
2. Component Architecture
Salesforce Flow / Process Builder
│
▼
┌─────────────────────────────────┐
│ InvocableAutomateQBService │ ← Global Apex Class (Invocable)
│ │
│ callQBService() │ → Customer / Product
│ createQBInvoiceFromMapping() │ → Invoice (primary path)
│ QBInvoiceCallout() │ → Invoice (bulk path)
│ createJournalLedgerRecords() │ → Journal Entry
│ createErrorRecords() │ → Failure logging
└────────────┬────────────────────┘
│ single record
▼
┌─────────────────────────────────┐
│ QueueableAutomateQBService │ ← Queueable + AllowsCallouts
│ │
│ execute() │
│ createQBCustomer() │ → HTTP POST to QBO API
│ createQBInvoice() │ → HTTP POST to QBO API
└────────────┬────────────────────┘
│
▼
┌─────────────────────────────────┐
│ WS_Quickbooks │ ← OAuth + QBO REST API
└─────────────────────────────────┘
Supporting Classes:
CMQBConnectorUtility → FLS, request/record factory methods
QBApiDataWrapper → Request/Response wrapper models
Data → CRUD + FLS enforced DML
QuickbooksController → Customer/Product ID resolution
3. Class Reference
3.1 InvocableAutomateQBService
File: force-app/main/default/classes/InvocableAutomateQBService.cls Access Modifier: global with sharing
The primary service class. Contains the @InvocableMethod entry point and all orchestration logic.
|
Property |
Value |
|---|---|
|
Sharing Model |
|
|
Access |
|
|
FLS Enforcement |
Dynamic — reads from |
3.1.1 Method: callQBService
@InvocableMethod(label='Call QB Service' description='This function is responsible to call QB Web Service.')
global static void callQBService(List<QBWrapper> listRecords)
Purpose: Primary invocable entry point for Customer and Product synchronisation. Called directly from Salesforce Flows.
Routing Logic:
|
Condition |
Behaviour |
|---|---|
|
|
Enqueues |
|
|
Bulk path: builds staging records with |
|
|
Calls |
|
No custom field mappings found |
Calls |
Bulk Processing Flow (Customer):
-
Resolves the default or specified QuickBooks Company and Realm ID.
-
Queries
cm_finance__Custom_Field_Mapping__cfor the sObject's field-to-QB mapping. -
Dynamically queries the source sObject for all mapped fields.
-
Calls
QuickbooksController.getRelatedQuickbooksCustomers()to detect if a QB Customer record already exists (update vs. create decision). -
Builds
QBApiDataWrapper.QBRequestWrapperper record and callsWS_Quickbooks.createCustomerResponseWrapper(). -
Creates or upserts
cm_finance__Quickbooks_Customer__crecords usingComposite_Unique_Key__c(QB_Id::Customer::RealmId) as the upsert key. -
Sets
Process_via_Batch__c = trueandIgnore_Webhook__c = trueon all staged records.
Bulk Processing Flow (Product):
-
Same company/mapping resolution as Customer.
-
Resolves optional Account QB IDs for Income, Expense, and Asset account lookups.
-
Builds product response wrappers and maps SF account record IDs → QB account IDs.
-
Creates or upserts
cm_finance__Quickbooks_Product__crecords usingComposite_Unique_Key__c(QB_Id::Product::RealmId).
3.1.2 Method: createQBInvoiceFromMapping (Currently Active)
global static void createQBInvoiceFromMapping(List<QBWrapper> listRecords)
Purpose: Primary invoice creation path. Uses cm_finance__Custom_Field_Mapping__c configuration to dynamically build an invoice from any source sObject. This is the recommended and currently active method for invoice automation.
Prerequisites (must be configured in cm_finance__Custom_Field_Mapping__c):
|
Mapping Field |
Purpose |
|---|---|
|
|
JSON: QB invoice field → SF source field |
|
|
API name of the SF field holding the Customer record ID |
|
|
JSON configuration for child line-item records |
|
|
JSON: QB custom field Definition ID → SF field |
Processing Flow:
1. Resolve QB company from QBWrapper.oRequestWrapper.companyId
2. Query Custom_Field_Mapping__c for Mapping_Type__c = 'Invoice' and matching sObject
3. Extract all required SF field names from all mapping configurations
4. Query source sObject for all fields in one SOQL
5. For each record:
a. Build QBApiDataWrapper.Invoice via populateField() from cm_finance__Field_Mapping__c
b. Resolve QB Customer ID via cm_finance__Source_Invoice_Customer_Lookup_field__c
→ If no QB Customer found for 1 record: auto-trigger callQBService() to create it first
→ If no QB Customer found for >1 record: log Failed invoice record
c. Build Line Items from child sObjects via Invoice_Product_Pre_Population_Mapping__c
→ Resolves QB Product IDs from cm_finance__Quickbooks_Product__c
→ Resolves QB Class IDs from cm_finance__Quickbooks_Class__c
d. Populate QB Custom Fields from cm_finance__QB_Custom_Fields_Mapping__c
6. Call QBInvoiceCallout() with all valid requests
7. Any pre-validation failures → insert Failed cm_finance__Quickbooks_Invoice__c records immediately
Line Item Mapping Configuration (Invoice_Product_Pre_Population_Mapping__c JSON structure):
{
"query": "SELECT Id, {0}, ProductId__c, UnitPrice__c, Quantity__c, Description__c, QBClass__c FROM OpportunityLineItem WHERE {0} IN :parentRecordId",
"lookupFieldName": "OpportunityId",
"fieldMapping": {
"sfProductId": "ProductId__c",
"ProductQuantity": "Quantity__c",
"ProductUnitPrice": "UnitPrice__c",
"ProductDescription": "Description__c",
"QBClassLookup": "QBClass__c"
}
}
|
Key |
Description |
|---|---|
|
|
SOQL template for fetching child records. Use |
|
|
Child object field that holds the parent record ID |
|
|
Child field containing the Salesforce Product record ID |
|
|
Child field for line item quantity |
|
|
Child field for unit price |
|
|
Child field for line item description |
|
|
Child field holding QB Class lookup ID |
Failure Modes & Error Records Created:
|
Scenario |
Error Message on |
|---|---|
|
QB Company not provided |
|
|
No Custom Field Mapping found |
|
|
No invoice line items resolved |
|
|
Customer lookup field empty |
|
|
QB Customer not found (>1 record) |
|
3.1.3 Method: QBInvoiceCallout
global static void QBInvoiceCallout(List<QBWrapper> listRecords)
Purpose: Second-stage invoice processor. Accepts pre-built QBApiDataWrapper.Invoice objects inside QBWrapper.oRequestWrapper and stages them for QB sync.
Routing:
|
Condition |
Behaviour |
|---|---|
|
|
Sets |
|
|
Bulk path — builds and inserts staged |
Bulk Lookup Resolution:
The bulk path resolves all lookups in-memory before DML, performing 4 SOQL queries against:
-
cm_finance__Quickbooks_Customer__c→ mapsQB_Id::CompanyRecordId → SF Customer Record Id -
cm_finance__Quickbooks_Product__c→ mapsQB_Id::CompanyRecordId → SF Product Record Id -
cm_finance__Quickbooks_Class__c→ mapsQB_Id::CompanyRecordId → SF Class Record Id -
cm_finance__Quickbooks_Custom_Field__c→ mapsDefinitionId~CompanyRecordId → SF Custom Field Record Id
Custom Field Encoding: Custom field values are temporarily stored in cm_finance__Custom_Field_1__c / _2__c / _3__c using the format DefinitionId::FieldValue. The method splits this, resolves the SF Custom Field record ID, and writes the final value.
3.1.4 Method: createQBSalesReceiptFromMapping (Currently Active)
global static void createQBSalesReceiptFromMapping(List<QBWrapper> listRecords)
Purpose: Primary Sales Receipt creation path. Uses cm_finance__Custom_Field_Mapping__c configuration to dynamically build a Sales Receipt from any source sObject. This is the recommended and currently active method for Sales Receipt automation.
Prerequisites (must be configured in cm_finance__Custom_Field_Mapping__c):
|
Mapping Field |
Purpose |
|---|---|
|
|
JSON: QB Sales Receipt field → Salesforce source field |
|
|
API name of the Salesforce field holding the Customer record ID |
|
|
JSON configuration for child line-item records |
|
|
JSON: QB custom field Definition ID → Salesforce field |
Processing Flow:
1. Resolve QB company from QBWrapper.oRequestWrapper.companyId
2. Query Custom_Field_Mapping__c for Mapping_Type__c = 'SalesReceipt' and matching sObject
3. Extract all required Salesforce field names from the mapping configurations
4. Query the source sObject for all fields in one SOQL query
5. For each record:
a. Build QBApiDataWrapper.SalesReceipt via populateField() from cm_finance__Field_Mapping__c
b. Resolve the QB Customer ID via cm_finance__Source_Invoice_Customer_Lookup_field__c
→ If no QB Customer is found for 1 record: auto-trigger callQBService() to create it first
→ If no QB Customer is found for >1 record: log a Failed Sales Receipt record
c. Build line items from child sObjects via Invoice_Product_Pre_Population_Mapping__c
→ Resolve QB Product IDs from cm_finance__Quickbooks_Product__c
→ Resolve QB Class IDs from cm_finance__Quickbooks_Class__c
d. Populate QB custom fields from cm_finance__QB_Custom_Fields_Mapping__c
6. Call QBSalesReceiptCallout() with all valid requests
7. Insert Failed cm_finance__Quickbooks_Sales_Receipt__c records for pre-validation failures
Line Item Mapping: Uses the configured child-record query, parent lookup field, product field, quantity, unit price, description, and optional QB Class lookup to build Sales Receipt line items. Product and Class references are resolved from their corresponding QuickBooks mapping records before the request is sent.
Failure Modes & Error Records Created:
|
Scenario |
Error Message on |
|---|---|
|
QB Company not provided |
|
|
No Custom Field Mapping found |
|
|
No Sales Receipt line items resolved |
|
|
Customer lookup field empty |
|
|
QB Customer not found (>1 record) |
|
3.1.5 Method: QBSalesReceiptCallout
global static void QBSalesReceiptCallout(List<QBWrapper> listRecords)
Purpose: Second-stage Sales Receipt processor. Accepts pre-built QBApiDataWrapper.SalesReceipt objects inside QBWrapper.oRequestWrapper and either sends them through the real-time queueable path or stages them for bulk QB synchronisation.
Routing:
|
Condition |
Behaviour |
|---|---|
|
|
Sets |
|
|
Bulk path — builds and inserts staged |
Bulk Lookup Resolution:
The bulk path resolves all references in memory before DML, using Salesforce mapping records for:
-
cm_finance__Quickbooks_Customer__c→ QB Customer ID to Salesforce Customer record -
cm_finance__Quickbooks_Product__c→ QB Product ID to Salesforce Product record -
cm_finance__Quickbooks_Class__c→ QB Class ID to Salesforce Class record -
cm_finance__Quickbooks_Custom_Field__c→ Definition ID and company to Salesforce Custom Field record -
cm_finance__Payment_Method__c→ QB Payment Method ID to Salesforce Payment Method record -
-
cm_finance__Quickbooks_Account__c→ QB Account ID to Salesforce QB Account record
-
Custom Field Encoding: Custom field values are temporarily stored in cm_finance__Custom_Field_1__c, _2__c, and _3__c using the format DefinitionId::FieldValue. The method splits each value, resolves the Salesforce Custom Field record ID, and writes the final value to the staged Sales Receipt record.
3.1.6 Method: createErrorRecords
public static void createErrorRecords(List<QBWrapper> listRecords)
Purpose: Fallback error handler. Creates Failed status records in the corresponding QB object for each item in the list, using upsert on cm_finance__Parent_Record_Id__c.
|
|
Record Created |
Upsert Key |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3.1.7 Method: createJournalLedgerRecords
global static Map<String, Object> createJournalLedgerRecords(List<QBWrapper> lstRecords)
Purpose: Delegates journal ledger creation to QuickbooksController.createJournalLedgerInQuickbooks().
Required QBWrapper Fields:
|
Field |
Description |
|---|---|
|
|
Salesforce ID of the QB Company record |
|
|
QuickBooks Realm ID |
|
|
Pre-built JSON request body for the journal entry API |
Return Value: Map<String, Object> with keys:
-
isSuccess(Boolean) -
message(String) — error detail ifisSuccess = false
3.2 Inner Class: QBWrapper
global class QBWrapper { ... }
The data transfer object passed to all invocable and static methods in this service. Fields marked with @InvocableVariable are directly assignable from Salesforce Flows.
|
Field |
Type |
Invocable |
Required |
Description |
|---|---|---|---|---|
|
|
|
✅ |
✅ |
Salesforce record ID of the source sObject being synced |
|
|
|
✅ |
✅ |
Type of entity: |
|
|
|
✅ |
❌ |
Salesforce record ID of the QB Company. Falls back to default company if blank. |
|
|
|
✅ |
❌ |
(Customer) SF record ID of the parent QB Customer (for sub-customer creation) |
|
|
|
✅ |
❌ |
(Product) SF record ID of the QB Account to use as Asset Account |
|
|
|
✅ |
❌ |
(Product) SF record ID of the QB Account to use as Income Account |
|
|
|
✅ |
❌ |
(Product) SF record ID of the QB Account to use as Expense Account |
|
|
|
❌ |
❌ |
QuickBooks Realm/Company ID (internal use) |
|
|
|
❌ |
❌ |
Pre-built JSON string (used for journal ledger) |
|
|
|
❌ |
❌ |
Pre-built API request object (populated programmatically for invoice flow) |
3.3 QueueableAutomateQBService
File: force-app/main/default/classes/QueueableAutomateQBService.cls Access Modifier: public with sharing Implements: Queueable, Database.AllowsCallouts
Handles the real-time single-record path. Enqueued by InvocableAutomateQBService when listRecords.size() == 1.
|
Property |
Value |
|---|---|
|
PDF Generation |
Controlled by |
|
FLS Enforcement |
Inherited from |
Method: execute
Routes to createQBCustomer(), createQBSalesReceipt() or createQBInvoice() based on requestType.
Customer → Invoice Chaining: After a Customer is successfully created in QBO:
-
If the wrapper also contains a pre-built
oRequestWrapper.oInvoice, the method automatically re-enqueues itself withrequestType = 'Invoice'. -
This enables a customer-first, invoice-second sequential flow in a single automation trigger.
Flow execution:
Queueable[1]: requestType='Customer'
→ Creates customer in QBO
→ If invoice is pending in wrapper:
enqueue Queueable[2]: requestType='Invoice'
Queueable[2]: requestType='Invoice'
→ Creates invoice in QBO, sets customerRef from Queueable[1] result
Customer → Sales Receipt Chaining: After a Customer is successfully created in QBO:
-
If the wrapper also contains a pre-built
oRequestWrapper.oSalesReceipt, the method automatically re-enqueues itself withrequestType = 'SalesReceipt'. -
This enables a customer-first, sales receipt-second sequential flow in a single automation trigger.
Flow execution:
Queueable[1]: requestType='Customer'
→ Creates customer in QBO
→ If sales receipt is pending in wrapper:
enqueue Queueable[2]: requestType='SalesReceipt'
Queueable[2]: requestType='SalesReceipt'
→ Creates sales receipt in QBO, sets customerRef from Queueable[1] result
Method: createQBCustomer
public static List<cm_finance__Quickbooks_Customer__c> createQBCustomer(InvocableAutomateQBService.QBWrapper oReqWrapper)
Steps:
-
Resolves QB Company and Realm ID.
-
Calls
CMQBConnectorUtility.getQBObjectMapping()to getCustomerfield mapping for the source sObject. -
Queries source sObject for all mapped fields.
-
Checks for existing QB Customer (update scenario) via
QuickbooksController.getRelatedQuickbooksCustomers(). -
Calls
WS_Quickbooks.createCustomer()— live HTTP callout to QBO. -
On success: enriches and upserts
cm_finance__Quickbooks_Customer__cusingComposite_Unique_Key__c. -
On failure: upserts a
Failedstatus Customer record usingParent_Record_Id__c.
Composite Unique Key format: {QB_Id}::Customer::{RealmId}
Method: createQBInvoice
public static void createQBInvoice(InvocableAutomateQBService.QBWrapper oWrapper)
Steps:
-
Validates QB Company.
-
Calls
WS_Quickbooks.createInvoice()— live HTTP callout to QBO. -
Resolves all reference lookups (Customer, Product, Class, Custom Fields) from Salesforce.
-
Sets invoice status to
Invoice PDF PendingifGenerateInvoicePDFsetting istrue, elseAwaiting Salesforce Sync. -
Inserts
cm_finance__Quickbooks_Invoice__cand childcm_finance__Quickbooks_Invoice_Item__crecords. -
Sets
Composite_Unique_Key__c({QB_Id}::{RealmId}) andIgnore_Webhook__c = true. -
On failure: upserts a
Failedstatus invoice record.
Method: createQBSalesReceipt
public static void createQBSalesReceipt(InvocableAutomateQBService.QBWrapper oWrapper)
Steps:
-
Validates QB Company.
-
Calls
WS_Quickbooks.createQBSalesReceipt()— live HTTP callout to QBO. -
Resolves all reference lookups (Customer, Product, Class, Custom Fields, Payment Method, QB Account) from Salesforce.
-
Sets sales receipt status to
Sales Receipt PDF PendingifGenerateSalesReceiptPDFsetting istrue, elseAwaiting Salesforce Sync. -
Inserts
cm_finance__Quickbooks_Sales_Receipt__cand childcm_finance__Quickbooks_Sales_Receipt_Item__crecords. -
Sets
Composite_Unique_Key__c({QB_Id}::{RealmId}) andIgnore_Webhook__c = true. -
On failure: upserts a
Failedstatus invoice record.
3.4 Supporting Class: CMQBConnectorUtility
File: force-app/main/default/classes/CMQBConnectorUtility.cls Access Modifier: public with sharing
Central utility class used by all automation service classes.
|
Method |
Purpose |
|---|---|
|
|
Returns |
|
|
Returns the default QB Company Salesforce record ID |
|
|
Returns the default QB Realm/Company ID |
|
|
Queries |
|
|
Populates a |
|
|
Populates a |
|
|
Populates a |
|
|
Factory: builds/enriches a |
|
|
Factory: builds/enriches a |
|
|
Factory: builds/enriches a |
|
|
Factory: builds/enriches a |
|
|
Factory: builds/enriches a |
|
|
Validates email format via regex; returns |
|
|
Parses |
|
|
Dynamically resolves the correct SF lookup field from |
3.5 Supporting Class: QBApiDataWrapper
File: force-app/main/default/classes/QBApiDataWrapper.cls Access Modifier: global with sharing
All data models for the QB API integration layer.
Key Wrapper Classes
|
Class |
Purpose |
|---|---|
|
|
Container passed to |
|
|
Container returned from |
|
|
Encapsulates |
Entity Classes
|
Class |
Key Fields |
Notable |
|---|---|---|
|
|
|
|
|
|
|
— |
|
|
|
— |
|
|
|
|
|
|
|
|
|
|
|
— |
|
|
|
— |
|
|
Journal entry data structure |
Used for |
|
|
|
— |
|
|
|
— |
|
|
|
Maps to QB's custom fields on Invoice |
|
|
|
— |
Supporting Classes
|
Class |
Purpose |
|---|---|
|
|
|
|
|
|
|
|
Invoice/Journal line item: |
|
|
|
|
|
Account ref and posting type for journal lines |
4. End-to-End Flow Diagrams
4.1 Customer Automation (Single Record)
Flow triggers with 1 Customer record
│
▼
callQBService()
│
├── size == 1 AND type != 'Product'
│ │
│ ▼
│ System.enqueueJob(QueueableAutomateQBService)
│ │
│ ▼
│ execute() → createQBCustomer()
│ │
│ ├── [Success] → Upsert cm_finance__Quickbooks_Customer__c
│ │ Status: 'Awaiting Salesforce Sync'
│ │ Composite_Unique_Key__c set
│ │
│ └── [Failure] → Upsert cm_finance__Quickbooks_Customer__c
│ Status: 'Failed', error in Status_Description__c
│
└── (end)
4.2 Invoice Automation (Single Record) — Primary Path
Flow triggers with 1 Invoice record
│
▼
createQBInvoiceFromMapping()
│
├── Resolve Custom Field Mapping for source sObject + company
├── Query source sObject fields
├── Resolve QB Customer ID from lookup field
│ │
│ ├── [QB Customer Not Found, 1 record]
│ │ │
│ │ ▼
│ │ callQBService() → creates Customer first
│ │ (Invoice creation deferred to Queueable chain)
│ │
│ └── [QB Customer Found]
│ │
│ ▼
│ Build Invoice + Line Items + Custom Fields
│ │
│ ▼
│ QBInvoiceCallout()
│ │
│ ├── size == 1
│ │ │
│ │ ▼
│ │ System.enqueueJob(QueueableAutomateQBService)
│ │ │
│ │ ▼
│ │ createQBInvoice() → Live API callout
│ │ │
│ │ ├── [Success] → Insert Invoice + Invoice Items
│ │ │ Status: 'Invoice PDF Pending' or 'Awaiting Salesforce Sync'
│ │ │
│ │ └── [Failure] → Upsert Invoice, Status: 'Failed'
│ │
│ └── (end)
│
└── (end)
4.3 Bulk Customer/Product Automation
Flow triggers with N records (N > 1 or type = 'Product')
│
▼
callQBService()
│
├── Resolve QB Company + Realm ID
├── Query Custom_Field_Mapping__c
├── Dynamically query source sObjects
├── For each record: build QBApiDataWrapper request wrapper
│ (no live API callout here)
├── Create/Upsert cm_finance__Quickbooks_Customer__c or Product records
│ Status: 'Awaiting QB Sync'
│ Process_via_Batch__c = true
│ Ignore_Webhook__c = true
│
└── Batch job picks up records and performs live QB API callout
4.4 Sales Receipt Automation (Single Record) — Primary Path
Flow triggers with 1 Sales Receipt record
│
▼
createQBSalesReceiptFromMapping()
│
├── Resolve Custom Field Mapping for source sObject + company
├── Query source sObject fields
├── Resolve QB Customer ID from lookup field
│ │
│ ├── [QB Customer Not Found, 1 record]
│ │ │
│ │ ▼
│ │ callQBService() → creates Customer first
│ │ (Sales Receipt creation deferred to Queueable chain)
│ │
│ └── [QB Customer Found]
│ │
│ ▼
│ Build Sales Receipt + Line Items + Custom Fields
│ │
│ ▼
│ QBSalesReceiptCallout()
│ │
│ ├── size == 1
│ │ │
│ │ ▼
│ │ System.enqueueJob(QueueableAutomateQBService)
│ │ │
│ │ ▼
│ │ createQBSalesReceipt() → Live API callout
│ │ │
│ │ ├── [Success] → Insert Sales Receipt + Sales Receipt Items
│ │ │ Status: 'Sales Receipt PDF Pending' or
│ │ │ 'Awaiting Salesforce Sync'
│ │ │
│ │ └── [Failure] → Upsert Sales Receipt, Status: 'Failed'
│ │
│ └── (end)
│
└── (end)
4.4 Sales Receipt Automation (Single Record) — Primary Path
Flow triggers with 1 Sales Receipt record
│
▼
createQBSalesReceiptFromMapping()
│
├── Resolve Custom Field Mapping for source sObject + company
├── Query source sObject fields
├── Resolve QB Customer ID from lookup field
│ │
│ ├── [QB Customer Not Found, 1 record]
│ │ │
│ │ ▼
│ │ callQBService() → creates Customer first
│ │ (Sales Receipt creation deferred to Queueable chain)
│ │
│ └── [QB Customer Found]
│ │
│ ▼
│ Build Sales Receipt + Line Items + Custom Fields
│ │
│ ▼
│ QBSalesReceiptCallout()
│ │
│ ├── size == 1
│ │ │
│ │ ▼
│ │ System.enqueueJob(QueueableAutomateQBService)
│ │ │
│ │ ▼
│ │ createQBSalesReceipt() → Live API callout
│ │ │
│ │ ├── [Success] → Insert Sales Receipt + Sales Receipt Items
│ │ │ Status: 'Sales Receipt PDF Pending' or
│ │ │ 'Awaiting Salesforce Sync'
│ │ │
│ │ └── [Failure] → Upsert Sales Receipt, Status: 'Failed'
│ │
│ └── (end)
│
└── (end)
4.4 Sales Receipt Automation (Single Record) — Primary Path
https://cloudmaven.atlassian.net/avpviz/c/a8fd9983-e91d-4da7-b3cc-922119981cc4/w/80bbde58-b86c-4fdc-8111-7f976d003d57/d/bfa8eb0d-2460-4679-b1c5-3b431ad5958a/chart/92733349-7539-4407-8181-c66ed92b6882
5. Custom Object Record Lifecycle
|
Stage |
Status Value |
Trigger |
|---|---|---|
|
Automation created (bulk) |
|
|
|
Automation created (real-time, success) |
|
|
|
Automation created (real-time, invoice or sales receipt) |
|
|
|
Automation pre-validation failure |
|
Pre-callout validation in |
|
Live API callout failure |
|
|
6. Configuration Reference
6.1 cm_finance__Custom_Field_Mapping__c
This object drives all dynamic field mapping. One record per sObject per company per mapping type.
|
Field |
API Name |
Purpose |
|---|---|---|
|
Source |
|
Must be |
|
sObject API Name |
|
API name of the source SF object |
|
Mapping Type |
|
|
|
QB Company |
|
Lookup to the QB Company record |
|
Field Mapping |
|
JSON: |
|
Customer Lookup Field |
|
(Invoice only) SF field holding the parent Customer record ID |
|
Product Pre-Population |
|
(Invoice only) JSON config for child line-item records |
|
QB Custom Fields |
|
JSON: |
6.2 cm_finance__Quickbooks_Common_Settings__c (Custom Setting)
|
Instance Name |
Value |
Effect |
|---|---|---|
|
|
Semicolon-separated user type names |
User types that bypass FLS enforcement. Default: |
|
|
|
If |
7. Configuring a Flow Automation
Step 1 — Set Up Field Mapping
Create a cm_finance__Custom_Field_Mapping__c record:
Source: Quickbooks
sObject API Name: Opportunity (or any source object)
Mapping Type: Invoice
QB Company: [Your QB Company record]
Field Mapping: {"txnDate": "CloseDate", "dueDate": "DueDate__c"}
Customer Lookup Field: AccountId
Product Pre-Population: { "query": "SELECT Id, {0}, Product2Id, UnitPrice, Quantity FROM OpportunityLineItem WHERE {0} IN :parentRecordId", "lookupFieldName": "OpportunityId", "fieldMapping": { "sfProductId": "Product2Id", "ProductQuantity": "Quantity", "ProductUnitPrice": "UnitPrice" } }
Step 2 — Configure the Flow
-
Add an Action element to your Flow.
-
Search for "Call QB Service" (the
@InvocableMethodlabel). -
Map the following input variables:
|
Flow Variable |
QBWrapper Field |
Notes |
|---|---|---|
|
Record ID |
|
The triggering record's ID |
|
Request Type |
|
Hardcode to |
|
Company Record ID |
|
SF ID of the QB Company record. Optional if default is set. |
-
For Invoice flows, the
oRequestWrapperis built internally bycreateQBInvoiceFromMapping()— no additional mapping needed in the Flow.
Step 3 — Invoke createQBInvoiceFromMapping for Invoice Flows
For invoice automations, do not call callQBService directly from the Flow. Instead, call createQBInvoiceFromMapping from Apex (e.g., a trigger or invocable wrapper) that passes the QBWrapper list with oRequestWrapper.companyId pre-populated.
8. Error Handling Summary
|
Layer |
Mechanism |
Output |
|---|---|---|
|
Pre-validation |
|
|
|
API callout failure (real-time) |
|
|
|
Unexpected exception |
|
No record created; debug logs only |
|
Missing company |
|
|
|
Missing field mapping |
|
|
Note: Exceptions in the main methods are caught silently (debug only). Monitor debug logs or the Failed status records in QB custom objects to diagnose automation issues.
9. Key Design Decisions
|
Decision |
Rationale |
|---|---|
|
Single record → Queueable |
Salesforce Flows cannot perform callouts in the same transaction. Queueable with |
|
Multiple records → Batch staging |
Avoids hitting Queueable chain limits (max 50 chained jobs). Bulk records are staged and processed by a scheduled batch. |
|
|
Prevents the QB webhook listener from processing records already created by this automation, avoiding duplicate sync loops. |
|
|
Flags records for batch job pickup, enabling the actual QBO API callout outside the automation transaction. |
|
Upsert on |
Handles both create and update scenarios in a single DML operation. Format: |
|
|
Format: |
|
Auto customer creation for invoices |
If |