QuickBooks Connector

Class Level Information for QuickBooks Integration

/**
* @class       BatchAutoMatchQBCustomers
* @description Iterates cm_finance__Quickbooks_Customer__c records where
*              cm_finance__Parent_Record_ID__c != null and
*              cm_finance__Auto_Matched__c = false,
*              and attempts to match each to a Salesforce Account.
*
*              On a successful match:
*                - cm_finance__Auto_Matched__c  is set to true
*                - cm_finance__Auto_Matched_Salesforce_Record_ID__c is stamped with the Account Id
*
*              Field mappings are stored as a JSON blob in cm_finance__CM_QB_Connector_Settings__mdt.
*              Account records are loaded ONCE per execute() chunk — no
*              repeated queries per QB record.
*
*              Matching tiers (in JSON order):
*                "Email"        → normalize QB email domain,
*                                 compare against SF Account website domain
*                "Phone"        → normalized phone digits
*                "Company Name" → SOSL fuzzy name search
*
*              Address SOSL always runs last as a silent built-in fallback.
*              Sends a summary email to the running user on finish().
*
* @usage
*   Database.executeBatch(new BatchAutoMatchQBCustomers(null), 1);
*   Database.executeBatch(new BatchAutoMatchQBCustomers('AutoMatch_QBCustomerJson', 7), 1);
*/


/**
* @class       BatchAllQBSFAccountsSync
* @description Pulls all Account records from QuickBooks into Salesforce as
*              cm_finance__Quickbooks_Account__c records.
*
*              When run as a Schedulable, first queries the QB API for the
*              total account count, then self-dispatches as a batchable job
*              sized by the Quickbooks_Common_Settings__c BatchSize value.
*
*              Each execute() chunk calls the QB API with a startposition/
*              maxresults page window (tracked via currentOffset), maps the
*              response to cm_finance__Quickbooks_Account__c records via
*              WS_Quickbooks.createAccountRecords(), builds a
*              Composite_Unique_Key__c (QuickbooksId::Account::RealmId),
*              and upserts using that key.
*
*              finish() stamps cm_finance__Last_Sync_Config__c on the
*              Quickbooks Company record with lastSyncedAt and status.
*
*              Duplicate-job guard: start() exits early if another instance
*              of this batch is already Processing.
*
* @usage
*   Database.executeBatch(new BatchAllQBSFAccountsSync(totalCount), batchSize);
*   System.schedule('QB Accounts Sync', cronExpr, new BatchAllQBSFAccountsSync());
*/


/**
* @class       BatchAllQBSFCustomersSync
* @description Pulls all Customer records from QuickBooks into Salesforce as
*              cm_finance__Quickbooks_Customer__c records.
*
*              Follows the same count-first / paginate-in-chunks pattern as
*              BatchAllQBSFAccountsSync. Additionally resolves parent-child
*              customer relationships using a QB-Id-to-Salesforce-Id map
*              before upserting via Composite_Unique_Key__c.
*
*              finish() updates cm_finance__Last_Sync_Config__c on the
*              Company record and then chains BatchAutoMatchQBCustomers
*              to attempt automatic Account matching on the newly synced
*              customers.
*
* @usage
*   Database.executeBatch(new BatchAllQBSFCustomersSync(totalCount), batchSize);
*   System.schedule('QB Customers Sync', cronExpr, new BatchAllQBSFCustomersSync());
*/


/**
* @class       BatchAllQBSFInvoiceSync
* @description Pulls all Invoice records from QuickBooks into Salesforce as
*              cm_finance__Quickbooks_Invoice__c records, including line
*              items as cm_finance__Quickbooks_Invoice_Item__c.
*
*              For each page of invoices:
*                - Resolves QB Customer, Product, Class, and Custom Field
*                  IDs to their Salesforce counterparts via pre-built maps.
*                - Upserts invoices on Composite_Unique_Key__c.
*                - Upserts line items on their own Composite_Unique_Key__c.
*                - Removes or archives orphaned line items based on the
*                  deleteExtraLineItems setting.
*
*              finish() stamps cm_finance__Last_Sync_Config__c on the
*              Company record.
*
* @usage
*   Database.executeBatch(new BatchAllQBSFInvoiceSync(totalCount), batchSize);
*   System.schedule('QB Invoices Sync', cronExpr, new BatchAllQBSFInvoiceSync());
*/


/**
* @class       BatchAllQBSFSalesReceiptSync
* @description Performs a full, paginated pull of QuickBooks Online Sales Receipt
*              transactions into Salesforce as
*              cm_finance__Quickbooks_Sales_Receipt__c records, with related
*              cm_finance__Quickbooks_Sales_Receipt_Item__c line items.
*
*              Sales Receipts represent immediate-payment sales in QuickBooks.
*              The sync preserves the transaction's customer, transaction date,
*              document number, payment method, deposit account, memo, class,
*              custom fields, amounts, discounts, tax, and total values whenever
*              those fields are supported by the Salesforce data model.
*
*              For each QuickBooks page, the batch:
*                - Queries the SalesReceipt endpoint using startPosition and
*                  maxResults pagination.
*                - Resolves QB Customer, Product/Item, Class, Tax, and Custom
*                  Field IDs to their Salesforce counterparts using pre-built
*                  maps.
*                - Maps sales lines, quantities, rates, amounts, descriptions,
*                  taxable status, discounts, and tax details to receipt items.
*                - Upserts receipts using Composite_Unique_Key__c, typically
*                  QuickbooksId::SalesReceipt::RealmId.
*                - Upserts each line item using its own composite key and keeps
*                  the parent receipt relationship intact.
*                - Deletes or archives line items no longer returned by
*                  QuickBooks according to deleteExtraLineItems.
*
*              The batch uses the configured QuickBooks company, batch size,
*              credentials, and API instance. It prevents overlapping full-sync
*              runs, records failures with meaningful status/error information,
*              and updates cm_finance__Last_Sync_Config__c on the
*              cm_finance__Quickbooks_Company__c record in finish(), including
*              the last sync timestamp and outcome.
*
* @usage
*   Database.executeBatch(new BatchAllQBSFSalesReceiptSync(totalCount), batchSize);
*   System.schedule(
*     'QB Sales Receipts Sync',
*     cronExpr,
*     new BatchAllQBSFSalesReceiptSync()
*   );
*/


