Technical Documentation - Manual Record Creation


1. Overview

The Manual Record Creation system is the interactive, UI-driven layer of the CloudMaven QB Connector. It exposes a Lightning Web Component (cmQBConnectorMaster) embedded on any Salesforce record page that allows users to:

  • Push Salesforce records (Customers, Vendors, Products, Invoices, Sales Receipt) to QuickBooks Online

  • Pull QuickBooks records back into Salesforce

  • Create net-new QB entities (Customers, Vendors, Products, Invoices, Sales Receipt) directly from the Salesforce UI with live field pre-fill from the source record

The system is backed by three Apex controller classes — QuickbooksController (Customer, Vendor, Product, Account sync), QBSalesReceiptController (Sales Receipt-specific operations) and QBInvoiceController (Invoice-specific operations) — and relies on the shared service layer (WS_Quickbooks, QBApiDataWrapper, CMQBConnectorUtility, Data) used across all integration pathways.


2. Component Architecture

┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                      Salesforce Record Page                                           │
│                                                                                                      │
│  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐  │
│  │                                  cmQBConnectorMaster (LWC)                                     │  │
│  │                                                                                                │  │
│  │                                  Screen State Machine                                          │  │
│  │                                    (Boolean Flags)                                             │  │
│  │                                                                                                │  │
│  │  ┌──────────────┐    ┌───────────────┐    ┌─────────────────────────┐                         │  │
│  │  │ Company      │    │ Home Screen   │    │ Feature Sections        │                         │  │
│  │  │ Select       │    │               │    │                         │                         │  │
│  │  │ Screen       │    │ Push / Pull   │    │ • Invoice Section       │                         │  │
│  │  │              │    │               │    │ • Product Section       │                         │  │
│  │  │              │    │               │    │ • Sales Receipt Section │                         │  │
│  │  └──────┬───────┘    └──────┬────────┘    └───────────┬─────────────┘                         │  │
│  │         │                    │                         │                                       │  │
│  │         └────────────────────┴─────────────────────────┘                                       │  │
│  │                              │                                                                 │  │
│  │                    @AuraEnabled Apex Calls                                                    │  │
│  └──────────────────────────────┼─────────────────────────────────────────────────────────────────┘  │
│                                 │                                                                    │
│       ┌─────────────────────────┼──────────────────────────┬──────────────────────────┐              │
│       │                         │                          │                          │              │
│  ┌────▼────────────────┐  ┌────▼─────────────────┐  ┌────▼─────────────────┐  ┌────▼───────────────┐│
│  │ QuickbooksController│  │ QBInvoiceController  │  │ QBSalesReceipt       │  │ QBBillController   ││
│  │                     │  │                      │  │ Controller           │  │                    ││
│  │ • Companies         │  │ • checkExistingInvoice│ │ • checkExisting      │  │ • checkExistingBill││
│  │ • Customers         │  │ • fetchRelatedCustomer││   SalesReceipt        │  │ • fetchRelatedVendor││
│  │ • Vendors           │  │ • sendPrefilledInvoice││ • fetchRelatedCustomer│  │   FromParent        ││
│  │ • Products          │  │ • createInvoice...   │  │ • sendPrefilled      │  │ • sendPrefilledBill││
│  │ • Accounts          │  │ • syncInvoice...     │  │   SalesReceipt       │  │ • createBillIn     ││
│  │                     │  │                      │  │ • createSalesReceipt │  │   Quickbooks       ││
│  │                     │  │                      │  │ • syncSalesReceipt...│  │                    ││
│  └──────────┬──────────┘  └──────────┬───────────┘  └──────────┬───────────┘  └────────┬───────────┘│
│             │                        │                         │                       │            │
│             └────────────────────────┼─────────────────────────┴───────────────────────┘            │
│                                      │                                                              │
│  ┌───────────────────────────────────▼───────────────────────────────────────────────────────────┐  │
│  │                                  Shared Service Layer                                         │  │
│  │                                                                                               │  │
│  │  WS_Quickbooks  │  QBApiDataWrapper  │  Data                                                 │  │
│  │  CMQBConnectorUtility  │  UpsertCustomRecords                                                 │  │
│  └───────────────────────────────────┬───────────────────────────────────────────────────────────┘  │
│                                      │                                                              │
│                                      ▼                                                              │
│                            QuickBooks Online API                                                    │
│                                                                                                      │
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘

3. LWC Component — cmQBConnectorMaster

3.1 Component Identity

Property

Value

Component Name

cmQBConnectorMaster

JS Class Name

InvoiceController (extends NavigationMixin(LightningElement))

Template

cmQBConnectorMaster.html

Location

force-app/main/default/lwc/cmQBConnectorMaster/

Apex Imports

25+ @AuraEnabled methods from QuickbooksController, QBInvoiceController, CMQBConnectorUtility, QBSalesReceiptController, QBBillController

3.2 Screen State Machine

The component renders exactly one screen at a time using mutually exclusive boolean flags:

Flag

Screen Rendered

Trigger

companySelectionScreen

Company picker (only when >1 company)

connectedCallback when >1 company returned

homeScreen

Push/Pull tab interface

After company selected (or single company auto-selected)

invoiceSection

Invoice creation form

User clicks Push Invoice button

productSection

Product creation form

User clicks Push Product button

createNewCustomer

Warning: go create QB customer first

fetchRelatedCustomerFromParent returns no linked QB customer

bQB_IdFound = false

Record picker to link existing QB record

Retrieve tab flow when no existing QB record found

salesReceiptCreatorVisible

Open the child component to create sales receipt (salesReceiptCreator)

User clicks Push Sales Receipt button

3.3 Initialization Flow (connectedCallback)

connectedCallback()
  │
  ├─ @wire(CurrentPageReference) → extracts recordId from URL state
  ├─ @wire(getObjectInfo, {objectApiName: Quickbooks_Invoice__c}) → schema metadata
  ├─ @wire(getPicklistValues) → Terms picklist on Quickbooks_Invoice__c
  ├─ @wire(getPicklistValues) → Product_Type__c on Quickbooks_Product__c
  │
  └─ getConnectedCompanies()
       │
       ├─ Calls: getListCompanies() [QuickbooksController]
       │
       ├─ 0 companies → show error toast
       ├─ 1 company  → auto-select, call getFieldMappings()
       └─ >1 company → show companySelectionScreen

3.4 Home Screen Initialization (getFieldMappings)

getFieldMappings(companyRecordId)
  │
  └─ Calls: getQBObjectMappingForUI() [CMQBConnectorUtility]
       │
       └─ Returns list of compatible service tabs
            (e.g., Customer, Vendor, Invoice, Product, SalesReceipt)
            Each tab renders a Push card + Retrieve card

3.5 Invoice Push Flow

User clicks "Push Invoice"
  │
  ├─ checkExistingInvoice(recordId, companyRecordId)
  │     └─ If exists → show "already created" warning (Yes/No)
  │
  └─ If not exists:
        ├─ fetchRelatedCustomerFromParent(recordId, companyRecordId)
        │     ├─ Success → populate customerRef on invoice wrapper
        │     └─ No QB customer found:
        │           ├─ custSourceOnInvoice returned → show createNewCustomer screen
        │           └─ Other failure → show error
        │
        ├─ getTermsOptions(label)  → populate Terms combobox
        ├─ getQuickbooksClasses(companyId)  → populate Class combobox (if enabled)
        │
        ├─ sendPrefilledInvoice(recordId, companyId, ...) [pre-fill path]
        │     └─ Populates invoice fields from mapped source sObject fields
        │     OR
        │     createEmptyInvoiceWrapper(mapCustomFields, companyId, parentRecordId)
        │     └─ Blank invoice with today's date, default due date
        │
        └─ Render invoiceSection

3.6 Invoice Submission

User clicks "Create Invoice"
  │
  ├─ validateBusinessRules()
  │     ├─ Name required
  │     ├─ No negative prices
  │     ├─ Inventory type: qty + startDate + assetAccount required
  │     ├─ incomeAccount always required
  │     └─ expenseAccount required only if purchaseCost > 0
  │
  └─ createInvoiceInQuickbooks(sInvoiceWrapper, parentRecordId, companyId, companyRecordId)
        └─ Returns {isSuccess, message, apiResponse}

