In the previous blog post, I’ve demonstrated how to extend standard APIs by using them as a template for a new custom API that contains the customizations you want to do. While that approach works for most APIs, there are some APIs that are more complicated. I’m talking about APIs for sales and purchase documents. These APIs are special because they are not based on the standard tables Sales Header, Sales Line, etc.
I take the sales documents as examples here, but as you can imagine this also applies to purchase documents.
API salesOrders
The API page for Sales Orders is based on the table “Sales Order Entity Buffer”. This table holds a copy of all records in the table “Sales Header” of type Order. This is managed from codeunit “Graph Mgt – Sales Order Buffer”. I’d recommend to take a look at this codeunit to read how this is managed. Similarly, the API page for Sales Order Lines is based on table “Sales Invoice Line Aggregate”. The word Invoice in this name is not a typo, this is really the name. The same table is also used for the Sales Invoice Line API. But unlike the “Sales Order Buffer” table, the lines are not copied over. Instead, the API pages use them as temporary source tables and fill them on demand. See below for a full list of all buffer tables used by the sales and purchase document APIs.
Here is a high-level dataflow diagram for the salesOrders API.
As you can see, the salesOrders API reads data from the table “Sales Order Entity Buffer”. It does not insert or update data in that table. At least not directly. The API page is based on that table, so when you insert a new order or update an existing one, the API page uses the same table to store the values. But right before it inserts or updates it in the database, it calls the codeunit “Graph Mgt – Sales Order Buffer” to propagate the values to the real “Sales Header” table. This happens in the OnInsert / OnModify / OnDelete triggers in the API page. See below the code of these triggers and notice the exit(false) which cancels the operation to the source table of the page.
trigger OnDeleteRecord(): Boolean begin GraphMgtSalesOrderBuffer.PropagateOnDelete(Rec); exit(false); end; trigger OnInsertRecord(BelowxRec: Boolean): Boolean begin CheckSellToCustomerSpecified(); GraphMgtSalesOrderBuffer.PropagateOnInsert(Rec, TempFieldBuffer); SetDates(); UpdateDiscount(); SetCalculatedFields(); exit(false); end; trigger OnModifyRecord(): Boolean begin if xRec.Id <> Id then Error(CannotChangeIDErr); GraphMgtSalesOrderBuffer.PropagateOnModify(Rec, TempFieldBuffer); UpdateDiscount(); SetCalculatedFields(); exit(false); end;
The codeunit “Graph Mgt – Sales Order Buffer” transfers the values from “Sales Order Entity Buffer” to the “Sales Header” table. Below is the code of the PropagateOnInsert. The Modify works similar, but it gets the record first before updating it or inserts if it doesn’t exist.
procedure PropagateOnInsert(var SalesOrderEntityBuffer: Record "Sales Order Entity Buffer"; var TempFieldBuffer: Record "Field Buffer" temporary) var SalesHeader: Record "Sales Header"; TypeHelper: Codeunit "Type Helper"; TargetRecordRef: RecordRef; DocTypeFieldRef: FieldRef; begin if SalesOrderEntityBuffer.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled) then exit; TargetRecordRef.Open(DATABASE::"Sales Header"); DocTypeFieldRef := TargetRecordRef.Field(SalesHeader.FieldNo("Document Type")); DocTypeFieldRef.Value(SalesHeader."Document Type"::Order); TypeHelper.TransferFieldsWithValidate(TempFieldBuffer, SalesOrderEntityBuffer, TargetRecordRef); // SetTable does not transfer globals, which will affect the logic in OnInsert trigger. We have to insert here and modify latter. TargetRecordRef.Insert(true); SalesHeader.Get(TargetRecordRef.RecordId()); SalesHeader.CopySellToAddressToBillToAddress; SalesHeader.SetDefaultPaymentServices; SalesHeader.Modify(true); SalesOrderEntityBuffer."No." := SalesHeader."No."; SalesOrderEntityBuffer.Get(SalesOrderEntityBuffer."No."); end;
As you can see, the field values are actually transferred with the generic function TransferFieldsWithValidate in codeunit TypeHelper. This function uses the TempFieldBuffer as the list of field values that needs to be transferred. It is important to understand that this table has a field Order which is automatically filled by the function RegisterFieldSet in the API page. In short, without copying a lot of source code here, the fields are transferred in the order in which they appear in the API page. Furthermore, the fields are mapped on their field ID only, not by name.
When a record is inserted or updated in the “Sales Header” table, then the very same codeunit synchronizes the data to the table “Sales Order Entity Buffer”. By the way, the same pattern exists for the Sales Quote.
API salesInvoices
The API page for Sales Invoices on the other hand, is based on the table “Sales Invoice Entity Aggregate”. From the API point of view, the data flow is exactly the same as with sales orders. However, there is a difference from a functional perspective. The table “Sales Invoice Entity Aggregate” does not only contain unposted sales invoices from table “Sales Header”, it also contains posted invoices from table “Sales Invoice Header”. And that makes a huge difference of course. The idea behind this is to have one endpoint that returns both unposted and posted sales invoices. On top of that, the posted sales invoice will keep the same id. As a result, the sales invoice will keep the same resource URL throughout the whole lifecycle from unposted to posted document.
Here is the high-level data flow diagram for the salesInvoices API:
Status field
There is one thing about the salesOrders and salesInvoices APIs that can be confusing. And that is the status field. We all know the status field in the table “Sales Header”. But the value Open in the sales APIs has a different meaning compared to the same value on the sales document in the Business Central UI. In the API it actually means that the sales document is Released. In other words, status Open in the API means that the sales document is not open in terms of Business Central UI. 🥴 Instead, the status Draft in the APIs is equivalent to Open in the sales document. My educated guess for this difference is that Microsoft tried to align the APIs with common status values as used by other solutions.
Sales Document status
Sales orders and invoices have a status based on the enum “Sales Document Status” which has these values:
Value | Description |
---|---|
Open | The sales document can be modified |
Released | The sales document is ready to be shipped and cannot be modified |
Pending Approval | The sales document is waiting to be approved before being released |
Pending Prepayment | The sales document is awaiting upfront payment from the customer before being released |
salesOrder API status
The salesOrders API has a status field, which is based on the enum “Sales Order Entity Buffer Status”. This enum has these values:
Value | Description |
---|---|
Draft | The sales order status is Open |
In Review | The sales order status is Pending Approval |
Open | The sales order status is Released or Pending Prepayment |
salesInvoices API status
The salesInvoices API has yet another status field, based on the enum “Invoice Entity Aggregate Status” with these values:
Value | Description |
---|---|
” “ | No idea… |
Draft | Unposted sales invoice with status Open |
In Review | Unposted sales invoice with status Pending Approval |
Open | If unposted: status Released or Pending Prepayment. If posted: waiting for payment |
Paid | Posted invoice is closed |
Canceled | Posted invoice is canceled and a corrective credit memo has been created for the posted invoice |
Corrective | Posted invoice is canceled and a new sales invoice has been created |
For the canceled and corrected invoice, see https://docs.microsoft.com/en-us/dynamics365/business-central/sales-how-correct-cancel-sales-invoice
Sales Lines
Now we have that out of our way, let’s look at the sales lines before we dive into modifying the API. While the sales header values are actually duplicated in the buffer tables, the sales lines are not. Instead, all APIs for sales lines are using the same table: “Sales Invoice Line Aggregate” as a temporary source table. The lines are loaded into the temporary table by calling the function LoadLines in codeunit “Sales Invoice Aggregator”. It is basically a TransferFields, followed by some extra calls to update line amounts.
Inserting or updating sales lines is done in a similar way as the sales headers. The codeunit “Sales Invoice Aggregator” has functions to propagate the field values from the temporary record to the table “Sales Line”. Again, the function TransferFieldsWithValidate from codeunit “Type Helper” is used. No magic stuff in here, just the same story again.
Adding a custom field to the table and API
Let’s now look at how we can customize those sales APIs. We start with the salesInvoices API as an example. The scenario is to add a custom field Webshop Reference Id to the table “Sales Header” and to the API.
The tableextension for the “Sales Header” table looks like:
tableextension 50100 SalesHeaderAJK extends "Sales Header" { fields { field(50100; WebshopReferenceIdAJK; Text[30]) { Caption = 'Webshop Reference Id'; } } }
And of course we want to have this field on the posted sales invoice as well:
tableextension 50101 SalesInvoiceHeaderAJK extends "Sales Invoice Header" { fields { field(50100; WebshopReferenceIdAJK; Text[30]) { Caption = 'Webshop Reference Id'; } } }
We start with copying the standard API as explained in the previous blog post. In order to add the field to the copied API page, it needs to be added to the table “Sales Invoice Entity Aggregate” as well. So you can guess the other tableextension:
tableextension 50102 SalesInvoiceEntityAggregateAJK extends "Sales Invoice Entity Aggregate" { fields { field(50100; WebshopReferenceIdAJK; Text[30]) { Caption = 'Webshop Reference Id'; } } }
Now we can add the field to the API page. Don’t forget to include the call to RegisterFieldSet! And if the order is important, then make sure to place it in the correct position as well. Otherwise you can just add it to the end, or before the first part.
field(webshopReferenceId; Rec.WebshopReferenceIdAJK) { Caption = 'Webshop Reference Id'; trigger OnValidate() begin RegisterFieldSet(Rec.FieldNo(WebshopReferenceIdAJK)); end; }
Now this field can be used to create a new sales invoice.