/**
* @class       BatchAllQBSFPaymentSync
* @description Pulls all Payment records from QuickBooks into Salesforce as
*              cm_finance__Payment_Transaction__c records.
*
*              Each page calls the QB API, maps responses via
*              WS_Quickbooks.createPaymentTxnRecords(), sets
*              cm_finance__Payment_Status__c = 'Settled' and
*              cm_finance__Amount_Captured__c, then upserts on
*              Composite_Unique_Key__c.
*
*              If the getPaymentReceipt setting is enabled, the initial
*              status is set to 'Payment Receipt PDF Pending'.
*
*              finish() stamps cm_finance__Last_Sync_Config__c on the
*              Company record.
*
* @usage
*   Database.executeBatch(new BatchAllQBSFPaymentSync(totalCount), batchSize);
*   System.schedule('QB Payments Sync', cronExpr, new BatchAllQBSFPaymentSync());
*/


/**
* @class       BatchAllQBSFProductsSync
* @description Pulls all Item (Product/Service) records from QuickBooks into
*              Salesforce as cm_finance__Quickbooks_Product__c records.
*
*              Resolves income, expense, and asset QB Account IDs to their
*              Salesforce cm_finance__Quickbooks_Account__c counterparts
*              before upserting on Composite_Unique_Key__c.
*
*              finish() stamps cm_finance__Last_Sync_Config__c on the
*              Company record.
*
* @usage
*   Database.executeBatch(new BatchAllQBSFProductsSync(totalCount), batchSize);
*   System.schedule('QB Products Sync', cronExpr, new BatchAllQBSFProductsSync());
*/


/**
* @class       BatchAllQBSFVendorsSync
* @description Pulls all Vendor records from QuickBooks into Salesforce as
*              cm_finance__Quickbooks_Vendor__c records.
*
*              Follows the same count-first / paginate-in-chunks pattern.
*              Records are upserted on Composite_Unique_Key__c
*              (QuickbooksId::Vendor::RealmId).
*
*              finish() stamps cm_finance__Last_Sync_Config__c on the
*              Company record.
*
* @usage
*   Database.executeBatch(new BatchAllQBSFVendorsSync(totalCount), batchSize);
*   System.schedule('QB Vendors Sync', cronExpr, new BatchAllQBSFVendorsSync());
*/


/**
* @class       BatchQBClassSync
* @description Pulls all Class records from QuickBooks into Salesforce as
*              cm_finance__Quickbooks_Class__c records.
*
*              Resolves parent Class QB IDs to their Salesforce record IDs
*              before upsert. Records are keyed on
*              Composite_Unique_Key__c (QuickbooksId::Class::RealmId).
*
* @usage
*   Database.executeBatch(new BatchQBClassSync(totalCount), batchSize);
*   System.schedule('QB Class Sync', cronExpr, new BatchQBClassSync());
*/


/**
* @class       BatchQBTaxRateSync
* @description Pulls all TaxRate records from QuickBooks into Salesforce as
*              cm_finance__Tax_Rate__c records.
*
*              Records are upserted on Composite_Unique_Key__c
*              (UID::RealmId).
*
* @usage
*   Database.executeBatch(new BatchQBTaxRateSync(totalCount), batchSize);
*   System.schedule('QB TaxRate Sync', cronExpr, new BatchQBTaxRateSync());
*/

/**
* @class       BatchAllQBSFPaymentMethodSync
* @description Pulls all Payment Method records from QuickBooks into Salesforce as
*              cm_finance__Quickbooks_Payment_Method__c records.
*
*              Follows the same count-first / paginate-in-chunks pattern.
*              Records are upserted on Composite_Unique_Key__c
*              (QuickbooksId::PaymentMethod::RealmId).
*
*              finish() stamps cm_finance__Last_Sync_Config__c on the
*              Company record.
*
* @usage
*   Database.executeBatch(new BatchAllQBSFPaymentMethodSync(totalCount), batchSize);
*   System.schedule('QB Payment Method Sync', cronExpr, new BatchAllQBSFPaymentMethodSync());
*/


/**
* @class       BatchRefreshTokenUpdate
* @description Refreshes expired QuickBooks OAuth access tokens for all
*              active, connected cm_finance__Quickbooks_Company__c records.
*
*              start() queries cm_finance__Quickbooks_Credentials__c records
*              whose Company is Active and in 'Successfully Synced' status.
*
*              execute() refreshes the token only if the last refresh was
*              more than 24 hours ago, then updates
*              cm_finance__Refresh_Token__c and
*              cm_finance__Refresh_Token_Last_Modified_Date__c on the
*              credential record.
*
*              Runs with batch size 1 to stay within callout limits.
*
* @usage
*   Database.executeBatch(new BatchRefreshTokenUpdate(), 1);
*   System.schedule('QB Token Refresh', cronExpr, new BatchRefreshTokenUpdate());
*/


/**
* @class       BatchSFQBCustomerSync
* @description Pushes pending cm_finance__Quickbooks_Customer__c records
*              from Salesforce to QuickBooks (create or update).
*
*              Queries records where Process_via_Batch__c = true and
*              Status__c != 'Failed' (configurable via whereClause CMT).
*
*              Each execute() chunk (batch size 1) maps the SF record to a
*              QBApiDataWrapper.Customer, calls WS_Quickbooks.createCustomer(),
*              then updates the SF record with the returned QB Id, SyncToken,
*              and status. On failure, stamps Status__c = 'Failed' with the
*              error message and clears Process_via_Batch__c.
*
* @usage
*   Database.executeBatch(new BatchSFQBCustomerSync(), 1);
*   System.schedule('SF→QB Customer Sync', cronExpr, new BatchSFQBCustomerSync());
*/


/**
* @class       BatchSFQBInvoiceSync
* @description Pushes pending cm_finance__Quickbooks_Invoice__c records
*              from Salesforce to QuickBooks (create or update), including
*              all related line items.
*
*              Resolves customer, product, class, and custom field lookups
*              before calling WS_Quickbooks.createInvoice().
*
*              On success, stamps Composite_Unique_Key__c, sets
*              Ignore_Webhook__c = true to suppress the round-trip webhook,
*              clears Process_via_Batch__c, and upserts line items.
*              Orphaned line items are deleted or archived based on the
*              deleteExtraLineItems setting.
*
*              If GenerateInvoicePDF is enabled, status is set to
*              'Invoice PDF Pending'.
*
* @usage
*   Database.executeBatch(new BatchSFQBInvoiceSync(), 1);
*   System.schedule('SF→QB Invoice Sync', cronExpr, new BatchSFQBInvoiceSync());
*/