3.7 Sales Receipt Push Flow

User clicks "Push Sales Receipt"
  │
  ├─ checkExistingSalesReceipt(recordId, companyRecordId)
  │     └─ If exists → show "already created" warning (Yes/No)
  │
  └─ If not exists:
        ├─ fetchRelatedCustomerFromParent(recordId, companyRecordId)
        │     ├─ Success → populate customerRef on sales receipt wrapper
        │     └─ No QB customer found:
        │           ├─ custSourceOnSalesReceipt returned → show createNewCustomer screen
        │           └─ Other failure → show error
        │
        ├─ getQuickbooksClasses(companyId)  → populate Class combobox (if enabled)
        │
        ├─ sendPrefilledSalesReceipt(recordId, companyId, ...) [pre-fill path]
        │     └─ Populates sales receipt fields from mapped source sObject fields
        │     OR
        │     createEmptySalesReceiptWrapper(mapCustomFields, companyId, parentRecordId)
        │     └─ Blank invoice with today's date, default due date
        │
        └─ Render salesReceiptSection

3.8 Sales Receipt Submission

User clicks "Create Sales Receipt"
  │
  ├─ validateBusinessRules()
  │     ├─ Name required
  │     ├─ No negative prices
  │     └─ depositAccount always required
  │
  └─ createSalesReceiptInQuickbooks(sSalesReceiptWrapper, parentRecordId, companyId, companyRecordId)
        └─ Returns {isSuccess, message, apiResponse}

3.9 Product Push Flow

User clicks "Push Product"
  │
  ├─ getCreateProductWrapper(parentRecordId, companyId, companyRecordId)
  │     └─ Returns pre-filled QBRequestWrapper from source sObject + field mapping
  │
  └─ Render productSection
        ├─ Basic Info accordion: name, description, type, SKU, price, purchaseCost
        ├─ Pricing accordion: sales price, purchase cost
        ├─ Inventory Tracking accordion (only if type=Inventory):
        │     qty on hand, start date, asset account (lightning-record-picker)
        └─ Accounts accordion:
              income account (lightning-record-picker, filtered: type=Income)
              expense account (lightning-record-picker, filtered: type in [Expense, COGS])
              asset account (lightning-record-picker, filtered: type=Other Current Asset, subType=Inventory)

3.10 Bill Push Flow

Unlike Invoice (inline in cmQBConnectorMaster) or Sales Receipt (delegated to salesReceiptCreator), Bill push is delegated to the child component billCreator, shown when billCreatorVisible = true.

User clicks "Push Bill" → showBillCreator() sets billCreatorVisible = true
  •
  billCreator (child) connectedCallback → init()
    •
    ├─ existingVendorFound → checkExistingBill()
    •
    └─ else → fetchRelatedVendorFromParent(recordId, companyRecordId)
          ├─ Success → existingVendorWrapper populated → checkExistingBill()
          ├─ No QB vendor found (vendSourceOnBill returned) → show createNewVendor screen
          │     • user confirms → createVendorsInQuickbooks(...) → createVendorWrapper(existingVendorId) → checkExistingBill()
          └─ Other failure → show error, close modal

checkExistingBill(recordId, companyRecordId)
  ├─ Bill already exists → show "already created" state, stop
  └─ Not exists:
        ├─ mappings.prefillBill = true → fetchPrefillBill()
        │     └─ sendPrefilledBill(recordId, companyId, sourceObjectName, mapCustomFields, mapFieldMapping, mapProductFields)
        │           populates billWrapper + line items from mapped source sObject fields
        └─ mappings.prefillBill = false → createEmptyBillWrapper(mapCustomFields, companyId, parentRecordId)
              blank bill: today's date, default "Net 30" terms, calculated due date

  → applyVendorToWrapper() binds vendorRef onto billWrapper
  → getQuickbooksClasses(companyId) populates Class combobox (if any)
  → Render billSection

3.11 Bill Submission

User clicks "Create Bill"
  •
  └─ handleCreateBill()
        └─ createBillInQuickbooks(sBillWrapper, parentRecordId, companyId, companyRecordId)
              └─ Returns {isSuccess, message, apiResponse}

Note: unlike Sales Receipt Submission, billCreator has no client-side
validateBusinessRules() step — the wrapper is submitted as-is.

3.12 Retrieve (Pull) Flow

User clicks "Retrieve" on any entity tab
  │
  ├─ validateQBId(recordId, companyId)
  │     └─ Checks if source record has a QB_Id field populated
  │
  ├─ QB_Id found → call appropriate sync method:
  │       Customer → syncCustomerFromQuickbooks(lstRecordIds, companyId, companyRecordId)
  │       Vendor   → syncVendorFromQuickbooks(lstRecordIds, companyId, companyRecordId)
  │       Product  → syncProductsFromQuickbooks(lstRecordIds, companyId, companyRecordId)
  │       Invoice  → syncInvoiceFromQuickbooks(recordId, companyId, companyRecordId)
  │ Sales Receipt  → syncSalesReceiptFromQuickbooks(recordId, companyId, companyRecordId)
  │           Bill  → syncBillFromQuickbooks(recordId, companyId, companyRecordId)
  │
  └─ QB_Id not found → show lightning-record-picker
        User selects existing QB record → populateParentRecordId()
              Customer → populateParentRecordIdOnCustomer(recordId, parentRecordId)
              Vendor   → populateParentRecordIdOnVendor(recordId, parentRecordId)
              Product  → populateParentOnProduct(recordId, parentRecordId)
              Invoice  → populateParentRecordIdOnInvoice(recordId, parentRecordId)
         SalesReceipt  → populateParentRecordIdOnSalesReceipt(recordId, parentRecordId)
              Bill  → populateParentRecordIdOnBill(recordId, parentRecordId)

3.13 Key LWC Properties

Property

Type

Purpose

recordId

String

SF record ID from page URL state

selectedCompanyId

String

QB Realm ID of active company

selectedCompanyRecordId

String

SF record ID of QB Company

invoiceWrapper

Object

Live invoice data model bound to the form

lineItems

Array

Line item rows in invoice form

productWrapper

Object

Product data model bound to product form

customerRef

Object

{Cust_QBID, CustName, CustRecordId} — customer for invoice

showClass

Boolean

Drives QB Class column visibility in line items

mapFieldMapping

Object

Field mapping for invoice pre-fill

mapCustomFields

Object

Custom field mapping for invoice custom fields

mapProductFields

Object

Field mapping for product line items

salesReceiptWrapper

Object

Live sales receipt data model bound to the form

billWrapper

Object

Live bill data model bound to the form (billCreator child component)

3.14 Due Date Calculation

// When Terms selection changes:
dueDate = invoiceDate + parseInt(termValue)

// termValue sourced from getTermsOptions() metadata
// e.g., Net30 → 30, Net60 → 60, Net90 → 90

3.15 Line Item Totals

calculateTotals() {
  subtotal = sum of (qty * unitPrice) for all lineItems
  taxableSubtotal = sum of (qty * unitPrice) for lineItems where taxable = true
}

4. Apex Controller — QuickbooksController

Class declaration: public with sharing class QuickbooksController FLS enforcement: public static Boolean enforceFLS = CMQBConnectorUtility.enforceFLS

4.1 Company Methods


getListCompanies() → List<cm_finance__Quickbooks_Company__c>

Retrieves all active QB companies ordered by Default descending, then CreatedDate descending. The LWC uses this to populate the company selector on first load.



Access

@AuraEnabled

Query

WHERE Active__c = true ORDER BY Default__c DESC, CreatedDate DESC

Returns

List of company records or null on error


4.2 Customer Methods


syncCustomerFromQuickbooks(List<Id> lstRecordIds, String companyId, String companyRecordId) → Map<String, Object>

Retrieves one or more QB Customer records from QuickBooks Online using existing Quickbooks_Id__c values from linked SF Customer records, then upserts them in Salesforce on Composite_Unique_Key__c.

