Skip to main content

Scenario: Take a sales order from entry to invoice

Goal. An order arrives from your web shop, EDI hub or CRM. You create it in D365 as a sales order for the customer, confirm it, ship it (post the packing slip), invoice it and read back where it stands. Every posting happens exactly once, even when your system sends a step again after a timeout.

When to use. Any order-to-cash integration in which the external system drives the D365 sales documents. Each posting step can be checked first with a validation-only preview. For goods that come back, continue with Sales return to credit note; to collect the invoice, see Customer payment and settlement; to bill something that is not on an order, see Free text invoice. To take back a packing slip posted in error before it is invoiced, see Cancel a posted sales packing slip. When orders arrive as files, put Inbound file to posted document in front of this flow.

Prerequisites​

  1. Security. The D365 user your Entra application is mapped to needs these privileges (see Security model):

    • MGCLatticeSalesOrderCaptureServiceMaintain for createSalesOrder and createSalesOrderBatch;
    • MGCLatticeAccountsReceivableServiceMaintain for confirmSalesOrder, postSalesPackingSlip, invoiceSalesOrder and their batch variants;
    • MGCLatticeAccountsReceivableServiceView (or MGCLatticePostingPreviewExecute) for getSalesOrderStatus.

    MGCLatticeIntegrationOperatorRole and MGCLatticeIntegrationAdministratorRole hold all of them, and their Lattice entity duties cover the LatticeCustomerPackingSlipJournalLines read-back. The standard entities this page also uses (SalesOrderHeadersV2, SalesOrderLines, SalesOrderConfirmationLines, SalesInvoiceHeaders, SalesInvoiceLines) are Microsoft entities: they need the standard D365 privileges.

  2. D365 configuration in the legal entity:

    • number sequences for sales orders, confirmations, packing slips and invoices (Accounts receivable > Setup > Accounts receivable parameters, Number sequences tab);
    • the customer (USMF US-001) with a customer posting profile, and the released item (USMF D0001);
    • on-hand inventory where the line ships from (in USMF, site 1 / warehouse 11 stocks D0001), unless the item allows negative physical inventory;
    • a sales price: on the line, from a trade agreement or from the item's base price (see Sales prices and price inquiry).
  3. Lattice setup. A valid license: an expired or invalid license refuses the create and posting operations with LicenseNotValid. To send your own order keys instead of D365 order numbers, set up an external system (System administration > Setup > Lattice > External systems) as described in Cross-reference keys. If an administrator turned on the inbound order policy (System administration > Setup > Lattice > Inbound order policy parameters, off by default), orders created through the integration can be held for price tolerance or confirmed automatically; see Integration operations.

Steps​