/**
* @class       BatchSFQBSalesReceiptSync
* @description Pushes pending cm_finance__Quickbooks_Sales_Receipt__c records
*              from Salesforce to QuickBooks (create or update), including
*              all related line items.
*
*              Resolves customer, product,payment method,account, class, and custom field lookups
*              before calling WS_Quickbooks.createSalesReceipt().
*
*              On success, stamps Composite_Unique_Key__c, sets
*              Ignore_Webhook__c = true to suppress the round-trip webhook,
*              clears Process_via_Batch__c, and upserts line items.
*              Orphaned line items are deleted or archived based on the
*              deleteExtraLineItems setting.
*
*              If GenerateSalesReceiptPDF is enabled, status is set to
*              'Sales Receipt PDF Pending'.
*
* @usage
*   Database.executeBatch(new BatchSFQBSalesReceiptSync(), 1);
*   System.schedule('SF→QB Sales Receipt Sync', cronExpr, new BatchSFQBSalesReceiptSync());
*/


/**
* @class       BatchSFQBProductSync
* @description Pushes pending cm_finance__Quickbooks_Product__c records
*              from Salesforce to QuickBooks (create or update).
*
*              Resolves income, expense, and asset QB Account IDs from
*              cm_finance__Quickbooks_Account__c before calling
*              WS_Quickbooks.createProduct(). On success, stamps QB Id,
*              SyncToken, and clears Process_via_Batch__c.
*
* @usage
*   Database.executeBatch(new BatchSFQBProductSync(), 1);
*   System.schedule('SF→QB Product Sync', cronExpr, new BatchSFQBProductSync());
*/


/**
* @class       BatchSFQBVendorSync
* @description Pushes pending cm_finance__Quickbooks_Vendor__c records
*              from Salesforce to QuickBooks (create or update).
*
*              Maps all vendor fields (name, address, financial, 1099 flag,
*              account number) to a QBApiDataWrapper.Vendor before calling
*              WS_Quickbooks.createVendor(). Sets Ignore_Webhook__c = true
*              and clears Process_via_Batch__c on success.
*
* @usage
*   Database.executeBatch(new BatchSFQBVendorSync(), 1);
*   System.schedule('SF→QB Vendor Sync', cronExpr, new BatchSFQBVendorSync());
*/


/**
* @class       BatchQBSFAccountWebhook
* @description Processes inbound QuickBooks webhook notifications for Account
*              (Chart of Accounts) changes by fetching the updated records
*              from the QB API and upserting them into Salesforce.
*
*              Receives a list of QB Account IDs and the Realm ID from
*              Webhook_QBChangeHandler, then delegates each chunk to
*              QuickbooksIntegrationHelper.handleQBAccount().
*
*              Duplicate-job guard prevents concurrent runs of this batch.
*
* @usage
*   Database.executeBatch(new BatchQBSFAccountWebhook(lstQBIds, realmId), batchSize);
*/


/**
* @class       BatchQBSFCustomerWebhook
* @description Processes inbound QuickBooks webhook notifications for Customer
*              changes by fetching updated records from the QB API and
*              upserting them into Salesforce.
*
*              Delegates each chunk to
*              QuickbooksIntegrationHelper.handleQBCustomer().
*
* @usage
*   Database.executeBatch(new BatchQBSFCustomerWebhook(lstQBIds, realmId), batchSize);
*/


/**
* @class       BatchQBSFInvoiceWebhook
* @description Processes inbound QuickBooks webhook notifications for Invoice
*              changes by fetching updated records from the QB API and
*              upserting them into Salesforce.
*
*              Skips records where Ignore_Webhook__c = true (round-trip
*              suppression) and clears the flag afterward.
*              Delegates processing to
*              QuickbooksIntegrationHelper.handleQBInvoice().
*
* @usage
*   Database.executeBatch(new BatchQBSFInvoiceWebhook(lstQBIds, realmId), batchSize);
*/


/**
* @class       BatchQBSFSalesReceiptWebhook
* @description Processes inbound QuickBooks webhook notifications for Sales Receipt
*              changes by fetching updated records from the QB API and
*              upserting them into Salesforce.
*
*              Skips records where Ignore_Webhook__c = true (round-trip
*              suppression) and clears the flag afterward.
*              Delegates processing to
*              QuickbooksIntegrationHelper.handleQBSalesReceipt().
*
* @usage
*   Database.executeBatch(new BatchQBSFSalesReceiptWebhook(lstQBIds, realmId), batchSize);
*/

/**
* @class       BatchQBSFPaymentWebhook
* @description Processes inbound QuickBooks webhook notifications for Payment
*              changes by fetching updated records from the QB API and
*              upserting them into Salesforce.
*
*              Delegates each chunk to
*              QuickbooksIntegrationHelper.handleQBPayment().
*
* @usage
*   Database.executeBatch(new BatchQBSFPaymentWebhook(lstQBIds, realmId), batchSize);
*/

/**
* @class       BatchQBSFPaymentMethodWebhook
* @description Processes inbound QuickBooks webhook notifications for Payment Method
*              changes by fetching updated records from the QB API and
*              upserting them into Salesforce.
*
*              Delegates each chunk to
*              QuickbooksIntegrationHelper.handleQBPaymentMethod().
*
* @usage
*   Database.executeBatch(new BatchQBSFPaymentMethodWebhook(lstQBIds, realmId), batchSize);
*/

/**
* @class       BatchQBSFProductWebhook
* @description Processes inbound QuickBooks webhook notifications for
*              Item (Product/Service) changes by fetching updated records
*              from the QB API and upserting them into Salesforce.
*
*              Delegates each chunk to
*              QuickbooksIntegrationHelper.handleQBProduct().
*
* @usage
*   Database.executeBatch(new BatchQBSFProductWebhook(lstQBIds, realmId), batchSize);
*/


/**
* @class       BatchQBSFVendorWebhook
* @description Processes inbound QuickBooks webhook notifications for Vendor
*              changes by fetching updated records from the QB API and
*              upserting them into Salesforce.
*
*              Delegates each chunk to
*              QuickbooksIntegrationHelper.handleQBVendor().
*
* @usage
*   Database.executeBatch(new BatchQBSFVendorWebhook(lstQBIds, realmId), batchSize);
*/