Flow:

  1. Query cm_finance__Quickbooks_Customer__c WHERE Parent_Record_ID__c IN lstRecordIds to collect existing QB IDs

  2. Build QB SQL: SELECT * FROM Customer WHERE Id IN ('<id1>','<id2>',...)

  3. Call WS_Quickbooks.queryCustomer(oWrapper)

  4. On success: call WS_Quickbooks.createCustomerRecords() to build SF records

  5. Resolve sub-customer parent lookups via a secondary QB ID → SF ID map

  6. Set Composite_Unique_Key__c = QB_Id::Customer::RealmId, set Ignore_Webhook__c = true

  7. Upsert on Composite_Unique_Key__c via data.upsurt()

  8. On failure: insert a Failed status customer record



Access

@AuraEnabled

Returns

{isSuccess, message?, apiResponse?}

Error record

Created with Status = Failed and error message

Upsert key

Composite_Unique_Key__c

Echo suppression

Ignore_Webhook__c = true


createCustomersInQuickbooks(List<Id> lstRecordIds, String companyId, String companyRecordId) → Map<String, Object>

Pushes one or more Salesforce records as QB Customers. Supports create-or-update: if a QB Customer already exists for the record, its QB_Id and SyncToken are included in the request body (making it an update).

Flow:

  1. Resolve source sObject name from record ID

  2. Call CMQBConnectorUtility.getQBObjectMapping(objectName, 'Customer', companyRecordId) to get field mappings

  3. Call getRelatedQuickbooksCustomers(lstRecordIds, companyRecordId) → Map<String, String> (parentId → QB_Id::SyncToken)

  4. Query source sObject fields per field mapping

  5. For each record: build QBRequestWrapper via CMQBConnectorUtility.createCustomerRequestWrapper(); if existing QB record, populate QB_Id and QB_SyncToken on the wrapper

  6. Call WS_Quickbooks.createCustomer(oWrapper) per record

  7. On success: upsert SF Customer record on Composite_Unique_Key__c; return {isSuccess: true, customerId: <SF ID>}

  8. On failure: insert Failed customer record



Access

@AuraEnabled

Create vs Update

Determined by presence of existing QB ID — no separate code path

Returns

{isSuccess, customerId?, message?, apiResponse?}


populateParentRecordIdOnCustomer(String recordId, String parentRecordId) → void

Sets Parent_Record_ID__c on an existing cm_finance__Quickbooks_Customer__c record. Called from the LWC when the user manually links an existing QB customer to the SF record via the record picker.



Access

@AuraEnabled

DML

data.modify()


getRelatedQuickbooksCustomers(List<Id> lstRecordIds, String companyRecordId) → Map<String, String>

Non-@AuraEnabled helper. Returns a map of parentRecordId → QB_Id::SyncToken for all non-Failed QB Customer records linked to the provided record IDs. Used internally by createCustomersInQuickbooks to detect existing QB entities before push, enabling upsert semantics.


4.3 Vendor Methods

The Vendor methods mirror the Customer methods in structure and logic:

Method

Description

syncVendorFromQuickbooks(lstRecordIds, companyId, companyRecordId)

Retrieves QB Vendors by existing QB IDs; upserts in SF

createVendorsInQuickbooks(lstRecordIds, companyId, companyRecordId)

Pushes SF records as QB Vendors; supports create-or-update

populateParentRecordIdOnVendor(recordId, parentRecordId)

Links an existing QB Vendor record to an SF source record

getRelatedQuickbooksVendors(lstRecordIds, companyRecordId)

Internal helper: parentRecordId → QB_Id::SyncToken map for vendors

Key field: Composite_Unique_Key__c = QB_Id::Vendor::RealmId


4.4 Account Methods


syncAccountFromQuickbooks(List<Id> lstRecordIds) → Map<String, Object>

Syncs cm_finance__Quickbooks_Account__c records from QB. Unlike Customer/Vendor, this method accepts QB Account records directly (not source SF records), so it derives company information from the QB Account's company lookup rather than from parameters.

Flow:

  1. Validate input object type is cm_finance__Quickbooks_Account__c

  2. Extract existing Quickbooks_Id__c values from the provided records

  3. Look up the company from the first record's cm_finance__Quickbooks_Company__c

  4. Build QB SQL: SELECT * FROM Account WHERE Id IN (...)

  5. Call WS_Quickbooks.queryAccount(oWrapper)

  6. On success: upsert SF records on Composite_Unique_Key__c = QB_Id::Account::RealmId



Access

@AuraEnabled

Input type

Must be cm_finance__Quickbooks_Account__c records (not arbitrary SF objects)

Company resolution

Derived from record's company lookup, not parameter


4.5 Product Methods


getCreateProductWrapper(String parentRecordId, String companyId, String companyRecordId) → Map<String, Object>

Pre-fills a QBRequestWrapper for product creation using the source SF record's field values and the configured field mapping. If the product already exists in QB, its existing QB_Id, SyncToken, name, type, and account references are pre-populated on the wrapper (enabling update semantics on submit).

Flow:

  1. Resolve source object name from parentRecordId

  2. Call CMQBConnectorUtility.getQBObjectMapping(objectName, 'Product', companyRecordId)

  3. Call getRelatedQuickbooksProducts([parentRecordId], companyRecordId) — returns existing QB product if any

  4. Query source sObject fields per field mapping

  5. Build QBRequestWrapper via CMQBConnectorUtility.createProductRequestWrapper()

  6. If existing QB product found: populate QB_Id, SyncToken, name, type, and account refs (as Name::QB_Id::SF_Id encoded strings)

  7. Ensure incomeAccount, expenseAccount, assetAccount are always initialized (empty if not set)

  8. Return {isSuccess, ProductWrapper (JSON), sMessage}



Access

@AuraEnabled

Failure handling

Creates Failed product record if mapping not found

Account encoding

Name::QB_Id::SF_RecordId triple for each account reference


createProductInQuickbooks(String wrapperJson) → Map<String, Object>

Submits a product to QB using a pre-built QBRequestWrapper JSON. Resolves account SF record IDs to QB IDs before the callout by querying Quickbooks_Account__c records.

Flow:

  1. Deserialize wrapperJson → QBApiDataWrapper.QBRequestWrapper

  2. For each of incomeAccount, expenseAccount, assetAccount: if recordId is set, query Quickbooks_Account__c to resolve Name and Quickbooks_Id__c

  3. Set credentials: WS_Quickbooks.sCompanyId = oWrapper.companyRealmId

  4. Call WS_Quickbooks.createProduct(oWrapper)

  5. On success: upsert SF product on Composite_Unique_Key__c = QB_Id::Product::RealmId; set Ignore_Webhook__c = true; set account lookups from wrapper's account record IDs

  6. On failure: insert Failed product record



Access

@AuraEnabled

Account resolution

Batch-queries all three account types before callout

Returns

{isSuccess, message?, apiResponse?}


syncProductsFromQuickbooks(List<Id> lstRecordIds, String companyId, String companyRecordId) → Map<String, Object>

Retrieves QB Item records from QuickBooks using existing QB IDs, then upserts them in Salesforce with full account relationship resolution (income/expense/asset accounts mapped by QB ID → SF record ID).

Flow:

  1. Query Quickbooks_Product__c WHERE Parent_Record_ID__c IN lstRecordIds to collect QB IDs

  2. Build QB SQL: SELECT * FROM Item WHERE Id IN (...)

  3. Call WS_Quickbooks.queryProduct(oWrapper)

  4. On success: call WS_Quickbooks.createProductRecords() to build SF records

  5. Collect all income/expense/asset account QB IDs from returned products

  6. Batch-resolve account QB IDs → SF record IDs via Quickbooks_Account__c query

  7. Set account lookups, Composite_Unique_Key__c, Ignore_Webhook__c = true

  8. Upsert on Composite_Unique_Key__c



Access

@AuraEnabled

Account resolution

Single batch query for all three account types

Upsert key

Composite_Unique_Key__c


populateParentOnProduct(String recordId, String parentRecordId) → void

Sets Parent_Record_ID__c on an existing QB Product record. Called from the LWC when the user manually links an existing QB product to the SF source record via the record picker.


getRelatedQuickbooksProducts(List<Id> lstRecordIds, String companyRecordId) → Map<String, cm_finance__Quickbooks_Product__c>

Non-@AuraEnabled helper. Returns a map of parentRecordId → Quickbooks_Product__c including full account relationship fields. Used internally by getCreateProductWrapper to pre-fill product form with existing QB data.