Every service call is POST https://<your-environment>/api/services/MGCLatticeAccountsReceivableServiceGroup/<service>/<operation> with an OAuth 2.0 client-credentials token (Trial and day-one guide) and the request in _request. Give each step its own new RequestId and store it with the step: a resend of the same step must carry the same RequestId, and a RequestId already used by another operation is refused.

  1. Create the order. Choose one of two ways.

    a. One call: createSalesOrder (service MGCLatticeSalesOrderCaptureService, path /api/services/MGCLatticeAccountsReceivableServiceGroup/MGCLatticeSalesOrderCaptureService/createSalesOrder). The order is created with the standard D365 defaulting and validation, in one transaction.

    {
    "_request": {
    "RequestId": "5d0c2b4e-7f1a-4c3d-9e8b-1a2f3c4d5e60",
    "LegalEntityId": "USMF",
    "UserContext": "Admin",
    "CustAccount": "US-001",
    "CustomerRef": "PO-88231",
    "Lines": [
    { "ItemId": "D0001", "Qty": 10, "UnitPrice": 100, "SiteId": "1", "WarehouseId": "11" }
    ]
    }
    }

    DocumentId and PostedDocumentId are the new order number (for example 001367); the message reads Sales order 001367 created for customer US-001 with 1 line(s). A UnitPrice of 0 or none takes the trade agreement or default price. This operation has no preview.

    b. The standard entities. POST https://<your-environment>/data/SalesOrderHeadersV2 with the header, then POST .../data/SalesOrderLines with each line:

    { "dataAreaId": "USMF", "OrderingCustomerAccountNumber": "US-001", "InvoiceCustomerAccountNumber": "US-001",
    "CurrencyCode": "USD", "PaymentTermsName": "Net30", "SalesOrderName": "Web order PO-88231" }
    { "dataAreaId": "USMF", "SalesOrderNumber": "001367", "ItemNumber": "D0001", "OrderedSalesQuantity": 10,
    "SalesPrice": 100, "ShippingSiteId": "1", "ShippingWarehouseId": "11" }

    The header create returns SalesOrderNumber. These two calls are not covered by a Lattice RequestId.

    What the next steps need: the order number and, to post single lines, each line's number. Read it with GET .../data/SalesOrderLines?cross-company=true&$filter=dataAreaId eq 'USMF' and SalesOrderNumber eq '001367'&$select=LineNumber,ItemNumber,OrderedSalesQuantity. A request line may also name its order line by ItemId alone when only one line of that item still has a quantity to post.

  2. Preview the posting (optional). Send the confirmation of step 3 with "PreviewMode": true. The service checks the request, finds the order and resolves the lines and quantities, then answers Success true, PreviewMode true, PreviewApplied true, PreviewFidelity 1 and the message Preview (ValidationOnly): sales order confirmation request for 001367 is valid. Nothing was posted. No document is posted, no number is used and the RequestId stays free, so you can post with it next. postSalesPackingSlip and invoiceSalesOrder preview the same way. A preview does not run the posting itself: a stock or posting-setup problem only shows on the real post.

  3. Confirm. confirmSalesOrder (service MGCLatticeSalesOrderPostingService, path /api/services/MGCLatticeAccountsReceivableServiceGroup/MGCLatticeSalesOrderPostingService/confirmSalesOrder). Without Lines the whole order is confirmed. With Lines, only the selected lines are confirmed, each for its Qty; this confirms 5 of the 10 ordered:

    {
    "_request": {
    "RequestId": "8a6f1e2d-3b4c-4d5e-a6f7-081920a1b2c3",
    "LegalEntityId": "USMF",
    "UserContext": "Admin",
    "SalesOrderId": "001367",
    "PostingDate": "2026-10-07",
    "Lines": [ { "LineNum": 1, "ItemId": "D0001", "Qty": 5 } ]
    }
    }

    DocumentId is the order and PostedDocumentId the confirmation number: Sales order 001367 confirmed with confirmation CCF-00000128.

  4. Post the packing slip. postSalesPackingSlip, same service and request shape. Without Lines, everything not yet delivered ships; with Lines, the selected lines and quantities. PostedDocumentId is the packing slip number: Packing slip SPK-00002040 posted for sales order 001367.

  5. Invoice. invoiceSalesOrder, same service and request shape. Without Lines, an order with packing slips is invoiced for the delivered quantity not yet invoiced (an order without packing slips for its ordered quantity). PostedDocumentId is the invoice number: Invoice CIV-00000744 posted for sales order 001367. Keep it: it is the invoice a customer payment is settled against.

  6. Read the status. getSalesOrderStatus on MGCLatticeSalesOrderPostingService:

    { "_request": { "RequestId": "c41d7e90-25f6-4b8a-9c3d-6e7f8a9b0c1d", "LegalEntityId": "USMF",
    "UserContext": "Admin", "DocumentId": "001367" } }

    The answer carries DocumentStatus (the sales status, for example Invoiced) and StatusDetails, a list of Name/Value pairs: SalesStatus, CustAccount, and ConfirmDate, PackingSlipDate and InvoiceDate once those documents exist. The query writes nothing.

Many orders at once. createSalesOrderBatch, confirmSalesOrderBatch, postSalesPackingSlipBatch and invoiceSalesOrderBatch take { "_requests": [ ... ] }, each item a full request with its own RequestId. Every item runs on its own: the answer has TotalCount, SuccessCount, FailureCount, Status (3 all succeeded, 4 all failed, 6 PartialSuccess) and one Results entry per item with its ItemIndex, Success, ErrorCode, DocumentId and PostedDocumentId.