/**
* @class       InvocableAutomateQBService
* @description Flow-invocable entry point for triggering QuickBooks sync
*              operations from Salesforce automation (Flows, Process Builder).
*
*              For single-record, non-Product requests, enqueues a
*              QueueableAutomateQBService job immediately.
*
*              For Product or multi-record requests, resolves field mappings
*              from cm_finance__Custom_Field_Mapping__c and processes the
*              operation synchronously within the invocable call.
*
*              Supported requestType values:
*                "Customer" → create/update QB Customer
*                "Invoice"  → create/update QB Invoice
*                "Product"  → create/update QB Item
*                "SalesReceipt"  → create/update QB Sales Receipt
*
* @usage
*   // From Flow: "Call QB Service" action, pass QBWrapper list
*   InvocableAutomateQBService.callQBService(listRecords);
*/


/**
* @class       InvocableCreateQBPayment
* @description Flow-invocable class that creates a Payment record in
*              QuickBooks for a given cm_finance__Payment_Transaction__c.
*
*              Also implements Queueable to perform the callout
*              asynchronously. The @InvocableMethod enqueues one job per
*              record Id passed in.
*
*              On success, stamps cm_finance__Quickbooks_Id__c,
*              cm_finance__Quickbooks_SyncToken__c, and
*              cm_finance__Composite_Unique_Key__c on the transaction, and
*              sets Ignore_Webhook__c = true.
*
*              If the QB response includes a deposit account and the
*              transaction has none linked, a new
*              cm_finance__Quickbooks_Account__c stub is created via
*              UpsertCustomRecords.upsertQBAccountRecords().
*
* @usage
*   // From Flow: "Create QB Payment" action, pass Payment Transaction Id list
*   InvocableCreateQBPayment.callCreateQBPaymentService(listIds);
*/


/**
 * @class       InvocableGetQBInvoicePDF
 * @description Flow-invocable and Queueable class responsible for retrieving
 *              Invoice or Sales Receipt PDF documents from QuickBooks and
 *              updating the corresponding Salesforce transaction record.
 *
 *              The class is invoked from Flow through the
 *              callQBInvoicePDFService() method. It accepts the Salesforce
 *              record ID, QuickBooks transaction ID, document number,
 *              QuickBooks Company record ID, and an optional transaction type.
 *
 *              The transaction type determines whether the class retrieves an
 *              Invoice PDF or a Sales Receipt PDF. If the transaction type is
 *              not provided, the transaction is treated as an Invoice.
 *
 *              The Queueable execution performs the following operations:
 *                1. Retrieves the active QuickBooks Company configuration.
 *                2. Loads the corresponding QuickBooks credentials.
 *                3. Refreshes the QuickBooks OAuth access token.
 *                4. Calls the appropriate QuickBooks PDF service:
 *                     - Invoice      -> WS_Quickbooks.getInvoicePDF()
 *                     - SalesReceipt -> WS_Quickbooks.getSalesReceiptPDF()
 *                5. Processes the PDF response and retrieves the generated
 *                   Content Document ID.
 *                6. Updates the corresponding Salesforce Invoice or Sales
 *                   Receipt record with the Content Document ID and marks the
 *                   PDF as generated.
 *                7. Updates the transaction status to
 *                   'Awaiting Salesforce Sync' when the PDF is successfully
 *                   retrieved.
 *
 *              If the QuickBooks Company, authorization, API response, PDF
 *              content, or Content Document ID is unavailable, the class
 *              updates the corresponding transaction record with a Failed
 *              status and an appropriate error description.
 *
 *              The class implements Database.AllowsCallouts because PDF
 *              retrieval is performed through an external HTTP callout.
 *
 * @usage
 *   // From Flow: 'Get QB Invoice PDF' action.
 *   // Pass the required transaction details using InvoiceDataWrapper.
 *   InvocableGetQBInvoicePDF.callQBInvoicePDFService(listRecords);
 *
 *   // Invoice: transactionType = 'Invoice' or leave it blank.
 *   // Sales Receipt: transactionType = 'SalesReceipt'.
 */


/**
* @class       QueueableAutomateQBService
* @description Queueable that executes the actual QB API callout for
*              InvocableAutomateQBService when a single-record,
*              non-Product request is submitted.
*
*              For requestType = 'Customer', calls createQBCustomer() and
*              then optionally chains a new job for requestType = 'Invoice'
*              so that a customer-then-invoice sequence can be executed in
*              two linked queue slots.
*
*              For requestType = 'Invoice', calls createQBInvoice()
*              directly.
*              For requestType = 'SalesReceipt', calls createQBSalesReceipt()
*              directly.
*
* @usage
*   System.enqueueJob(new QueueableAutomateQBService(oWrapper));
*/


/**
* @class       QueueableGetQBCustomFields
* @description Queueable that fetches custom field definitions from the
*              QuickBooks API and upserts them as
*              cm_finance__Quickbooks_Custom_Field__c records, keyed on
*              Composite_Unique_Key__c (DefinitionId::RealmId).
*
*              After the upsert, stamps the 'CustomFields' entry in
*              cm_finance__Last_Sync_Config__c on the Company record.
*
* @usage
*   QueueableGetQBCustomFields job = new QueueableGetQBCustomFields();
*   job.companyId = companyRealmId;
*   System.enqueueJob(job);
*/


/**
* @class       QueueableGetQBPaymentReceiptPDF
* @description Queueable that retrieves a Payment Receipt PDF from
*              QuickBooks and stores it as a ContentDocument linked to the
*              cm_finance__Payment_Transaction__c record.
*
*              First refreshes the OAuth access token, then calls
*              WS_Quickbooks.getPaymentReceiptPDF() and stamps the
*              content document Id and sync status on success.
*
* @usage
*   System.enqueueJob(
*     new QueueableGetQBPaymentReceiptPDF(
*       paymentRecordId, transactionNumber, paymentQBId, companyRecordId
*     )
*   );
*/


/**
* @class       QueueableQBPaymentDataFetcher
* @description Queueable that performs a single QB Batch API call to
*              refresh the Account, Customer, and Invoice records
*              associated with a given Payment Transaction, all in one
*              round-trip.
*
*              Builds a BatchItemRequest body combining up to three queries
*              (Account, Customer, Invoice), dispatches it via
*              WS_Quickbooks.batchQBAPI(), then parses the BatchItemResponse
*              and upserts each object type via UpsertCustomRecords helpers.
*
* @usage
*   System.enqueueJob(new QueueableQBPaymentDataFetcher(paymentRecordId));
*/