5. Apex Controller — QBInvoiceController

Class declaration: public with sharing class QBInvoiceController Static properties:

  • storePDF: Reads GenerateInvoicePDF custom setting — controls initial invoice status

  • enforceFLS: From CMQBConnectorUtility.enforceFLS

5.1 Pre-Flight Check Methods


checkExistingInvoice(String recordId, String companyRecordId) → Map<String, Object>

Checks whether a non-Failed QB Invoice already exists for the given SF record + company combination. The LWC calls this first when the user clicks "Push Invoice" to decide whether to show a duplicate warning.



Access

@AuraEnabled

Query

WHERE Parent_Record_Id__c = :recordId AND Quickbooks_Company__c = :companyRecordId AND Status__c != 'Failed'

Returns

{isSuccess, exists (Boolean), message?}


getTermsOptions(String label) → String

Retrieves the payment terms JSON for the invoice Terms combobox from cm_finance__CM_QB_Connector_Settings__mdt custom metadata by MasterLabel.



Access

@AuraEnabled

Default

'{"Net30":30,"Net60":60,"Net90":90}' when metadata record not found

Returns

JSON string of {termLabel: daysInteger} pairs


fetchRelatedCustomerFromParent(String recordId, String companyRecordId) → Map<String, Object>

Resolves the QB Customer linked to an invoice's source record by traversing a 4-step chain through configured field mappings. This is the primary customer resolution path when opening the invoice form.

Resolution chain:

Custom_Field_Mapping__c
  → Source_Invoice_Customer_Lookup_field__c
       → Source sObject[customerLookupField]
            → Quickbooks_Customer__c[Parent_Record_ID__c = customerLookupValue]
                 → createCustomerWrapper(QB Customer Id)

Response key

Meaning

{isSuccess: true, customerWrapper, customerId}

Customer found and serialized

{isSuccess: false, custSourceOnInvoice: <SF Id>}

SF source found but no linked QB customer — triggers "create customer first" screen

{isSuccess: false, errorMessage}

Mapping misconfiguration or missing field



Access

@AuraEnabled

Mapping source

cm_finance__Custom_Field_Mapping__c.Source_Invoice_Customer_Lookup_field__c


checkExistingQBCustomer(String recordId, String mapFields) → Map<String, Object>

Alternative customer resolution for cases where the source record directly holds a QB Customer lookup field (rather than going through the 4-step chain). Used by the LWC when the field mapping includes a QuickbooksCustomer key.



Access

@AuraEnabled

Returns

{isSuccess, customerWrapper?} or {isSuccess: false, isRelatedCustomerNotFound: true}


createCustomerWrapper(String recordId) → String

Serializes a cm_finance__Quickbooks_Customer__c record into a QBApiDataWrapper.Customer JSON string for use by the invoice form's customer reference fields.

Fields serialized: Company_Name__c, First_Name__c, Last_Name__c, Phone__c, Email__c, billing address (5 fields), shipping address (5 fields), Quickbooks_Id__c, Id



Access

@AuraEnabled

Returns

JSON-serialized QBApiDataWrapper.Customer or null


getQuickbooksClasses(String companyId) → List<cm_finance__Quickbooks_Class__c>

Returns active QB Class records for the company. Returns null without querying if the ShowClassInLWC custom setting is not true.



Access

@AuraEnabled

Guard

ShowClassInLWC custom setting must = true

Query

WHERE Quickbooks_Company__c = :companyId AND Active__c = true


5.2 Invoice Wrapper Methods


sendPrefilledInvoice(String recordId, String companyId, String sourceObjectName, String mapCustomFields, String mapFieldMapping, String mapProductFields) → String

The primary invoice pre-fill method. Queries the source sObject, maps all configured fields onto a QBApiDataWrapper.Invoice instance, resolves custom fields from QB definition IDs, and resolves product line items by traversing the child relationship.

Phase 1 — Field collection:

  • Merges all SF field names from mapFieldMapping values and mapCustomFields values into a single setFields

  • Executes a single dynamic SOQL against sourceObjectName WHERE Id = :recordId

Phase 2 — Invoice population:

  • Calls oInvoice.populateField(key, value) for each mapped field

  • Default txnDate = today if not mapped

  • Default dueDate = txnDate + getDefaultDaysForDueDateInvoice() (fallback 30) if not mapped

Phase 3 — Custom fields:

  • Queries cm_finance__Quickbooks_Custom_Field__c WHERE Definition_Id__c IN mapCustom.keySet()

  • Builds lstCustomFields with {definitionId, customFieldName, value} for each

Phase 4 — Product line items:

  • Parses mapProductFields.query (a dynamic child SOQL template) and mapProductFields.lookupFieldName

  • Executes child query against parentRecordId

  • Resolves SF Product IDs → QB Product records via cm_finance__Quickbooks_Product__c

  • Returns productsArray as list of {ProductRecordId, productQBId, qty, rate, ...}

Fallback: If mapFieldMapping is empty (no configured mapping), delegates to createEmptyInvoiceWrapper().



Access

@AuraEnabled

Returns

JSON {bSuccess, invoiceWrapper?, invoicePopulated?, productsPopulated?, productsArray?, message}


createEmptyInvoiceWrapper(String mapCustomFields, String companyId, String parentRecordId) → String

Creates a blank invoice wrapper with sensible defaults. Used when there is no field mapping configured or as a fallback from sendPrefilledInvoice.

Default values:

  • txnDate = System.today()

  • dueDate = txnDate + getDefaultDaysForDueDateInvoice() (fallback 30)

  • billingEmail = '', terms = '', deposit = 0

  • Billing address fields initialized to empty strings

If mapCustomFields is provided, queries the source object and custom field definitions to pre-populate lstCustomFields with empty or mapped values.



Access

@AuraEnabled

Returns

JSON {isSuccess: true, invoiceWrapper} or {isSuccess: false, message}


5.3 Invoice Create / Sync Methods


createInvoiceInQuickbooks(String sInvoiceWrapper, String parentRecordId, String companyId, String companyRecordId) → String

The main invoice creation method. Deserializes the LWC's invoice form state, builds a typed QBApiDataWrapper.Invoice via createInvoiceWrapperAPI(), calls the QB API, and persists the result in Salesforce.

Pre-condition: mapInvoice.get('Products') must be non-null and non-empty — invoices require at least one line item.

Phase 1 — API callout:

  • Builds QBApiDataWrapper.Invoice via private createInvoiceWrapperAPI(mapInvoice)

  • Calls WS_Quickbooks.createInvoice(oRequestWrapper)

Phase 2 — Lookup resolution (on success):

  • Collects QB IDs for: customers, products, classes, custom field definition IDs

  • Resolves each to SF record IDs via batch queries:

    • Quickbooks_Customer__c WHERE Quickbooks_Id__c IN setCustomerQBId

    • Quickbooks_Product__c WHERE Quickbooks_Id__c IN setProductQBId

    • Quickbooks_Class__c WHERE Quickbooks_Id__c IN setClassQBId

    • Quickbooks_Custom_Field__c WHERE Definition_Id__c IN setCustomFieldId

Phase 3 — Custom field decoding:

  • Raw format stored by QB: DefinitionId::Value

  • After resolution: Custom_Field_N__c set to the value, Quickbooks_Custom_Field_N__c set to the SF Custom Field record ID

Phase 4 — Record persistence:

  • Sets Composite_Unique_Key__c = QB_Id::Invoice::RealmId

  • Sets Ignore_Webhook__c = true

  • Sets initial Status__c = 'Invoice PDF Pending' if storePDF = true, else 'Awaiting Salesforce Sync'

  • data.create(lstAllInvoice) → then data.create(lstAllLineItems) (sequential, not upsert)

On any failure: Creates a Failed invoice record with the error message.



Access

@AuraEnabled

Required guard

Products list must not be empty

Returns

JSON {isSuccess, message?, apiResponse?}

Error record

Always created on any failure path


populateParentRecordIdOnInvoice(String recordId, String parentRecordId) → void

Sets Parent_Record_ID__c on an existing cm_finance__Quickbooks_Invoice__c record. Called from the LWC when the user manually links an existing QB invoice to the SF record via record picker.