So far so good. This works for both headers and lines in the same way. To summarize: in order to add a new field to the API:
- Add the field with a tableextension to Sales Header or Sales Line
- Optionally add the field with a tableextension to the posted document tables
- Create a custom API page based on the standard API
- Add the field with a tableextension to buffer table used by the API page (see table below)
- Add the field to the custom API page and call RegisterFieldSet from the OnValidate
The buffer tables for all sales and purchase document APIs:
API | Table |
---|---|
salesQuotes | Sales Quote Entity Buffer |
salesQuoteLines | Sales Invoice Line Aggregate |
salesOrders | Sales Order Entity Buffer |
salesOrderLines | Sales Invoice Line Aggregate |
salesInvoices | Sales Invoice Entity Aggregate |
salesInvoiceLines | Sales Invoice Line Aggregate |
salesCreditMemos | Sales Cr. Memo Entity Buffer |
salesCreditMemoLines | Sales Invoice Line Aggregate |
purchaseInvoices | Purch. Inv. Entity Aggregate |
purchaseInvoiceLines | Purch. Inv. Line Aggregate |
Adding an existing field to the API
The second scenario is less obvious. Not all fields in the sales document tables are available in the API. How can we add these fields to the API? Let’s say we want to add the fields “Blanket Order No.” and “Blanket Order Line No.” to the salesOrderLines API. I didn’t take these fields randomly, I’ve got this question a couple of times and it was actually the reason for writing this blog post. 😊
Let’s start by creating the custom salesOrderLines API. That’s just the same procedure as described in the previous blog post. But the salesOrderLines API is linked to the salesOrders API (the header). We need to create a custom API page for the header as well. Follow the same procedure to create the custom salesOrders API and then change the salesOrderLines part to point to the custom salesOrderLines API page. The only required change is the name of the subpage.
part(salesOrderLines; SalesOrderLinesAPIAJK) { Caption = 'Lines'; EntityName = 'salesOrderLine'; EntitySetName = 'salesOrderLines'; SubPageLink = "Document Id" = Field(Id); }
Now we are ready to add the fields to the custom salesOrderLines API. Since these fields are already available in the “Sales Line” table we don’t have to create a tableextension for the “Sales Line” table. But unfortunately, they are not available in the table “Sales Invoice Line Aggregate”. And that is a BIG miss from Microsoft. Apparently, only the fields that are used in the standard APIs have been added. The same counts for the “Sales Header” table and the corresponding buffer tables. I’m not sure why. In an attempt to save some space because the data is being duplicated? That doesn’t make a big difference I would say, and for the sales line it does not make a difference at all because that table is only used as a temporary table. I think that the scenario of extending the standard API with fields from the base app just wasn’t considered. So, if Microsoft is reading this, can you please make sure that the API buffer tables contain all fields from the document tables? That would be very helpful!
The only option we have now is to add the fields to the buffer table in our own number range and then somehow copy the values to the target table. So let’s start with the tableextension:
tableextension 50103 SalesInvoiceLineAggregateAJK extends "Sales Invoice Line Aggregate" { fields { field(50100; BlanketOrderNoAJK; Code[20]) { AccessByPermission = TableData "Sales Shipment Header" = R; Caption = 'Blanket Order No.'; TableRelation = "Sales Header"."No." WHERE("Document Type" = CONST("Blanket Order")); } field(50101; BlanketOrderLineNoAJK; Integer) { AccessByPermission = TableData "Sales Shipment Header" = R; Caption = 'Blanket Order Line No.'; TableRelation = "Sales Line"."Line No." WHERE("Document Type" = CONST("Blanket Order"), "Document No." = FIELD(BlanketOrderNoAJK)); } } }
And these fields need to be added to the custom salesOrderLines API page as well. The position is not really important because we will be validating those fields at the end.
Next question is how to get the values from these fields to the target fields in the base table. First thing to look at would be events. But unfortunately, there are no events at all in the codeunits “Sales Invoice Aggregator” or the other codeunits used by the API pages. Bummer…. Now we need to write custom code for something that could have been solved out-of-the-box. Let me give you two options, so you have something to choose.
Option 1: With table extensions
This option is added after the post was published. 😀 But it is a more elegant way and gives you full control over the order in which the fields are validated. But… there is a disadvantage as you will see.
The idea is to create a table extension for the “Sales Line” table with the same fields we added to the aggregate table used by the API. When the custom fields are changed, then we change the target field. And when the target field is changed, the API fields are kept in sync. With that, we don’t require any further coding in the API page. The tableextension will look like this:
tableextension 50104 SalesLineAJK extends "Sales Line" { fields { modify("Blanket Order No.") { trigger OnAfterValidate() begin BlanketOrderNoAJK := "Blanket Order No."; end; } modify("Blanket Order Line No.") { trigger OnAfterValidate() begin BlanketOrderLineNoAJK := "Blanket Order Line No."; end; } field(50100; BlanketOrderNoAJK; Code[20]) { AccessByPermission = TableData "Sales Shipment Header" = R; Caption = 'Blanket Order No.'; TableRelation = "Sales Header"."No." WHERE("Document Type" = CONST("Blanket Order")); trigger OnValidate() begin Validate("Blanket Order No.", BlanketOrderNoAJK); end; } field(50101; BlanketOrderLineNoAJK; Integer) { AccessByPermission = TableData "Sales Shipment Header" = R; Caption = 'Blanket Order Line No.'; TableRelation = "Sales Line"."Line No." WHERE("Document Type" = CONST("Blanket Order"), "Document No." = FIELD(BlanketOrderNoAJK)); trigger OnValidate() begin Validate("Blanket Order Line No.", BlanketOrderLineNoAJK); end; } } }
On the API page we just add the custom fields like we did in the previous example with a new custom field:
field(blanketOrderNo; Rec.BlanketOrderNoAJK) { Caption = 'Blanket Order No.'; trigger OnValidate() begin RegisterFieldSet(Rec.FieldNo(BlanketOrderNoAJK)); end; } field(blanketOrderLineNo; Rec.BlanketOrderLineNoAJK) { Caption = 'Blanket Order Line No.'; trigger OnValidate() begin RegisterFieldSet(Rec.FieldNo(BlanketOrderLineNoAJK)); end; }
Because the fields are registered in the field buffer through the function RegisterFieldSet, we have full control over the order in which they are validated.
The only disadvantage of this approach is that the custom fields need to be in sync with the target fields. That means that you need to write upgrade code for the initial synchronization, because the custom fields will be empty after installation. And if the original fields could be changed by means of a direct value assignment instead of a validate, then the values are out of sync as well. But it would be easy to solve that with an event subscriber codeunit of course.
If the order of the fields are important, then this approach is the best option. If the order of the fields are not important and you are not sure about keeping the values in sync, then the next option might be the better choice.
Option 2: With a manual event subscriber
The second way I could think of is by using event subscribers to the events of the “Sales Line” table. This should be a manual subscriber codeunit to avoid unnecessary event hits. The advantage of this method is that you don’t have to keep values in sync between the original field and the API field. And you don’t have to write any upgrade code for the initial synchronization. The disadvantage is that you don’t have control over the exact order. As you can see, the fields don’t call the function RegisterFieldSet because they will be synced at the end of the process, right before the insert or modify.
field(blanketOrderNo; Rec.BlanketOrderNoAJK) { Caption = 'Blanket Order No.'; } field(blanketOrderLineNo; Rec.BlanketOrderLineNoAJK) { Caption = 'Blanket Order Line No.'; }
Here is an example of the code for this manual event subscriber codeunit:
codeunit 50100 SalesLineAPISubscriberAJK { EventSubscriberInstance = Manual; var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; procedure SetSalesLineInvoiceLineAggregate(Rec: Record "Sales Invoice Line Aggregate") begin SalesInvoiceLineAggregate := Rec; end; [EventSubscriber(ObjectType::Table, Database::"Sales Line", 'OnBeforeInsertEvent', '', false, false)] local procedure OnBeforeInsertSalesLine(var Rec: Record "Sales Line") begin if Rec.IsTemporary then exit; UpdateSalesLine(Rec); end; [EventSubscriber(ObjectType::Table, Database::"Sales Line", 'OnBeforeModifyEvent', '', false, false)] local procedure OnBeforeModifySalesLine(var Rec: Record "Sales Line") begin if Rec.IsTemporary then exit; UpdateSalesLine(Rec); end; local procedure UpdateSalesLine(var SalesLine: Record "Sales Line") begin SalesLine.Validate("Blanket Order No.", SalesInvoiceLineAggregate.BlanketOrderNoAJK); SalesLine.Validate("Blanket Order Line No.", SalesInvoiceLineAggregate.BlanketOrderLineNoAJK); end; }
Don’t forget to test for Rec.IsTemporary! During the validation of fields, temporary lines are being created, and they will hit this event subscriber as well, resulting in a potential recursive overflow.
As you can see, the UpdateSalesLine function just validates the fields that I need. This could be more generic by using a similar field buffer table, but I decided to keep the code readable.
No we need to bind this codeunit to the right procedure. That’s done in the custom salesOrderLines API page:
var SalesLineAPISubscriber: Codeunit SalesLineAPISubscriberAJK; trigger OnInsertRecord(BelowxRec: Boolean): Boolean var GraphMgtSalesOrderBuffer: Codeunit "Graph Mgt - Sales Order Buffer"; begin SalesLineAPISubscriber.SetSalesLineInvoiceLineAggregate(Rec); BindSubscription(SalesLineAPISubscriber); GraphMgtSalesOrderBuffer.PropagateInsertLine(Rec, TempFieldBuffer); UnbindSubscription(SalesLineAPISubscriber); SetCustomFields(); end; trigger OnModifyRecord(): Boolean var GraphMgtSalesOrderBuffer: Codeunit "Graph Mgt - Sales Order Buffer"; begin SalesLineAPISubscriber.SetSalesLineInvoiceLineAggregate(Rec); BindSubscription(SalesLineAPISubscriber); GraphMgtSalesOrderBuffer.PropagateModifyLine(Rec, TempFieldBuffer); UnbindSubscription(SalesLineAPISubscriber); SetCustomFields(); end;
There is one thing left that we can’t solve with these event subscribers. The propagate functions return the buffer record which has been updated with all values from the sales line. It does that by first clearing all values and use TransferFields. As a result, our custom fields have been cleared, so we need to put them back. For that I have created the function SetCustomFieldValues. The call is already in the code above. The function looks like this:
local procedure SetCustomFields() var SalesLine: Record "Sales Line"; begin SalesLine.GetBySystemId(Rec.SystemId); Rec.BlanketOrderNoAJK := SalesLine."Blanket Order No."; Rec.BlanketOrderLineNoAJK := SalesLine."Blanket Order Line No."; end;
The final step that we need to do is to get the values into the buffer table when the record is read with a HTTP GET. For that, we can use the same function SetCustomFields. Just call it from the OnAfterGetRecord trigger.
trigger OnAfterGetRecord() begin SetCustomFields(); end;
This works the same for the salesOrder and salesInvoice APIs (the headers). Of course you could also create event subscribers to keep the field values synchronized. Below is the test result that proves all of this is actually working. 😊