/**
* @class       Webhook_QBChangeHandler
* @description REST resource (URL: /services/apexrest/getUpdatesQB/) that
*              receives QuickBooks webhook event notifications via HTTP POST.
*
*              Parses the eventNotifications payload to extract entity names
*              and IDs for create/update/void operations, then dispatches
*              the appropriate webhook batch class:
*                Customer     → BatchQBSFCustomerWebhook
*                Account      → BatchQBSFAccountWebhook
*                Invoice      → BatchQBSFInvoiceWebhook
*                Payment      → BatchQBSFPaymentWebhook
*                Item         → BatchQBSFProductWebhook
*                Vendor       → BatchQBSFVendorWebhook
*                SalesReceipt → BatchQBSFSalesReceiptWebhook
*
*              Batch size is read from Quickbooks_Common_Settings__c.BatchSize.
*
* @usage
*   POST /services/apexrest/getUpdatesQB/
*   Body: QuickBooks webhook JSON payload
*/


/**
* @class       iQuickbooksService
* @description Interface that defines the contract for the QuickBooks API
*              service layer. Implemented by WS_Quickbooks.
*
*              Covers create/query methods for:
*                Account, Customer, Product, Invoice, CompanyInfo
*
*              Each create-or-query pair returns a QBResponseWrapper; each
*              createRecords method maps the raw API response into typed
*              Salesforce sObject lists.
*
* @usage
*   iQuickbooksService svc = new WS_Quickbooks();
*/


/**
* @class       QuickbooksIntegrationHelper
* @description Helper class that processes QB API responses for inbound
*              webhook batches (BatchQBSF*Webhook classes).
*
*              For each entity type, the corresponding handle* method:
*                1. Checks whether the incoming QB IDs already exist in
*                   Salesforce with Ignore_Webhook__c = true (round-trip
*                   suppression) and clears that flag.
*                2. For any remaining IDs, queries the QB API and upserts
*                   the returned records via WS_Quickbooks helpers.
*
*              Supported methods:
*                handleQBAccount(), handleQBCustomer(), handleQBInvoice(),
*                handleQBProduct(), handleQBVendor(), handleQBPayment(),
*                handleQBSalesReceipt()
*
* @usage
*   QuickbooksIntegrationHelper helper = new QuickbooksIntegrationHelper();
*   helper.lstRecordQBIds = lstQBIds;
*   helper.realmId        = realmId;
*   helper.handleQBInvoice();
*/

/**
* @class       AdminServices
* @description Global service class that provides administrative operations
*              for the QB Connector backend, called primarily from LWC
*              components in the Admin Console.
*
*              Key operations:
*                updateQBCredentials()        → writes Client ID, Client Secret,
*                                               and State URL to a named
*                                               cm_finance__Quickbooks_Credentials__c
*                                               record for Sandbox or Production.
*                updateEndpointsFromDefault() → copies all API endpoint fields
*                                               from the 'Default' credential
*                                               record to a company-specific one.
*                insertPaymentCredentials()   → upserts a
*                                               cm_finance__Gateway_Credential__c
*                                               record for a payment gateway.
*
* @usage
*   AdminServices.updateQBCredentials(oWrapper);
*   AdminServices.updateEndpointsFromDefault(oWrapper);
*/


/**
* @class       AdminSetupController
* @description AuraEnabled controller that backs the Admin Console LWC
*              components. Exposes company management, field mapping,
*              sync configuration, and permission-set assignment actions.
*
*              Key @AuraEnabled methods:
*                getConnectedCompanies()          → returns all active
*                                                   cm_finance__Quickbooks_Company__c
*                                                   records, default first.
*                updateConnectedCompanies()       → persists company record
*                                                   changes from the LWC.
*                getQBIdApiName()                 → returns the SF field path
*                                                   for cm_finance__Quickbooks_Id__c
*                                                   per sObject from field mapping.
*                Additional methods cover sync trigger, field mapping CRUD,
*                permission set assignment, and company default management.
*
* @usage
*   // LWC: @wire or imperative call
*   AdminSetupController.getConnectedCompanies();
*/


/**
* @class       BulkQBInvoicingService
* @description AuraEnabled service that supports the Bulk Invoice Upload LWC.
*
*              Key @AuraEnabled methods:
*                generateTemplate()   → builds a CSV template from the
*                                       BulkInvoiceFieldConfig CMT record,
*                                       returns it as a base64-encoded string
*                                       for browser download.
*                validateHeaders()    → compares uploaded CSV header row
*                                       against expected columns and returns
*                                       a list of validation errors.
*                processInvoices()    → parses validated CSV rows, creates
*                                       cm_finance__Quickbooks_Invoice__c and
*                                       related cm_finance__Quickbooks_Invoice_Item__c
*                                       records, and queues them for QB sync.
*
* @usage
*   // LWC: @wire or imperative call
*   BulkQBInvoicingService.generateTemplate();
*   BulkQBInvoicingService.validateHeaders(csvContent);
*/


/**
* @class       CMQBConnectorUtility
* @description Central utility class for the QB Connector. Provides shared
*              helpers consumed by all batch, trigger, queueable, and
*              controller classes.
*
*              Key responsibilities:
*                FLS control       → enforceFLSSetting() reads GuestUsers CMT,
*                                    returns false for guest/automated contexts.
*                Email validation  → validateEmail() regex check.
*                Date parsing      → parseDate() from YYYY-MM-DD string.
*                HTTP factory      → createHTTPRequest() builds HttpRequest with
*                                    auth headers and content types.
*                Record builders   → createCustomerRecord(), createInvoiceRecord(),
*                                    createProductRecord(), createVendorRecord(),
*                                    createQBAccountRecord(), createQBPaymentRecord()
*                                    stamp status/description/parent-Id fields.
*                Parent lookup     → populateParentLookup() resolves
*                                    cm_finance__Parent_Record_Id__c to the
*                                    correct relationship field via Schema describe.
*                Reverse mapping   → getReverseFieldMapping() fetches
*                                    cm_finance__Custom_Field_Mapping__c and
*                                    returns a keyed map for parent field writes.
*                Field sync        → qbUpdateParentValues() applies QB field values
*                                    to Salesforce parent records using JSON field maps.
*                Settings helpers  → getDefaultCompanyId(), getDefaultCompanyRealmId(),
*                                    getWhereClause(), getApiInstance(),
*                                    generateInvoicePDF(), generatePaymentLink(),
*                                    getPaymentReceipt(), deleteExtraLineItemsSetting().
*
* @usage
*   Boolean flsOn = CMQBConnectorUtility.enforceFLS;
*   cm_finance__Quickbooks_Customer__c stub =
*     CMQBConnectorUtility.createCustomerRecord(recordId, '', 'Failed', msg, null);
*/