Access

@AuraEnabled

DML

data.modify()


syncInvoiceFromQuickbooks(String recordId, String companyId, String companyRecordId) → Map<String, Object>

Retrieves the most recent QB Invoice for the SF record from QuickBooks Online and syncs it back into Salesforce with full line item reconciliation.

Phase 1 — Identify target:

  • Queries cm_finance__Quickbooks_Invoice__c WHERE Parent_Record_ID__c = :recordId AND QB_Id != null ORDER BY CreatedDate DESC LIMIT 1

  • Builds QB SQL: SELECT * FROM Invoice WHERE Id IN ('<QB_Id>')

Phase 2 — Lookup resolution (same as createInvoice):

  • Customer QB ID → SF Customer record

  • Product QB IDs → SF Product records

  • Class QB IDs → SF Class records

  • Custom field definition IDs → SF Custom Field records

  • Custom field decoded from DefinitionId::Value format

Phase 3 — Line item reconciliation:

  • Before the callout, loads all existing line items into mapInvLineItem (keyed by Composite_Unique_Key__c); items without a key go into lstDeleteLineItem

  • After callout: upserts invoice on Composite_Unique_Key__c

  • For incoming line items: upserts on Composite_Unique_Key__c — existing lines are updated, new lines are inserted

  • Orphaned line items (in SF but not in QB response): archived (Archived__c = true) or hard-deleted based on deleteExtraLineItemsSetting() custom setting



Access

@AuraEnabled

Callout

WS_Quickbooks.queryInvoice(oWrapper)

Returns

{isSuccess, message}

Orphan handling

Setting-driven: archive or hard delete


5.4 Private Helper


createInvoiceWrapperAPI(Map<String, Object> mapInvoice) → QBApiDataWrapper.Invoice (private)

Deserializes the raw LWC JSON map (passed from createInvoiceInQuickbooks) into a fully typed QBApiDataWrapper.Invoice object ready for the API callout.

Key mappings:

LWC Map Key

Invoice Field

Notes

billingEmail

oInvoice.billingEmail

Direct string

terms

oInvoice.terms

Direct string

txnDate

oInvoice.txnDate

Direct string

dueDate

oInvoice.dueDate

Direct string

deposit

oInvoice.deposit

Decimal

Products[].ProductRecordId

itemRef.value/name

Resolved via Quickbooks_Product__c query

Products[].qbClassId

classRef.value

Direct string

Products[].ProductQuantity

salesItemDetail.quantity

Decimal

Products[].ProductUnitPrice

salesItemDetail.unitPrice

Decimal

Products[].ProductAmount

oLine.amount

Decimal

Products[].ProductDescription

oLine.description

String

customerRef.Cust_QBID

customerRef.value

QB Customer ID

customerRef.CustName

customerRef.name

Customer display name

customerRef.CustRecordId

customerRef.recordId

SF Customer record ID

customerMemo.value

customerMemo.value

Memo string

lstCustomFields[].definitionId + value

lstCustomFields

Only non-null pairs included

billingAddress.*

billingAddress.*

5 address fields

shippingAddress.*

shippingAddress.*

5 address fields

Line items are numbered sequentially starting at 1 via a count variable.

6. Apex Controller — QBSalesReceiptController

Class declaration: public with sharing class QBSalesReceiptController

Static properties:

  • storePDF — Reads the GenerateSalesReceiptPDF custom setting and controls the initial Sales Receipt status.

  • enforceFLS — From CMQBConnectorUtility.enforceFLS.

The controller is the AuraEnabled backend for the Create/View QB Sales Receipt LWC flow. It supports dynamic field mapping for Sales Receipt creation, including source-record fields, custom fields, product/line-item mappings, payment methods, deposit accounts, and QuickBooks Classes.

6.1 Pre-Flight Check Methods

checkExistingSalesReceipt(String recordId, String companyRecordId) → Map<String, Object>

Checks whether a non-Failed QB Sales Receipt already exists for the given Salesforce parent record and company. The LWC calls this first when the user clicks “Push Sales Receipt” to decide whether to show a duplicate warning.

Access

@AuraEnabled

Returns

{isSuccess, exists (Boolean), message?}

fetchRelatedCustomerFromParent(String recordId, String companyRecordId) → Map<String, Object>

Resolves the QB Customer linked to a Sales Receipt source record using the configured Sales Receipt customer lookup field mapping.

Resolution chain:

Custom_Field_Mapping__c (Mapping_Type__c = 'SalesReceipt')
  → configured Sales Receipt customer lookup field
       → Source sObject[customerLookupField]
            → Quickbooks_Customer__c[Parent_Record_ID__c = customerLookupValue]
                 → createCustomerWrapper(QB Customer Id)

checkExistingQBCustomer(String recordId, String mapFields) → Map<String, Object>

Checks the configured QuickBooks Customer lookup field on the source record and returns the related QB Customer wrapper when the source record directly stores a QB Customer lookup.

createCustomerWrapper(String recordId) → String

Retrieves a cm_finance__Quickbooks_Customer__c record and converts it into a QBApiDataWrapper.Customer JSON wrapper for the Sales Receipt LWC.

getQuickbooksClasses(String companyId) → List<cm_finance__Quickbooks_Class__c>

Returns active QuickBooks Classes for the selected QuickBooks company when the ShowClassInLWC setting is enabled.

6.2 Sales Receipt Wrapper Methods

sendPrefilledSalesReceipt(String recordId, String companyId, String sourceObjectName, String mapCustomFields, String mapFieldMapping, String mapProductFields) → String

Reads configured field mappings from the source record and returns a pre-filled Sales Receipt wrapper. It also resolves configured custom fields, products, payment method, and deposit account values.

  • Merges source-record fields from field mappings, custom field mappings, and line-item mappings into one query field set.

  • Populates the Sales Receipt wrapper using mapped source field values.

  • Builds lstCustomFields from QuickBooks custom field definition mappings.

  • Builds line-item data by resolving mapped Salesforce products to cm_finance__Quickbooks_Product__c records.

  • Resolves payment method and deposit account values when configured.

createEmptySalesReceiptWrapper(String mapCustomFields, String companyId, String parentRecordId) → String

Creates an empty Sales Receipt wrapper with default values and populates configured custom fields. It is used when there is no pre-fill mapping or as a fallback from sendPrefilledSalesReceipt.

6.3 Sales Receipt Create / Sync Methods

createSalesReceiptInQuickbooks(String sSalesReceiptWrapper, String parentRecordId, String companyId, String companyRecordId) → String

Creates a Sales Receipt in QuickBooks using the supplied Sales Receipt wrapper, creates the corresponding Salesforce Sales Receipt and line item records, and maps related Customer, Product, Class, Payment Method, Deposit Account, and Custom Field records.

  • Deserializes the LWC form state into a typed Sales Receipt request wrapper.

  • Calls WS_Quickbooks.createSalesReceipt().

  • Sets Composite_Unique_Key__c = QB_Id::SalesReceipt::RealmId.

  • Sets Ignore_Webhook__c = true to suppress the round-trip webhook.

  • Sets the initial status to 'Sales Receipt PDF Pending' when PDF generation is enabled; otherwise it uses 'Awaiting Salesforce Sync'.

  • Creates the Sales Receipt and related line-item records in Salesforce.

populateParentRecordIdOnSalesReceipt(String recordId, String parentRecordId) → void

Updates the Parent Record ID on an existing QuickBooks Sales Receipt record. This is called when the user manually links an existing QB Sales Receipt to the Salesforce source record through the LWC retrieve flow.

syncSalesReceiptFromQuickbooks(String recordId, String companyId, String companyRecordId) → Map<String, Object>

Retrieves the latest linked Sales Receipt from QuickBooks and syncs it back into Salesforce with line item reconciliation.

  • Queries the latest linked Sales Receipt and builds a QuickBooks query for its QB Id.

  • Resolves Customer, Product, Class, Payment Method, Deposit Account, and Custom Field references to Salesforce records.

  • Upserts the Sales Receipt by Composite_Unique_Key__c.

  • Upserts incoming line items and archives or deletes orphaned lines based on CMQBConnectorUtility.deleteExtraLineItemsSetting().