How to verify​

  • The response: Success true, Status 3, DocumentId the order, and PostedDocumentId the confirmation, packing slip or invoice number. An operation that posts nothing fails instead of answering success.
  • The status query: after the confirmation StatusDetails has ConfirmDate (a confirmation does not change DocumentStatus, which stays Backorder); after the packing slip it has PackingSlipDate, and DocumentStatus is Delivered once everything is delivered; after the invoice it has InvoiceDate and Invoiced.
  • OData read-back (add cross-company=true and filter on dataAreaId eq 'USMF'): SalesOrderConfirmationLines (ConfirmedSalesQuantity by SalesOrderNumber and LineNumber), LatticeCustomerPackingSlipJournalLines (Quantity by SalesOrderNumber and LineNumber), SalesInvoiceHeaders (InvoiceNumber by SalesOrderNumber) and SalesInvoiceLines (InvoicedQuantity by InvoiceNumber).
  • In D365: Sales and marketing > Sales orders > All sales orders, open the order; its document status shows the last document posted, and the confirmation, packing slip and invoice journals open from the order's action pane.
  • Call log: System administration > Inquiries > Lattice > Services > Service call log has one row per call with its status, error code and posted document (previews are flagged).
  • Business events, when activated: MGCLatticeSalesOrderConfirmedBusinessEvent, MGCLatticeSalesPackingSlipPostedBusinessEvent and MGCLatticeSalesOrderInvoicePostedBusinessEvent (events).

When it goes wrong​

ErrorCodeTypical cause in this flowWhat to do
ValidationFailed (1)No RequestId or unknown LegalEntityId; no order named (SalesOrderId is required.); a request line that matches no order line, names a line twice, has an ItemId that differs from the line, or asks for more than is left (Request line 1 asks to post 12.00 of line 1.00 of order 001367, but only 10.00 is left to post.); all lines unselected; createSalesOrder without CustAccount, lines, ItemId or Qty; an external key that does not resolveCorrect the request and send it again. Nothing was posted.
DocumentNotFound (2)The order is not in the request's legal entity (Sales order 001999 was not found.); createSalesOrder for a customer that does not existCheck the number and LegalEntityId.
BusinessRule (4)D365 refused the posting (for example not enough on-hand for the packing slip, a missing number sequence or posting profile; D365's reasons are in Errors), or there was nothing to post (Invoice of sales order 001367 posted nothing: no Invoice journal was created. Check that the order has open lines to post.)Fix the data or setup in D365, then resend. A failed posting is rolled back completely.
TransientRuntime (6)A runtime fault; IsRetryable is trueResend the same request with the same RequestId, with backoff.
DuplicateRequest (7)The RequestId was already used for another operation (for example the confirmation's RequestId sent to postSalesPackingSlip)Use a new RequestId for each step.
RequestInProgress (8)The same RequestId is still runningRetry later; it is not run twice.
RequestNeedsReview (9)An earlier attempt with this RequestId stopped without a resultCheck whether it posted (status query, journals); the idempotency reconciliation sweep resolves the stranded request.
PreviewUnsupported (10)PreviewMode true sent to createSalesOrderCreate the order without preview.
LicenseNotValid (12)The Lattice license is expired or invalid; previews still runRenew the license, then resend.

Retries are safe. Sending a successful request again with the same RequestId posts nothing: it answers Success true with the original DocumentId and PostedDocumentId, the message Duplicate request detected. Original request processed at ... and, in InformationalMessages, This is a cached response from a previous execution of the same RequestId. A failed request is not remembered, so the same RequestId runs again once you have fixed the cause. Inside a batch, an item that repeats an earlier item's RequestId replays that item. HTTP 401, 403 and 404 come from authentication, security or an undeployed service group before Lattice runs; see Trial and day-one guide.

Reference​