/**
* @class       CustomFieldMapperHandler
* @description AuraEnabled controller for the Custom Field Mapper LWC that
*              lets admins map QB Invoice custom fields to Salesforce fields.
*
*              Key @AuraEnabled methods:
*                getCustomFields()   → returns the current QB custom field
*                                     mapping JSON, all available QB custom
*                                     field definitions from
*                                     cm_finance__Quickbooks_Custom_Field__c,
*                                     and the describe fields of the mapped
*                                     sObject (for Invoice mappings only).
*                saveMapping()       → persists the updated JSON mapping to
*                                     cm_finance__QB_Custom_Fields_Mapping__c
*                                     and optionally updates the reverse mapping.
*                getFields()         → (public) returns label/value pairs for all
*                                     fields on a given sObject via Schema describe.
*
* @usage
*   // LWC: imperative call
*   CustomFieldMapperHandler.getCustomFields(recordId);
*   CustomFieldMapperHandler.saveMapping(mappingJson, recordId, true);
*/


/**
* @class       PostInstallScript
* @description InstallHandler executed once on fresh package installation
*              (not upgrades or push upgrades).
*
*              Creates the default cm_finance__Quickbooks_Credentials__c
*              record ('Default') with all API endpoint templates and
*              placeholder client credentials, plus a full set of
*              cm_finance__Quickbooks_Common_Settings__c hierarchy custom
*              settings (BatchSize, GenerateInvoicePDF, ApiInstance, etc.).
*
*              Also provides helper factory methods used in tests:
*                createPaymentGateway()       → builds a test gateway record.
*                createGatewayCredentials()   → builds test credential records
*                                              for UnityFi, Fiserv, or Actum.
*
* @usage
*   // Invoked automatically by Salesforce on package install.
*   // Manual: new PostInstallScript().onInstall(ctx);
*/


/**
* @class       QBApiDataWrapper
* @description Global wrapper class that defines all request and response
*              data transfer objects (DTOs) for the QuickBooks API layer.
*
*              Inner classes:
*                QBRequestWrapper      → holds typed QB entity objects
*                                        (Customer, Vendor, Product, Invoice,Sales Receipt
*                                        Payment, Payment Method, Account, JournalLedger),
*                                        plus raw query string and batch body.
*                QBResponseWrapper     → holds API response, typed entity lists,
*                                        company/realm IDs, and CompanyInformation.
*                APIResponse           → isSuccess, statusCode, body, errorMessage.
*                Customer, Vendor,     → field-level wrappers with populateField()
*                Product, Invoice,     → for dynamic property assignment.
*                Account, Payment,
*                JournalLedger,Payment
*                Method, SalesReceipt
*                Address, Line,        → nested structural wrappers for addresses,
*                SalesItemLineDetail,  → invoice line items, tax detail, etc.
*                AccountReference,
*                CustomField
*                TaxRate, QBClass,     → lightweight read wrappers for
*                CompanyInformation,   → tax rates, class records, and company info.
*                CustomField
*
* @usage
*   QBApiDataWrapper.QBRequestWrapper req = new QBApiDataWrapper.QBRequestWrapper();
*   req.oCustomer = new QBApiDataWrapper.Customer();
*/


/**
* @class       QBAuthController
* @description Visualforce page controller that handles the OAuth 2.0
*              authorization code callback from QuickBooks.
*
*              On page load (QuickbooksAuthAction()):
*                - If redirected back with an auth code: sanitizes and
*                  validates it, exchanges it for access + refresh tokens
*                  via WS_Quickbooks.getAccessToken(), creates or upserts a
*                  cm_finance__Quickbooks_Credentials__c record for the
*                  Realm ID, fetches company info, and fires a QB_Auth__e
*                  platform event with isSuccess = true.
*                - If redirected with an error: fires QB_Auth__e with
*                  isSuccess = false and the sanitized error message.
*                - If not yet redirected: builds the QB OAuth authorization
*                  URL and sets authURL for the page to redirect to.
*
* @usage
*   // VF page action: action="{!QuickbooksAuthAction}"
*/


/**
* @class       QBAccount_TriggerHandler
* @description Trigger handler for cm_finance__Quickbooks_Account__c.
*
*              BEFORE_INSERT / BEFORE_UPDATE:
*                updateQBCompanyLookup() → if cm_finance__Quickbooks_Company__c
*                is null and a default company exists, stamps the default
*                company Id and Realm Id on the record.
*
* @usage
*   // Invoked from the Quickbooks_Account trigger.
*/



/**
* @class       QBCompany_TriggerHandler
* @description Trigger handler for cm_finance__Quickbooks_Company__c.
*
*              BEFORE_INSERT / BEFORE_UPDATE:
*                handleMultipleEmailCheck() → if Email__c contains commas,
*                splits it into Email__c (first) and Emails__c (full list).
*                handleDefaultCheck()       → blocks direct Default__c = true
*                changes (must go through Admin Console). Prevents multiple
*                defaults in one DML.
*
*              AFTER_INSERT / AFTER_UPDATE:
*                handleDefaultUpdate()      → when Default__c becomes true,
*                writes the record Id and Realm Id to DefaultCompanyRecordId
*                and DefaultCompanyId hierarchy custom settings.
*
*              AFTER_DELETE:
*                handleAfterDelete()        → if the deleted company was the
*                default, resets both settings to 'test'. Also deletes the
*                linked cm_finance__Quickbooks_Credentials__c record.
*
* @usage
*   // Invoked from the Quickbooks_Company trigger.
*/


/**
* @class       QBCustomer_TriggerHandler
* @description Trigger handler for cm_finance__Quickbooks_Customer__c.
*
*              BEFORE_INSERT / BEFORE_UPDATE:
*                handleMultipleEmailCheck() → normalizes comma-separated emails.
*                updateParentLookup()       → resolves Parent_Record_Id__c to
*                                             the correct SF lookup field via
*                                             CMQBConnectorUtility.populateParentLookup().
*                                             Also stamps the default company
*                                             if cm_finance__Quickbooks_Company__c
*                                             is null.
*
*              AFTER_INSERT / AFTER_UPDATE:
*                updateParentFields()       → when Status__c transitions to an
*                                             accepted status and a Parent_Record_Id__c
*                                             exists, reads the reverse field mapping
*                                             and writes QB Customer field values back
*                                             to the parent Salesforce record.
*
* @usage
*   // Invoked from the Quickbooks_Customer trigger.
*/