6.4 Private Helper

createSalesReceiptWrapperAPI(Map<String, Object> mapSalesReceipt) → QBApiDataWrapper.SalesReceipt (private)

Deserializes the raw LWC JSON map passed from createSalesReceiptInQuickbooks into a fully typed QBApiDataWrapper.SalesReceipt object ready for the QuickBooks API callout.

Key mappings: customer reference, billing/shipping addresses, transaction date, deposit amount, payment reference number, deposit-to account reference, line items, classes, memo, and custom fields.

Usage:

// LWC: @wire or imperative call
QBSalesReceiptController.checkExistingSalesReceipt(recordId, companyRecordId);

7. Apex Controller — QBBillController

Class declaration: public with sharing class QBBillController

Static properties:

  • enforceFLS — From CMQBConnectorUtility.enforceFLS

Note: unlike QBInvoiceController, this class has no storePDF property (Bills have no PDF-generation step) and no getTermsOptions/checkExistingQBCustomer-equivalent methods — vendor resolution has a single path only.

7.1 Pre-Flight Check Methods

checkExistingBill(String recordId, String companyRecordId) → Map<String, Object>

Checks whether a non-Failed QB Bill already exists for the given SF record + company combination. Bill-side counterpart to checkExistingInvoice — the LWC calls this first when the user clicks "Push Bill" to decide whether to show a duplicate warning.

Access

@AuraEnabled

Query

WHERE cm_finance__Parent_Record_ID__c = :recordId AND cm_finance__Quickbooks_Company__c = :companyRecordId AND cm_finance__Status__c != 'Failed'

Returns

{isSuccess, exists (Boolean), message?}

fetchRelatedVendorFromParent(String recordId, String companyRecordId) → Map<String, Object>

Resolves the QB Vendor linked to a bill's source record by traversing a field-mapping chain — the vendor-side equivalent of fetchRelatedCustomerFromParent. This is the only vendor resolution path (there is no direct-lookup alternate like checkExistingQBCustomer on the Invoice side).

Resolution chain:

Custom_Field_Mapping__c (Mapping_Type__c = 'Bill')
  → Source_Invoice_Customer_Lookup_field__c   ← same field API name reused from Invoice mapping
       → Source sObject[vendorLookupField]
            → Quickbooks_Vendor__c[Parent_Record_ID__c = vendorLookupValue]
                 → createVendorWrapper(QB Vendor Id)

Note: the field that stores the vendor lookup API name on the mapping record is literally cm_finance__Source_Invoice_Customer_Lookup_field__c — the same field used for Invoice customer mapping, disambiguated only by cm_finance__Mapping_Type__c = 'Bill'.

Response Key

Meaning

{isSuccess: true, vendorId, vendorWrapper}

Vendor found and serialized

{isSuccess: false, vendSourceOnBill: <SF Id>}

SF source found but no linked QB vendor — triggers "create vendor first" screen

{isSuccess: false, isRelatedVendorNotFound: true, errorMessage}

Vendor lookup resolved but createVendorWrapper returned null

{isSuccess: false, errorMessage}

Mapping misconfiguration or missing field

Access

@AuraEnabled

Mapping source

cm_finance__Custom_Field_Mapping__c.cm_finance__Source_Invoice_Customer_Lookup_field__c WHERE cm_finance__Mapping_Type__c = 'Bill'

createVendorWrapper(String recordId) → String

Serializes a cm_finance__Quickbooks_Vendor__c record into a QBApiDataWrapper.Vendor JSON string for use by the bill form's vendor reference field.

Fields serialized: Company_Name__c, Display_Name__c, First_Name__c, Last_Name__c, Phone__c, Email__c, Website__c, billing address (5 fields: Street__c, State_Code__c, Postal_Code__c, City__c, Country__c), Quickbooks_Id__c, Id

Note: Vendor has only a billing address, no shipping address — unlike createCustomerWrapper on the Invoice side.

Access

@AuraEnabled

Returns

JSON-serialized QBApiDataWrapper.Vendor or null

getQuickbooksClasses(String companyId) → List<cm_finance__Quickbooks_Class__c>

Identical contract to the Invoice controller's method of the same name. Returns active QB Class records for the company. Returns null without querying if the ShowClassInLWC custom setting is not true.

Access

@AuraEnabled

Guard

ShowClassInLWC custom setting must = true

Query

WHERE cm_finance__Quickbooks_Company__c = :companyId AND cm_finance__Active__c = true

7.2 Bill Wrapper Methods

sendPrefilledBill(String recordId, String companyId, String sourceObjectName, String mapCustomFields, String mapFieldMapping, String mapProductFields) → String

The primary bill pre-fill method — bill-side counterpart to sendPrefilledInvoice, built around Lines (expense lines) instead of Invoice's product line items, with vendor + AP account in place of customer.

Phase 1 — Field collection:

  • Merges all SF field names from mapFieldMapping values, mapProductFields.fieldMapping values, and mapCustomFields values into a single setFields

  • Executes a single dynamic SOQL against sourceObjectName WHERE Id = :recordId

Phase 2 — Bill population:

  • Calls oBill.populateField(key, value) for each mapped field

  • vendorRef and apAccountRef initialized as empty AccountReference objects up front

  • Default txnDate = today if not mapped

  • Unlike Invoice, there is no default dueDate calculation here — getDefaultDaysForDueDateInvoice() is Invoice-only.

Phase 3 — Custom fields:

  • Queries cm_finance__Quickbooks_Custom_Field__c WHERE Definition_Id__c IN mapCustom.keySet()

  • Builds lstCustomFields with {definitionId, customFieldName, value} for each

Phase 4 — Line items:

  • Parses mapProductFields.query (dynamic child SOQL template) and mapProductFields.lookupFieldName, or falls back to the already-queried source record if no child query is configured

  • Resolves SF Product IDs → QB Product records via cm_finance__Quickbooks_Product__c, first by direct Id match, then by Parent_Record_Id__c for any remaining unmatched IDs

  • Returns productsArray as list of {ProductRecordId, productQBId, ...} plus any other mapped fields (qty, rate, amount, etc.)

Fallback: If no fields resolve from the source query, delegates to createEmptyBillWrapper().

Access

@AuraEnabled

Returns

JSON {bSuccess, billWrapper?, billPopulated?, productsPopulated?, productsArray?, message}

createEmptyBillWrapper(String mapCustomFields, String companyId, String parentRecordId) → String

Creates a blank bill wrapper with sensible defaults. Used when there is no field mapping configured or as a fallback from sendPrefilledBill.

Default values:

  • txnDate = System.today()

  • privateNote = '', docNumber = ''

  • vendorRef and apAccountRef initialized as empty AccountReference objects

  • No address defaults — unlike createEmptyInvoiceWrapper, the Bill wrapper carries no billing/shipping address fields of its own

  • If mapCustomFields is provided, queries the source object and custom field definitions to pre-populate lstCustomFields with empty or mapped values

Access

@AuraEnabled

Returns

JSON {isSuccess: true, billWrapper} or {isSuccess: false, message}

7.3 Bill Create / Sync Methods

createBillInQuickbooks(String sBillWrapper, String parentRecordId, String companyId, String companyRecordId) → String

The main bill creation method. Deserializes the LWC's bill form state, builds a typed QBApiDataWrapper.Bill via createBillWrapperAPI(), calls the QB API, and persists the result in Salesforce.

