A sample provider extension implementing connection with Test Provider
may have the following code structure:
Provider ID RWU
enum adding your provider ID.Entity ID RWU
enum adding your entities.Operation ID RWU
enum adding your operations.For all enumerator extensions and extension values please use numbers from the extension number line to avoid conflicts with other extensions. You can find the number range in the app.json
file, node idRanges
.
Create an extension of Provider ID RWU
enum and add your API provider (or providers) there:
enumextension 50000 "Test Provider ID RWU" extends "Provider ID RWU"
{
value(50000; "TestProvider")
{
Caption = 'Test Provider';
}
}
You can then select the value in the Provider ID field when creating the provider. This ID will be used for calling your provider's operations from code.
Create an extension of Entity ID RWU
enum and add your planned entities there:
enumextension 50001 "Test Entity ID RWU" extends "Entity ID RWU"
{
value(50000; "TestProvider_Contact")
{
Caption = 'Contact (Test Provider)';
}
value(50001; "TestProvider_Session")
{
Caption = 'Session (Test Provider)';
}
}
You can then select the value in the Entity ID field when creating the entity. This ID will be used for selecting your entity in code.
Create an extension of Operation ID RWU
enum and add your planned API operations there:
enumextension 50002 "Test Operation ID RWU" extends "Operation ID RWU"
{
value(50000; "TestProvider_CreateContact")
{
Caption = 'Create Contact (Test Provider)';
}
value(50001; "TestProvider_DeleteContact")
{
Caption = 'Delete Contact (Test Provider)';
}
value(50002; "TestProvider_UpdateContact")
{
Caption = 'Update Contact (Test Provider)';
}
value(50003; "TestProvider_RefreshToken")
{
Caption = 'Refresh Token (Test Provider)';
}
}
You can then select the value in the Operation ID field when creating the operation. This ID will be used for calling your operation from code.
The main integration codeunit should contain functions for all operations with the external API. These functions will be called in page actions, job queue codeunits, etc. Below you can find an example of such codeunit and its functions:
codeunit 50000 "Test Provider Management"
{
ApiScriptRWU: Codeunit "API Script RWU";
MyDialog: Dialog;
ProcessMsg: Label 'Processing...';
EntityID: Enum "Entity ID RWU";
ProviderID: Enum "Provider ID RWU";
OperationID: Enum "Operation ID RWU";
Note:
_UpdateTimestamp
parameter is optional and should be there only when the function will be used during record synchronization.
procedure RefreshContact(_Contact: Record Contact; _UpdateTimestamp: Boolean; _ShowMessage: Boolean)
var
UpdateMsg: Label 'Contact %1 updated.', Comment = '%1 - Contact';
begin
if _ShowMessage and GuiAllowed then
MyDialog.Open(ProcessMsg);
MappingId := APIScriptRWU.MAPPING(ProviderID::TestProvider,EntityID::TestProvider_Contact, _Contact);
//For BC 18 and newer use record SystemId instead
//MappingId := APIScriptRWU.MAPPING(ProviderID::TestProvider,EntityID::TestProvider_Contact, _Contact.SystemId);
//Connection not found, create the record
if MappingId = '' then begin
APIScriptRWU.INIT(OperationID::TestProvider_CreateContact);
APIScriptRWU.ENDPOINT(ProviderID::TestProvider,OperationID::TestProvider_CreateContact);
APIScriptRWU.ADD_VALUE('/',_Contact);
APIScriptRWU.EXECUTE();
end
//Connection found, update the record data
else begin
APIScriptRWU.INIT(OperationID::TestProvider_UpdateContact);
APIScriptRWU.ENDPOINT(ProviderID::TestProvider,OperationID::TestProvider_UpdateContact);
APIScriptRWU.ADD_VARIABLE('','ContactId',MappingId);
APIScriptRWU.ADD_VALUE('/',_Contact);
APIScriptRWU.EXECUTE();
end;
//(Optional) If the timestamp should be updated, do it and commit the data
if _UpdateTimestamp then begin
APIScriptRWU.SYNC_DATETIME(ProviderID::TestProvider,EntityID::TestProvider_Contact,DT2Date(_Contact.SystemModifiedAt),DT2Time(_Contact.SystemModifiedAt));
Commit();
end;
if _ShowMessage and GuiAllowed then begin
MyDialog.Close();
Message(UpdateMsg, _Contact."No.");
end;
end;
procedure UploadNewContacts(_ShowMessage: Boolean)
var
Contact: Record Contact;
LastTimeStamp: DateTime;
UpdateMsg: Label '%1 contacts updated.', Comment = '%1 - Contacts count';
begin
if _ShowMessage and GuiAllowed then
MyDialog.Open(ProcessMsg);
LastTimeStamp := APIScriptRWU.SYNC_DATETIME(ProviderID::TestProvider,EntityID::TestProvider_Contact,0D,0T);
Contact.SetCurrentKey(SystemModifiedAt);
if LastTimeStamp > 0DT then
Contact.SetFilter(SystemModifiedAt,'>%1',LastTimeStamp);
//For all records modified after the saved timestamp run the update
if Contact.FindSet() then
repeat
RefreshContact(Contact,true,_ShowMessage);
until Contact.Next() = 0;
if _ShowMessage and GuiAllowed then begin
MyDialog.Close();
Message(UpdateMsg, Contact.Count());
end;
end;
procedure DeleteContact(_Contact: Record Contact; _ShowMessage: Boolean)
var
ErrorMsg: Label 'Contact %1 does not exist in external system.', Comment = '%1 - Contact';
UpdateMsg: Label 'Contact %1 deleted.', Comment = '%1 - Contact';
begin
if _ShowMessage and GuiAllowed then
MyDialog.Open(ProcessMsg);
MappingId := APIScriptRWU.MAPPING(ProviderID::TestProvider,EntityID::TestProvider_Contact,_Contact);
//For BC 18 and newer use record SystemId instead
//MappingId := APIScriptRWU.MAPPING(ProviderID::TestProvider,EntityID::TestProvider_Contact, _Contact.SystemId);
IF MappingId = '' THEN
Error(ErrorMsg, _Contact."No.");
APIScriptRWU.INIT(OperationID::TestProvider_DeleteContact);
APIScriptRWU.ENDPOINT(ProviderID::TestProvider,OperationID::TestProvider_DeleteContact);
APIScriptRWU.ADD_VARIABLE('','ContactId',MappingId);
APIScriptRWU.EXECUTE();
ApiScriptRWU.DELETE_MAPPING(ProviderID::TestProvider,EntityID::TestProvider_Contact,_Contact);
if _ShowMessage and GuiAllowed then begin
MyDialog.Close();
Message(UpdateMsg, _Contact."No.");
end;
end;
Create an event handling CU, if you for example want to delete something in an external system when a record in Business Central is deleted, or create a new record there during Business Central record creation.
codeunit 50001 "Test Provider Event Handler"
{
SingleInstance = true;
[EventSubscriber(ObjectType::Table, 5050, 'OnAfterDeleteEvent', '', false, false)]
local procedure OnAfterDeleteContact(var Rec: Record "Contact"; RunTrigger: Boolean)
var
Contact: Record Contact;
TestProviderMgt: Codeunit "Test Provider Management";
begin
if Rec.IsTemporary then
exit;
//Delete the contact in an external system - and do not show any messages
TestProviderMgt.DeleteContact(Rec, false);
end;
}
Use this pattern, if you want to start some job like new contacts synchronization via job queue:
codeunit 50002 "Test Provider - Upload Contacts"
{
var
TestProviderMgt: Codeunit "Test Provider Management";
trigger OnRun()
begin
Clear(TestProviderMgt);
TestProviderMgt.UploadNewContacts(false);
end;
}
Use this template, if you want to add some API operation as an action on a page:
pageextension 50000 "Contact Card Test" extends "Contact Card"
{
layout
{
addlast(Processing)
{
group("Integrations")
{
Caption = 'Integrations';
action("CreateInExternalSystem")
{
Caption = 'Export contact';
Image = AddContacts;
ApplicationArea = All;
ToolTip = 'Create or update the contact in an external system';
trigger OnAction()
var
TestProviderMgt: Codeunit "Test Provider Management";
begin
TestProviderMgt.RefreshContact(Rec,false,true);
end;
}
}
}
}
}
Use this template for a more advanced filter during record synchronization – for example filter only invoices of customers, that have already been exported to an external system (i.e. they have a healthy connection in RESTwithUS).
query 50000 "Test Provider Customer Invoices"
{
Permissions = TableData "Cust. Ledger Entry" = r,
TableData "Detailed Cust. Ledg. Entry" = r;
elements
{
dataitem(API_Provider; "API Provider RWU")
{
column(ProviderID; "Provider ID Enum")
{
}
dataitem(API_Endpoint; "API Endpoint RWU")
{
DataItemLink = "API Provider Id" = API_Provider.ID;
column(EndpointID; "Endpoint ID Enum")
{
}
dataitem(CustomerMapping; "API Mapping RWU")
{
DataItemLink = "Schema ID" = API_Endpoint.ID;
dataitem(Cust_Ledger_Entry; "Cust. Ledger Entry")
{
DataItemLink = "Customer No." = CustomerMapping."Key Code";
column(Description; Description)
{
}
dataitem(DetailedInvoice; "Detailed Cust. Ledg. Entry")
{
DataItemLink = "Cust. Ledger Entry No." = Cust_Ledger_Entry."Entry No.";
DataItemTableFilter = "Entry Type" = CONST ("Initial Entry"), "Document Type" = CONST (Invoice);
column(Detailed_Entry_No; "Entry No.")
{
}
column(Entry_Type; "Entry Type")
{
}
column(Cust_Ledger_Entry_No; "Cust. Ledger Entry No.")
{
}
column(Customer_No; "Customer No.")
{
}
column(Document_Type; "Document Type")
{
}
column(Document_No; "Document No.")
{
}
column(Amount; Amount)
{
}
column(Currency_Code; "Currency Code")
{
}
column(Posting_Date; "Posting Date")
{
}
}
}
}
}
}
}
}
You can then use the query for new invoice synchronization like this:
procedure UploadNewInvoices(_FilterCustomerNo: Code[20]; _ShowMessage: Boolean)
var
DetailedCustLedgEntry: Record "Detailed Cust. Ledg. Entry";
TestProviderCustomerInvoices: Query "Test Provider Customer Invoices";
LastDetailedEntryNo: Integer;
EntityID: Enum "Entity ID RWU";
ProviderID: Enum "Provider ID RWU";
SyncFinishedMsg: Label 'Synchronization finished.';
ProcessMsg: 'Exporting entry no. #1#####';
begin
if _ShowMessage and GuiAllowed then
MyDialog.Open(ProcessMsg);
//Filter the query for a provider, entity and only new entries
Clear(TestProviderCustomerInvoices);
TestProviderCustomerInvoices.SetRange(ProviderID, ProviderID::TestProvider);
TestProviderCustomerInvoices.SetRange(EndpointID, EntityID::TestProvider_Customer);
LastDetailedEntryNo := APIScriptRWU.LAST_AUTO_INCREMENT(ProviderID::TestProvider, EntityID::TestProvider_Invoice);
TestProviderCustomerInvoices.SetFilter(Detailed_Entry_No, '>%1', LastDetailedEntryNo);
if _FilterCustomerNo <> '' then
TestProviderCustomerInvoices.SetRange(Customer_No, _FilterCustomerNo);
TestProviderCustomerInvoices.Open();
//Go through the query records and create each entry in an external system
while TestProviderCustomerInvoices.Read() do begin
if _ShowMessage then
MyDialog.Update(1, TestProviderCustomerInvoices.Detailed_Entry_No);
DetailedCustLedgEntry.Get(TestProviderCustomerInvoices.Detailed_Entry_No);
RefreshInvoice(DetailedCustLedgEntry, _ShowMessage);
end;
if _ShowMessage and GuiAllowed then begin
MyDialog.Close();
Message(SyncFinishedMsg);
end;
end;