The Book Order call flow

This page traces a booking end to end, with the actual class and method names. Use it to debug, extend, or audit the integration.

Sequence overview

The card and controller

The ccNetSuiteSyncCall component resolves its mode from the page it's on. An Order page means booking.

On load, it calls CcNsSyncCallController.getPreBookSummary(orderId), a cacheable read that returns the checklist counts, contract presence, amendment flag, and prior-run state. It gates on Order.Status = Activated.

Clicking the button calls startBookOrder(orderId). From then on, the card polls getRunStatus(runId) once a second until the run completes or fails. The JSON viewer calls getPayloadPreview(orderId), which returns the same builder output, serialized for display.

The card's NetSuite link follows what actually landed. RC stands for Revenue Cloud, and RC records mirror the Salesforce Revenue Cloud order graph into NetSuite. An RC booking links the contract. A booking whose Order landed as a native document links that document instead, captioned with its type, for example "Open Sales Order in NetSuite".

Guards before anything runs

CcNsSyncRunService.start(orderId) runs these steps in order, so nothing half-happens.

  1. In-flight guard. An existing Queued or Processing run throws "A booking run for this order is already in progress." A stale run, over 10 minutes old, gets superseded instead.
  2. Credential fail-fast. If configuration is missing, CcNsCredentials.load() throws before any run record exists.
  3. Synchronous payload build. CcNsPayloadBuilder.build(orderId) runs right here, so the async worker never mixes SOQL or DML with its callout. The stored payload is what gets sent, unchanged.
  4. Run insert. A CC_NetSuite_Sync_Run__c record is inserted with Status__c='Queued' and the serialized Payload_Json__c.
  5. Enqueue. System.enqueueJob(new CcNsBookOrderQueueable(runId)) starts the async worker.

Building and storing the payload

CcNsPayloadBuilder.build does the query work, with CcNsOrderGraphSelector running all the SOQL. It queries the order with its account, contract, legal entity, and contact lookups. It queries the order's lines, filtering amendments down to changed lines only. It queries the order's billing schedules using ReferenceEntityId, since that field is polymorphic. Then it queries their groups, all member schedules of those groups, needed to derive each group's start date, end date, and item, and the periods. CcNsMappingService.apply then attaches each node's mapping extras.

A missing Contract throws here, before any record or call exists. So does a missing Order Booking call. No run row is created either way.

See Anatomy of the payload for how the resulting document breaks down.

The account's addresses aren't part of the customer section. CcNsCustomNodeService emits the active address objects' rows as a carrier entry in customNodes, keyed __sublist:Customer and aimed at the same customer the payload lands on. An address is assembled from mapping rows the same way as any other configurable object.

The queueable and the signed call

CcNsBookOrderQueueable does callouts before any DML, throughout. It attaches a finalizer first, so an unhandled exception marks the run Failed and the card never polls forever. It reads the stored payload and sends it with CcNsRestClient.call(method, url, payloadJson), using the endpoint the Order Booking call is configured with. By default that's the connection's own RESTlet URL with POST and the book action, or it's whatever endpoint row the call names. The URL, method, and action are stamped on the run before the callout goes out, so a failure always says where it went. Here's the default URL, built from credentials:

https://<account>.restlets.api.netsuite.com/app/site/hosting/restlet.nl
     ?script=customscript_cc_control_rl&deploy=customdeploy_cc_control_rl

It signs with OAuth 1.0a HMAC-SHA256. The script and deploy query parameters get folded into the signature base string alongside the oauth parameters. Leaving them out causes the classic 401 error. The timeout is 120 seconds, with one immediate retry on HTTP 429. See Security model for the full signing detail.

On the response, HTTP 300 or higher fails the run, and the body is preserved. Otherwise, results merge into any prior legs. That merge, mergeResults, unions the id maps, keeps the max of per-section counts across idempotent re-runs, and appends errors. If NetSuite answered partial:true with a continueFrom section, that's its governance resume (see Inside the RESTlet). The queueable stamps continueFrom into the payload, marks the run Processing, and enqueues one more leg of itself.

The run's final status is a three-way call. It isn't a stand-in for HTTP success.

StatusWhen
FailedThe call itself errored, or NetSuite reported ok:true but no order actually landed
Completed with warningsThe order landed, but one or more lines had no matching NetSuite item. The queueable counts every error containing no NetSuite item for product and appends the tally to the run's error log
CompletedEverything landed clean

Only Failed counts as a failure. CcNsSyncRunService.DONE_STATUSES treats Completed and Completed with warnings alike as done, so a warned run stops the card's poll and counts toward the numbers on the contract.

The sync card's pill reads "Booked with warnings" for the middle state. Re-booking after you fix the item, in wizard step 7 or directly in NetSuite, resolves it. The Order's own NetSuiteSyncStatus__c still reads Completed either way, since the order document itself landed clean. Run Retention purges warned runs on the same schedule as clean ones.

Writebacks

CcNsWritebackService.apply(payload, result.ids) walks the payload to route each id, without guessing from id shapes. It stamps NetSuiteRecordId__c and NetSuiteSyncStatus__c='Completed' onto the Account, Contacts, Products that were previously unmatched, Contract, Order, Order lines (id only), and Billing Schedule Groups.

Updates are partial-success, via Database.update(..., false). A non-writable field warns in the run's error log instead of failing the booking. Schedules and periods carry no writeback fields, by design.

When the Order node targets a native transaction, the Order's id becomes the document's id. Its lines get no id at all, because a sublist line has no record id of its own.

The line ids matter later. Order Line Sync updates only lines that carry one. So on an order booked as a native transaction, the card doesn't offer Sync Order Lines.

The chained engine run

On success, the queueable chains one background job. If any document rows match contracts, that's the files leg, which itself chains onward to CcNsEngineQueueable. Otherwise CcNsEngineQueueable runs directly. No org setting gates this chain: the engine leg always runs when there's a contract to roll.

Inside it, two things always run: headless Update POBs for the contract, and the POB mirror back to Salesforce (see Writebacks & the POB mirror for the mechanics). The switchable part is the plans leg, headless Update Revenue Plans. It only runs while the Continuous Revenue Engine is on, in both the Salesforce sync settings and NetSuite's Continuous Revenue Settings.

Its results merge into the same run's Result_Json__c. That's why the card's numbers fill in about 20 to 30 seconds after Booked first appears.

The run records the outcome in the engine field:

ValueMeaning
"done" or "done with warnings"Plans were built. plansBuilt is present
"disabled"The Salesforce switch is off
"disabled in NetSuite"NetSuite answered the plans Suitelet with DISABLED
"skipped"Nothing to roll. engineSkipReason explains why: no contract id came back, the Order node landed as a native transaction, or no RC order line landed

contractsRolled and pobsMirrored are always present. plansBuilt is omitted whenever the engine didn't run. A booking NetSuite reports as landing no order is marked Failed, not green.