Pre-condition: mapBill.get('Lines') must be non-null — bills require at least one line. (Note the key is Lines, not Products as on the Invoice side, reflecting Bill's dual line-type model.)

Phase 1 — API callout:

  • Builds QBApiDataWrapper.Bill via private createBillWrapperAPI(mapBill)

  • Calls WS_Quickbooks.createBill(oRequestWrapper)

Phase 2 — Lookup resolution (on success):

  • Collects QB IDs for: vendors, AP accounts, GL accounts, products, classes

  • No custom-field-definition-id resolution here — unlike createInvoiceInQuickbooks, this method does not decode/resolve custom fields on the returned Bill record.

  • Resolves each to SF record IDs via batch queries: Quickbooks_Vendor__c, Quickbooks_Account__c for AP/GL accounts, Quickbooks_Product__c, and Quickbooks_Class__c.

Phase 3 — Record persistence:

  • Sets Composite_Unique_Key__c = QB_Id::Bill::RealmId

  • Sets Ignore_Webhook__c = true

  • Sets initial Status__c = 'Awaiting Salesforce Sync' unconditionally — no PDF-pending branch, since Bills have no PDF generation step

  • data.create(lstAllBill) → then data.create(lstAllLineItems) (sequential, not upsert)

  • On any failure: Creates a Failed bill record with the error message via CMQBConnectorUtility.createBillRecord

populateParentRecordIdOnBill(String recordId, String parentRecordId) → void

Sets Parent_Record_ID__c on an existing cm_finance__Quickbooks_Bill__c record. Bill-side counterpart to populateParentRecordIdOnInvoice — called from the LWC when the user manually links an existing QB bill to the SF record via record picker.

syncBillFromQuickbooks(String recordId, String companyId, String companyRecordId) → Map<String, Object>

Retrieves the most recent QB Bill for the SF record from QuickBooks Online and syncs it back into Salesforce with full line item reconciliation.

  • Queries the latest linked bill, then builds QB SQL: SELECT * FROM Bill WHERE Id IN ('<QB_Id>')

  • Resolves Vendor, AP Account, GL Account, Product, and Class QB IDs to SF records.

  • Reconciles line items by Composite_Unique_Key__c; incoming lines are upserted and orphaned lines are archived or hard-deleted based on CMQBConnectorUtility.deleteExtraLineItemsSetting().

Access

@AuraEnabled

Callout

WS_Quickbooks.queryBill(oWrapper)

Returns

{isSuccess, message}

Orphan handling

Setting-driven: archive or hard delete

7.4 Private Helper

createBillWrapperAPI(Map<String, Object> mapBill) → QBApiDataWrapper.Bill (private)

Deserializes the raw LWC JSON map (passed from createBillInQuickbooks) into a fully typed QBApiDataWrapper.Bill object ready for the API callout. Bill-side counterpart to createInvoiceWrapperAPI, but built around a dual line-type model (Item-based vs. Account-based expense lines) instead of Invoice's single sales-item-line type.

Note: unlike createInvoiceWrapperAPI, this method does not read lstCustomFields from mapBill — custom fields populated earlier in sendPrefilledBill/createEmptyBillWrapper are not carried through into the QB API payload.

Key mappings:

LWC Map Key

Bill Field

Notes

txnDate

oBill.txnDate

Direct string

dueDate

oBill.dueDate

Direct string

docNumber

oBill.docNumber

Direct string

privateNote

oBill.privateNote

Direct string

terms

oBill.terms + salesTermRef.name

Carries the QB term name chosen in the UI; the Id (SalesTermRef.value) is resolved against QBO right before submission in WS_Quickbooks.createBill

vendorRef.QB_Id

vendorRef.value

QB Vendor ID

vendorRef.displayName / companyName

vendorRef.name

Falls back to companyName if displayName is blank

vendorRef.recordId

vendorRef.recordId

SF Vendor record ID

apAccountRef.recordId

apAccountRef.value/name

Resolved via a fresh Quickbooks_Account__c query — not passed straight through like vendorRef

Lines[].lineType == 'Item' + productRecordId

detailType = ItemBasedExpenseLineDetail, itemRef, quantity, unitPrice, classRef, customerRef

Resolved via Quickbooks_Product__c query

Lines[].accountRecordId (non-Item lines)

detailType = AccountBasedExpenseLineDetail, accountRef, classRef, customerRef

Resolved via Quickbooks_Account__c query

Lines[].qbClassId

classRef.value

Direct string, shared across both line detail types

Lines[].customerRecordId

customerRef.value/name/recordId

Optional "billable to customer" reference on the line, resolved via Quickbooks_Customer__c

Lines[].amount

oLine.amount

Decimal

Lines[].description

oLine.description

String

Line items are numbered sequentially starting at 1 via a count variable — incremented for both Item-based and Account-based lines.


8. End-to-End Flow Diagrams

8.1 Invoice Push (Full Path)

User: clicks "Push Invoice" on SF Record Page
           │
           ▼
[LWC] checkExistingInvoice()
           │
    ┌──────┴──────┐
   exists?       not exists
    │                  │
"Already exists"  [LWC] fetchRelatedCustomerFromParent()
 show warning          │
    │          ┌───────┴────────────────┐
    │     QB customer               No QB customer
    │     found                     found
    │          │                        │
    │    [LWC] getTermsOptions()    Show "create customer"
    │    [LWC] getQuickbooksClasses()   screen (end)
    │    [LWC] sendPrefilledInvoice()
    │          │
    │    Render invoice form
    │          │
    │    User fills/edits form
    │          │
    │    [LWC] validateBusinessRules()
    │          │ fails → show errors
    │          │ passes →
    │    [LWC] createInvoiceInQuickbooks()
    │          │
    │    ┌─────┴──────┐
    │  Success      Failure
    │    │              │
    │  SF records   Failed invoice record
    │  created      created in SF
    │    │
    └────▶ Success toast → navigate to home screen

8.2 Product Retrieve (Pull Path)

User: clicks "Retrieve" on Product tab
           │
           ▼
[LWC] validateQBId(recordId, companyId)
           │
    ┌──────┴──────────────┐
QB_Id field found    QB_Id field not found
    │                        │
[LWC] syncProductsFromQuickbooks()   Show lightning-record-picker
    │                        │
[QB API] queryProduct()      User selects existing QB product
    │                        │
Upsert SF product       populateParentOnProduct(selectedId, recordId)
on Composite_Unique_Key__c   │
    │                   Success toast
Success toast

8.3 Sales Receipt Push (Full Path)

User: clicks "Push Sales Receipt" on SF Record Page
           │
           ▼
[LWC] checkExistingSalesReceipt()
           │
    ┌──────┴──────┐
   exists?       not exists
    │                  │
"Already exists"  [LWC] fetchRelatedCustomerFromParent()
 show warning          │
    │          ┌───────┴─────────────────────────────┐
    │     QB customer                         No QB customer
    │     found                                   found
    │          │                                     │
    │    [LWC] getQuickbooksClasses()       Show "create customer"
    │    [LWC] sendPrefilledSalesReceipt()      screen (end)
    │          │
    │    Render sales receipt form
    │          │
    │    User fills/edits form
    │          │
    │    [LWC] validateBusinessRules()
    │          │ fails → show errors
    │          │ passes →
    │    [LWC] createSalesReceiptInQuickbooks()
    │          │
    │    ┌─────┴──────┐
    │  Success      Failure
    │    │              │
    │  SF records   Failed sales receipt record
    │  created      created in SF
    │    │
    └────▶ Success toast → navigate to home screen

8.4 Bill Push workflow

User: clicks "Push Bill" on SF Record Page
           │
           ▼
[LWC] checkExistingBill()
           │
    ┌──────┴──────┐
   exists?       not exists
    │                  │
"Already exists"  [LWC] fetchRelatedVendorFromParent()
 show warning          │
    │          ┌───────┴─────────────────────────────┐
    │     QB vendor                            No QB vendor
    │     found                                   found
    │          │                                     │
    │    [LWC] getQuickbooksClasses()       Show "create vendor"
    │    [LWC] sendPrefilledBill()              screen (end)
    │          │
    │    Render bill form
    │          │
    │    User fills/edits form
    │          │
    │    [LWC] validateBusinessRules()
    │          │ fails → show errors
    │          │ passes →
    │    [LWC] createBillInQuickbooks()
    │          │
    │    ┌─────┴──────┐
    │  Success      Failure
    │    │              │
    │  SF records   Failed bill record
    │  created      created in SF
    │    │
    └────▶ Success toast → navigate to home screen

9. Shared Data Model

9.1 QBApiDataWrapper.Invoice

Field

Type

Description

billingEmail

String

Email for QB invoice delivery

txnDate

String

Invoice date (YYYY-MM-DD)

dueDate

String

Payment due date (YYYY-MM-DD)

terms

String

Payment terms label (e.g., "Net30")

deposit

Decimal

Deposit amount

customerRef

AccountReference

{value: QB_Id, name, recordId}

customerMemo

AccountReference

{value: memo string}

billingAddress

Address

Street, city, state, postal, country

shippingAddress

Address

Street, city, state, postal, country

lstLines

List<Line>

Line items

lstCustomFields

List<CustomField>

QB custom fields

9.2 QBApiDataWrapper.SalesReceipt

Field

Type

Description

billingEmail

String

Email for QB invoice delivery

txnDate

String

Invoice date (YYYY-MM-DD)

deposit

Decimal

Deposit amount

depositToAccountRef

AccountReference

{value: QB_Id, name, recordId}

paymentRefNum

String

Payment Reference number

customerRef

AccountReference

{value: QB_Id, name, recordId}

customerMemo

AccountReference

{value: memo string}

billingAddress

Address

Street, city, state, postal, country

shippingAddress

Address

Street, city, state, postal, country

lstLines

List<Line>

Line items

lstCustomFields

List<CustomField>

QB custom fields

9.3 QBApiDataWrapper.Product

Field

Type

Description

name

String

Product/service name

typeOfProduct

String

Service, Inventory, NonInventory

description

String

Sales description

purchaseDescription

String

Purchase description

salesPrice

Decimal

Unit sales price

purchaseCost

Decimal

Purchase cost

sku

String

SKU code

qty

Decimal

Quantity on hand (Inventory only)

invStartDate

String

Inventory start date (Inventory only)

incomeAccount

AccountReference

{value: QB_Id, name, recordId: SF_Id}

expenseAccount

AccountReference

{value: QB_Id, name, recordId: SF_Id}

assetAccount

AccountReference

{value: QB_Id, name, recordId: SF_Id}

9.4 QBApiDataWrapper.Bill

Field

Type

Description

recordId

String

Salesforce record ID for the linked QuickBooks Bill record

docNumber

String

Bill document number

totalAmount

Decimal

Total bill amount returned from QuickBooks

balance

Decimal

Outstanding balance on the bill

txnDate

String

Bill transaction date (YYYY-MM-DD)

dueDate

String

Bill due date (YYYY-MM-DD)

QB_Id

String

QuickBooks Bill ID

QB_SyncToken

String

QuickBooks SyncToken used for updates

privateNote

String

Private note or memo stored on the bill

vendorRef

AccountReference

{value: QB_Id, name, recordId} for the bill vendor

apAccountRef

AccountReference

{value: QB_Id, name, recordId} for the Accounts Payable account

terms

String

Payment terms label selected for the bill

salesTermRef

AccountReference

QuickBooks term reference; populated with the selected term name during field population

trnsTaxDetail

TxnTaxDetail

Transaction tax detail for the bill

lstLines

List<Line>

Bill line items, including item-based and account-based expense lines

lstCustomFields

List<CustomField>

QB custom fields mapped onto the bill wrapper

9.5 Composite_Unique_Key__c Format

Entity

Format

Customer

QB_Id::Customer::RealmId

Vendor

QB_Id::Vendor::RealmId

Product

QB_Id::Product::RealmId

Account

QB_Id::Account::RealmId

Invoice

QB_Id::Invoice::RealmId

Invoice Line Item

QBProductId::InvoiceId::QB_Id

Sales Receipt

QB_Id::SalesReceipt::RealmId

Sales Receipt Line Item

QBProductId::SalesReceiptId::QB_Id

Payment Method

QB_Id::PaymentMethod::RealmId

Bill

QB_Id::Bill::RealmId

Bill Line Item

QB_Id::Bill_Line_Id


10. Configuration Reference

Setting

Type

Key/Label

Effect

Company active flag

Custom Object field

Active__c = true

Only active companies shown in selector

Company default

Custom Object field

Default__c DESC

Default company auto-selects when only one

Batch size

Custom Setting (Quickbooks_Common_Settings__c)

BatchSize

Controls batch size for webhook batches (not used here)

Generate PDF

Custom Setting

GenerateInvoicePDF

and GenerateSalesReceiptPDF

When true, initial invoice status = 'Invoice PDF Pending'; when false = 'Awaiting Salesforce Sync'. Same in case for the Sales Receipt the initial status will be 'Sales Receipt PDF Pending'

Show QB Classes

Custom Setting

ShowClassInLWC

When true, QB Class column visible on invoice line items

Default due days

Custom Setting

(via CMQBConnectorUtility.getDefaultDaysForDueDateInvoice())

Default days added to invoice date for due date

Terms options

Custom Metadata (CM_QB_Connector_Settings__mdt)

MasterLabel per terms config

JSON {label: days} — populates invoice Terms combobox

Customer lookup field

Custom Field Mapping (Custom_Field_Mapping__c)

Source_Invoice_Customer_Lookup_field__c

Field on the source sObject that points to the QB Customer's SF parent record

FLS enforcement

Code constant

CMQBConnectorUtility.enforceFLS

When true, all SOQL and DML respect field-level security

Delete extra line items

Custom Setting

deleteExtraLineItemsSetting()

Controls whether orphaned line items are hard-deleted or archived during invoice sync


9. Account Filter Criteria (Product Form)

The lightning-record-picker components in the product form use server-side filter criteria passed from the LWC to restrict which QB Accounts are selectable:

Account Field

Filter Criteria

Income Account

Type__c = 'Income'

Expense Account

Type__c IN ('Expense', 'Cost of Goods Sold')

Asset Account

Type__c = 'Other Current Asset' AND SubType__c = 'Inventory'


10. Error Handling Patterns

10.1 Failed Record Creation

All push methods (Customer, Vendor, Product, Invoice) follow a consistent pattern on failure: a record with Status__c = 'Failed' is always inserted in Salesforce, capturing the error message. This ensures failed pushes are visible and auditable in the SF org without requiring log inspection.

10.2 Validation Guards

createInvoiceInQuickbooks fails fast (before any callout) if:

  • sInvoiceWrapper is blank

  • parentRecordId is blank

  • companyId is blank

  • mapInvoice.get('Products') is null or empty

The LWC-side validateBusinessRules() provides a second layer of validation before the Apex call is made.

10.3 Echo Suppression

All records created by Push operations set Ignore_Webhook__c = true. This flag is checked by Webhook_QBChangeHandler → QuickbooksIntegrationHelper to prevent the resulting QB change notification from triggering a redundant re-sync back into Salesforce.


11. Design Decisions

11.1 Screen State Machine via Boolean Flags

Rather than a router or navigation component, cmQBConnectorMaster uses a flat set of boolean flags to manage screen visibility. This pattern avoids LWC child component instantiation overhead and keeps all screen state in a single JS class context, which simplifies data passing between screens. The tradeoff is that the HTML template becomes large and all screen logic is in one file.

11.2 Composite_Unique_Key__c as Upsert Key

Rather than using the QB-assigned ID as the external upsert key directly, the connector uses a compound key QB_Id::EntityType::RealmId. This ensures multi-company safety: two companies can have customers with the same QB ID, and the compound key disambiguates them. The ::EntityType:: segment also provides implicit self-documentation on the record.

11.3 Create and Update on the Same Code Path

For Customer, Vendor, and Product push, the code checks for an existing QB record via getRelatedQuickbooks*() helpers and — if one exists — populates QB_Id and SyncToken on the request wrapper. The QB API interprets a request with an existing Id + SyncToken as an update. This means a user clicking "Push" on a record that already exists in QB will correctly update it rather than create a duplicate, with no separate code path required.

11.4 Pre-fill vs Empty Invoice Path

sendPrefilledInvoice is called when a field mapping is configured for the source sObject. If the mapping returns no fields (or no mapping exists), it falls back to createEmptyInvoiceWrapper. This graceful degradation means the component always renders a usable form even when the admin has not completed the mapping setup.

11.5 Sequential Invoice + Line Item Insert

Invoice records are inserted with data.create() (not upserted) on the creation path because the Salesforce record ID is needed as the parent lookup before line items can be created. Upsert on Composite_Unique_Key__c is used only on the sync path where the record may already exist.

11.6 Custom Field Encoding

QB returns custom field values with a DefinitionId::Value encoded format in the raw response. createInvoiceInQuickbooks and syncInvoiceFromQuickbooks both decode this format after the API call, splitting on :: to separate the definition ID (for SF lookup resolution) from the actual value.