/**
* @class       QBInvoice_TriggerHandler
* @description Trigger handler for cm_finance__Quickbooks_Invoice__c.
*
*              BEFORE_INSERT / BEFORE_UPDATE:
*                handleMultipleEmailCheck()       → normalizes Billing_Email__c.
*                updateParentLookup()             → resolves Parent_Record_Id__c;
*                                                   stamps default company.
*
*              AFTER_INSERT / AFTER_UPDATE:
*                callPDFService()                 → if GenerateInvoicePDF is on
*                                                   and Status__c = 'Invoice PDF
*                                                   Pending', enqueues
*                                                   InvocableGetQBInvoicePDF.
*                callGeneratePaymentLinkService() → if GeneratePaymentLink is on
*                                                   and a new Content Document
*                                                   has been attached with a
*                                                   non-zero balance, calls
*                                                   InvocableGeneratePaymentLink.
*                updateParentFields()             → writes QB Invoice field values
*                                                   back to the Salesforce parent,
*                                                   including QB custom field values
*                                                   resolved via
*                                                   cm_finance__QB_Custom_Fields_Mapping__c.
*
*              BEFORE_DELETE:
*                deleteInvoiceLineItem()          → removes all related
*                                                   cm_finance__Quickbooks_Invoice_Item__c
*                                                   records.
*
* @usage
*   // Invoked from the Quickbooks_Invoice trigger.
*/


/**
* @class       QBSalesReceipt_TriggerHandler
* @description Trigger handler for cm_finance__Quickbooks_Sales_Receipt__c.
*
*              BEFORE_INSERT / BEFORE_UPDATE:
*                handleMultipleEmailCheck()       → normalizes Billing_Email__c.
*                updateParentLookup()             → resolves Parent_Record_Id__c;
*                                                   stamps default company.
*
*              AFTER_INSERT / AFTER_UPDATE:
*                callPDFService()                 → if GenerateSalesReceiptPDF is on
*                                                   and Status__c = 'Sales Receipt PDF
*                                                   Pending', enqueues
*                                                   InvocableGetQBSalesReceiptPDF.
*                updateParentFields()             → writes QB Sales Receipt field values
*                                                   back to the Salesforce parent,
*                                                   including QB custom field values
*                                                   resolved via
*                                                   cm_finance__QB_Custom_Fields_Mapping__c.
*
*              BEFORE_DELETE:
*                deleteSalesReceiptLineItem()      → removes all related
*                                                   cm_finance__Quickbooks_Sales_Receipt_Item__c
*                                                   records.
*
* @usage
*   // Invoked from the Quickbooks_Invoice trigger.
*/

/**
* @class       QBProduct_TriggerHandler
* @description Trigger handler for cm_finance__Quickbooks_Product__c.
*
*              BEFORE_INSERT / BEFORE_UPDATE:
*                updateParentLookup() → resolves Parent_Record_Id__c and
*                                       stamps default company if absent.
*
*              AFTER_INSERT / AFTER_UPDATE:
*                updateParentFields() → when Status__c is in AcceptedStatusFields
*                                       and Parent_Record_Id__c exists, reads
*                                       the reverse field mapping and writes QB
*                                       Product values back to the Salesforce parent.
*
* @usage
*   // Invoked from the Quickbooks_Product trigger.
*/


/**
* @class       QBVendor_TriggerHandler
* @description Trigger handler for cm_finance__Quickbooks_Vendor__c.
*
*              BEFORE_INSERT / BEFORE_UPDATE:
*                handleMultipleEmailCheck() → normalizes comma-separated emails.
*                updateParentLookup()       → resolves Parent_Record_Id__c and
*                                             stamps default company if absent.
*
*              AFTER_INSERT / AFTER_UPDATE:
*                updateParentFields()       → writes QB Vendor field values back
*                                             to the Salesforce parent record via
*                                             reverse field mapping.
*
* @usage
*   // Invoked from the Quickbooks_Vendor trigger.
*/


/**
* @class       QBInvoiceController
* @description AuraEnabled controller for the Create/View QB Invoice LWC
*              component.
*
*              Key @AuraEnabled methods:
*                checkExistingInvoice()           → checks whether a QB invoice
*                                                   already exists for a given
*                                                   Salesforce parent record and
*                                                   company.
*                getTermsOptions()                → returns the SalesTermMapping
*                                                   JSON from CMT (Net30/60/90
*                                                   day values).
*                fetchRelatedCustomerFromParent() → resolves the QB Customer
*                                                   linked to a parent record
*                                                   using the configured
*                                                   Source_Invoice_Customer_Lookup_field.
*                prefillInvoice()                 → reads field mappings and
*                                                   returns pre-filled invoice
*                                                   field values from the parent
*                                                   record for the LWC form.
*                createInvoice()                  → creates or updates a
*                                                   cm_finance__Quickbooks_Invoice__c
*                                                   and its line items, then
*                                                   queues it for QB sync via
*                                                   InvocableAutomateQBService.
*                syncInvoicePDF()                 → manually enqueues PDF
*                                                   generation for an existing
*                                                   invoice.
*
* @usage
*   // LWC: @wire or imperative call
*   QBInvoiceController.checkExistingInvoice(recordId, companyRecordId);
*/


/**
* @class       QuickbooksController
* @description AuraEnabled controller that serves the QB data LWC components
*              (customer sync, product sync, vendor sync, and company selector).
*
*              Key @AuraEnabled methods:
*                getListCompanies()              → returns active QB companies
*                                                  ordered by Default__c DESC.
*                syncCustomerFromQuickbooks()    → fetches QB Customer records
*                                                  by QB Id and upserts them
*                                                  into Salesforce.
*                syncProductFromQuickbooks()     → same pattern for Products.
*                syncVendorFromQuickbooks()      → same pattern for Vendors.
*                getQBCustomers()               → returns cm_finance__Quickbooks_Customer__c
*                                                  records for a given parent
*                                                  record and company.
*                Additional methods support QB data search and linking
*                workflows from LWC.
*
* @usage
*   // LWC: @wire or imperative call
*   QuickbooksController.getListCompanies();
*/