I hope that this long post helps you in creating custom APIs for the sales documents. Maybe I should do a video about this as well, just to demonstrate all the steps above. Let me know in the comments! And if you found another way to do it, then please feel free to share.
Happy coding!
Hi AJK,
Thanks for the great post. Just to be sure, when you talk about extending a standard API then you actually take a copy of the standard API and turn it into a custom API. We can’t extend a standard API via a pageextension. Is this correct? Do you think this will ever be possible or is this technically unfeasible?
I’m still not convinced about using the buffer tables (such as “Sales Invoice Entity Aggregate”). What is the big advantage of using this table and not immediately the “Sales Header”? Can we see some kind of log in Business Central by using this buffer table?
Thanks you in advance!
Thanks for the great post.
Very useful and informative post
Thanks for the great post.
I actually like the 2. approach with the event subscriber, cause u avoid a table extension on sales lines.
But it has a downsid cause u can’t filter these fields like …salesOrderLines?$filter=BlanketOrderNoAJK eq ‘123’.
Hi AJK, for a customer I am building a webhook mechanism to inform another system that new invoices have been created. The issue there is that when a invoice is created through posting from an order, the SystemId in the Sales Invoice record can be use to ask the record from the salesInvoices API. But, if the Sales Invoice is created and posted manually, I seem to have to use the Draft Invoice SystemId from the Sales Invoice, otherwise the salesInvoice API cannot find the resource. I would expect to use SystemId in both cases.
Not sure about it. A sales order can result in multiple sales invoices, so why would it use the system id of the sales order for the created sales invoice? I expect the sales invoice to have it’s own unique system id. They are accessible through different APIs endpoints anyway.
But a draft sales invoice results in one posted sales invoice and only accessible by one API endpoint. In that case the system id is the same.
Does that make sense?
Hi AJK, cannot login to your site, above message is mine:-) Is this behaviour by design or a bug in the Aggregate codeunit, what do you think?
Hi AJ, no not yet, I will try to elaborate:-) A new manual sales invoice gets a SystemId when it is first created in de Sales Header table. If we post that manual invoice it will inherit the SystemId from it’s old sales header record but that is registered into the field Draft Invoice System Id of the Sales Invoice table, it also gets a SystemId. If I want to query this specific posted invoice through the salesInvoice API, I need Draft Invoice System ID to do that and cannot do it with the SystemId if I use the standard salesInvoice API.
I’m trying to extend the salesInvoice API to with a custom field. The custom field already existed as an extension on the Sales Invoice Header table, so I am just extending the aggregate and the API page. I guess I’ll need a one-off upgrade script to populate the old data. It’s a shame the Sales Invoice Entity Aggregate table isn’t like a view in SQL rather than a concrete table, to avoid duplicating data and having things out of sync.
I guess I got lucky adding yourReference to the API, as that does appear to already been in the aggregate table for purchase invoices…
I’m also “looking forward” to creating a purchase credit memo API, as that seems to be totally absent in the standard API. (Which suggests I should be doing something else..)
When extending the aggregate/buffer tables with a field that already exists in the base tables, using the original field number seem to make it work without needing extra triggers. But I suspect doing that would be leaving it open to trouble later on.
I also noticed some inconsistencies, e,g. Sales Cr. Memo Entity Buffer has the field “Requested Delivery Date”, but Sales Invoice Entity Aggregate does not. Feels a bit ad-hoc!
I’ve copied the Sales Order API but for whatever reason my copied API doesn’t pass the documentID to the lines so when i try to pass a nested JSON i’m just getting the error : You must specify a document id to get the lines. Do you have an Idea why? I tried to debug it but i can’t for the life of me find where the table SalesOrderEntityBuffer passes the value to “Sales Invoice Line Aggregate”
Setting the custom field in the OnAfterGetRecord trigger didn’t work for me. I had to use the OnAfterGetCurrRecord trigger instead.
Hi! It is an awesome post. I found here almost everything I was looking for, since we needed to insert both custom fields in the SalesOrder Header and SalesOrderLines.
But I have a question: we have implemented another approach that worked fairly well inside Business Central, but one associate needs to get this custom data (both from Sales Order Header and Sales Order Lines) from Power Bi; our implementation failed at providing the (extended fields) Sales Order Lines.
Before changing everything to test your solution, do you think this will work querying from Power Bi?
Thanks a lot.
Regards,
Diego
Hi AJ, I need to add sales comments to the sales order lines. Problem is the No. filter needed on the Sales Comment Line table is the sales order number which is not available on the “Sales Invoice Line Aggregate” table. On there you only have access to the Document Id (Guid) and not the Document No. field/ What are possible options to get around this?
Heinrich
Hi AJ and thanks for a great post.
But I have a question. I made a customized version of the purchaseOrders-api where I have added some fields.
In the function SetCustomFields() I get an error when I try to get the standard Purchase Header by SystemId.
PurchaseHeader.GetBySystemId(Rec.SystemId);
{
“error”: {
“code”: “Internal_RecordNotFound”,
“message”: “The Purchase Header does not exist. Identification fields and values: {2946DFB9-946D-EE11-8DEF-000D3AB750D1} CorrelationId: dfd905dd-95d8-45f9-9648-138c50293146.”
}
}
If I do a standard Get() it works – PurchaseHeader.Get(PurchaseHeader.”Document Type”::Order, Rec.”No.”);
Shouldn’t the SystemId, after the transferfield, be the same for Buffer table and standard?
Or am I doing something wrong?