**
* @class       QBSalesReceiptController
* @description AuraEnabled controller for the Create/View QB Sales Receipt LWC
*              component.
*
*              Key @AuraEnabled methods:
*                checkExistingSalesReceipt()        → checks whether a QB Sales
*                                                     Receipt already exists for
*                                                     a given Salesforce parent
*                                                     record and company.
*                fetchRelatedCustomerFromParent()  → resolves the QB Customer
*                                                     linked to a parent record
*                                                     using the configured Sales
*                                                     Receipt customer lookup
*                                                     field mapping.
*                checkExistingQBCustomer()         → checks the configured
*                                                     QuickBooks Customer lookup
*                                                     field on the source record
*                                                     and returns the related QB
*                                                     Customer wrapper.
*                createCustomerWrapper()            → retrieves a QuickBooks
*                                                     Customer record and converts
*                                                     it into a Customer wrapper
*                                                     for use by the LWC.
*                getQuickbooksClasses()             → returns active QuickBooks
*                                                     Classes for the selected
*                                                     QuickBooks company when class
*                                                     functionality is enabled.
*                sendPrefilledSalesReceipt()       → reads configured field
*                                                     mappings from the source
*                                                     record and returns a pre-filled
*                                                     Sales Receipt wrapper.
*                                                     Also resolves configured
*                                                     custom fields, products,
*                                                     payment method, and deposit
*                                                     account values.
*                createEmptySalesReceiptWrapper()  → creates an empty Sales
*                                                     Receipt wrapper with default
*                                                     values and populates
*                                                     configured custom fields.
*                createSalesReceiptInQuickbooks()  → 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.
*                populateParentRecordIdOnSalesReceipt()
*                                                   → updates the Parent Record
*                                                     ID on an existing QuickBooks
*                                                     Sales Receipt record.
*
*              The controller 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.
*
* @usage
*   // LWC: @wire or imperative call
*   QBSalesReceiptController.checkExistingSalesReceipt(recordId, companyRecordId);
*/

/**
* @class       UpsertCustomRecords
* @description Without-sharing utility class that centralises all upsert
*              operations for QB Connector objects, each using the
*              Composite_Unique_Key__c external ID field.
*
*              Methods (one per QB object type):
*                upsertQBCompanyRecords()      → cm_finance__Quickbooks_Company__c
*                upsertQBCredentials()         → cm_finance__Quickbooks_Credentials__c
*                                                (upserts on Name, FLS off)
*                upsertQBCustomerRecords()     → cm_finance__Quickbooks_Customer__c
*                upsertQBVendorRecords()       → cm_finance__Quickbooks_Vendor__c
*                upsertQBAccountRecords()      → cm_finance__Quickbooks_Account__c
*                upsertQBProductRecords()      → cm_finance__Quickbooks_Product__c
*                upsertQBInvoiceRecords()      → cm_finance__Quickbooks_Invoice__c
*                upsertQBSalesReceiptRecords() → cm_finance__Quickbooks_Sales_Receipt__c
*                upsertQBInvoiceItemRecords()  → cm_finance__Quickbooks_Sales_Receipt_Item__c
*                upsertQBSalesReceiptItemRecords() → cm_finance__Quickbooks_Invoice_Item__c
*                upsertQBPaymentRecords()      → cm_finance__Payment_Transaction__c
*                updateCredentials()           → updates Refresh_Token__c and
*                                                Refresh_Token_Last_Modified_Date__c,
*                                                FLS off.
*
*              Declared without sharing so credential upserts succeed
*              regardless of the running user's record access.
*
* @usage
*   UpsertCustomRecords.upsertQBAccountRecords(lstAccounts);
*   UpsertCustomRecords.updateCredentials(oCredentials, newRefreshToken);
*/


/**
* @class       UtilityService
* @description Shared utility class for the Payment Guru module.
*
*              Key responsibilities:
*                enforceFLSSetting()      → reads GuestUsers from
*                                           cm_finance__Payment_Guru_Common_Setting__mdt
*                                           and returns false for guest contexts.
*                gatewaySetting()         → returns cm_finance__Gateway_Credential__c
*                                           for a given gateway name.
*                getCommonSettings()      → fetches a single
*                                           cm_finance__Payment_Guru_Common_Setting__mdt
*                                           record by DeveloperName.
*                getDefaultRelationshipName() → resolves the relationship field
*                                              name between two sObjects via Schema
*                                              describe (shared with CMQBConnectorUtility).
*                decryptionMethod()       → AES-256 decryption using the
*                                           encryption key from Gateway Credentials.
*                mapPaymentCommonSettings → lazy-loaded static map of all
*                                           Payment Guru CMT settings.
*
* @usage
*   Boolean enforceFLS = UtilityService.enforceFLSSetting();
*   cm_finance__Gateway_Credential__c creds = UtilityService.gatewaySetting('Actum');
*/


/**
* @class       WS_Quickbooks
* @description Core QuickBooks API service class. Implements iQuickbooksService.
*              All callout logic lives here — every batch, queueable, and
*              controller class calls this class to communicate with the QB API.
*
*              Authentication:
*                getAccessToken()      → exchanges OAuth auth code for tokens
*                                        (initial connect flow).
*                refreshAccessToken()  → refreshes an existing access token
*                                        using the stored refresh token.
*
*              Batch API:
*                batchQBAPI()          → sends a single Batch API request
*                                        with multiple sub-queries.
*
*              CRUD + Query per entity (Account, Customer, Vendor, Product,Payment Method
*              Invoice,Sales Receipt, Payment, TaxRate, Class, CustomField, CompanyInfo):
*                create<Entity>()      → POST to QB API; maps request wrapper
*                                        to JSON body.
*                query<Entity>()       → GET/query to QB API with SOQL-like
*                                        query string.
*                query<Entity>Count()  → queries totalCount only.
*                create<Entity>Records() → maps QB API response JSON into
*                                          typed Salesforce sObject lists.
*
*              PDF retrieval:
*                getInvoicePDF()         → fetches invoice PDF bytes, stores as
*                                          ContentDocument linked to the invoice.
*                getSalesReceiptPDF()    → fetches sales receipt PDF bytes, stores as
*                                          ContentDocument linked to the invoice.
*                getPaymentReceiptPDF()  → same pattern for payment receipts.
*
*              Company:
*                getCompanyInfo()        → fetches QB company info and maps to
*                                          cm_finance__Quickbooks_Company__c.
*                getCustomFields()       → fetches QB invoice custom field
*                                          definitions.
*
*              Static fields sCompanyId and oCredentials must be set before
*              calling any API method.
*
* @usage
*   WS_Quickbooks.sCompanyId = realmId;
*   WS_Quickbooks.oCredentials = cm_finance__Quickbooks_Credentials__c.getInstance(realmId);
*   WS_Quickbooks svc = new WS_Quickbooks();
*   QBApiDataWrapper.QBResponseWrapper resp = svc.queryCustomer(oRequestWrapper);
*/