Skip to main content

Extending the Dime.Scheduler extension

Don't want to read?

Head straight for the repo and try it out now!

This tutorial shows you how to create a Business Central extension from scratch and integrate it with Dime.Scheduler. We'll build a simple example that sends salespersons as resources and opportunities as planning items.

Prerequisites

The Dime.Scheduler base app must be installed and configured. And needless to say, you almost must have a running instance of Dime.Scheduler itself, as Dime.Scheduler does not run inside Business Central. The extension, as we'll see, mostly does mapping between the two systems.

Step 1: Create a new project

Create a new AL project as you would do usually, such as by using the AL:Go command. When you've done that, add a dependency to Dime.Scheduler in the app.json file:

{
"id": "dimescheduler",
"name": "Dime.Scheduler",
"publisher": "Dime.Scheduler",
"version": "2.6.0.0"
}

Download the symbols by opening the command pallette ( CTRL + SHIFT + P), followed by running AL: Download Symbols. Of course, as mentioned in the prerequisites, this will only work when you have the Dime.Scheduler base app installed in your BC instance that's configured in the launch.json file.

In the end, your app.json file might look something like this:

{
"id": "mydimeschedulerextension",
"name": "My Dime.Scheduler Extension",
"publisher": "Your Company",
"version": "1.0.0.0",
"brief": "Integration with Dime.Scheduler",
"description": "Extension for integrating Business Central with Dime.Scheduler",
"privacyStatement": "",
"EULA": "",
"help": "",
"url": "",
"logo": "",
"dependencies": [
{
"id": "dimescheduler",
"name": "Dime.Scheduler",
"publisher": "Dime.Scheduler",
"version": "2.6.0.0"
}
],
"screenshots": [],
"platform": "16.0.0.0",
"application": "16.0.0.0",
"idRanges": [
{
"from": 2088000,
"to": 2088999
}
],
"resourceExposurePolicy": {
"allowDebugging": true,
"allowDownloadingSource": false,
"includeSourceInSymbolFile": false
},
"runtime": "14.0"
}

Step 2: Registering source types

If you want to plan additional BC tables in Dime.Scheduler, they must be registered in the Dime.Scheduler Source Types table. This source type metadata is sent along with each record that you send over to Dime.Scheduler. When planning data is sent from Dime.Scheduler to Business Central (in the Dime.Scheduler Appointments table), we can identify what table the entry belongs to, allowing us to process the planning data.

In this example, we add two tables:

  • Salespersons
  • Opportunities

This setup only needs to done once, so you can quickly add the entries manually if you're developing. For production purposes, you'll want to consider something like an installation code unit.

Create Install.codeunit.al to set up your source types:

codeunit 2088007 "DS Install Demo"
{
Subtype = Install;

trigger OnInstallAppPerCompany()
begin
InitializeDSSourceTypes();
end;

local procedure InitializeDSSourceTypes()
var
DSSourceType: Record "Dime DS Source Type";
DimeDSSetup: Record "Dime DS Setup";
begin
// Check if setup exist
if not DimeDSSetup.Get() then
exit;

// Initialize SalesPerson source type
if not DSSourceType.Get(DATABASE::"Salesperson/Purchaser") then begin
DSSourceType.Init();
DSSourceType."Table No." := DATABASE::"Salesperson/Purchaser";
DSSourceType."Source Type" := 'REP';
DSSourceType.Insert();
end;

// Initialize Opportunity source type
if not DSSourceType.Get(DATABASE::Opportunity) then begin
DSSourceType.Init();
DSSourceType."Table No." := DATABASE::Opportunity;
DSSourceType."Source Type" := 'OPP';
DSSourceType."Processing Codeunit No." := Codeunit::"DS Handle Opportunity Demo";
DSSourceType.Insert();
end;
end;
}

This code unit registers the two tables. Salespersons are marked by the type "REP" whereas opportunities get the "OPP" discriminator. It doesn't matter what value it is, as long as it doesn't interfere with other supported tables and as long as it's under 10 characters.

The opportunities table has an extra property: Processing Codeunit No.. As we'll see shortly, the Dime.Scheduler extension mostly consists of "Sending" and "Handling":

  • Sending: making planning data available in Dime.Scheduler (outgoing)
  • Handling: processing planning received from Dime.Scheduling (incoming)

When BC receives appointments that have the "OPP" source type, the Codeunit::"DS Handle Opportunity Demo" is invoked to process the planning data in the right place.

Step 3: Create the salesperson integration

To plan opportunities, we need to make the salespersons list available in Dime.Scheduler. The typical pattern is to add a button to the list and card.

Sending salespersons

Let's start by adding the logic to create resources in Dime.Scheduler based on the salespersons list. Create Salesperson/SendSalesperson.codeunit.al:

codeunit 2088003 "DS Send Sales Person Demo"
{
TableNo = "Salesperson/Purchaser";

trigger OnRun()
var
DimeDSSetup: Record "Dime DS Setup";
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSDocFilterValueMgt: Codeunit "Dime DS Doc. Filter Value Mgt.";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
DSSendOpportunityDemo: Codeunit "DS Send Opportunity Demo";
RecRef: RecordRef;
begin
// Ensure setup records exist
if not DimeDSConnectorSetup.Get() then
exit;

if not DimeDSSetup.Get() then
exit;

// Transfer the filter values for the resource - uses the DS Doc. Filter Value Sources setup
RecRef.GetTable(Rec);
RecRef.SetRecFilter();
DimeDSDocFilterValueMgt.TransferDocFilterValues(RecRef);

DSWebServMgt.InitParameters();

// Mandatory fields
DSWebServMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebServMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::"Salesperson/Purchaser"));
DSWebServMgt.AddParameter('ResourceNo', Rec.Code);

DSWebServMgt.AddParameter('ResourceName', Rec.Name);
DSWebServMgt.AddParameter('DisplayName', Rec.Name);
DSWebServMgt.AddParameter('ResourceType', 'Salesperson');

// Setting DoNotShow to TRUE hides the Resource from all users
DSWebServMgt.AddParameter('DoNotShow', DSWebServMgt.HandleBool((Rec.Blocked)));

DSWebServMgt.AddParameter('Email', Rec."E-Mail");
DSWebServMgt.AddParameter('Phone', Rec."Phone No.");
DSWebServMgt.AddParameter('MobilePhone', Rec."Phone No.");
DSWebServMgt.AddParameter('FieldServiceEmail', Rec."E-Mail");

// Sending Links with the Resource - up to 3 are available in mboc_upsertResource, use mboc_upsertResourceUrl for more
RecRef.GetTable(Rec);
DSWebServMgt.AddParameter('url1', DimeDSDimeSchedulerMgt.GeneratePageUrl(RecRef, 5116));
DSWebServMgt.AddParameter('urldesc1', 'Salesperson Card');

DSWebServMgt.CallDimeSchedulerWS('mboc_upsertResource');
end;

var
DSWebServMgt: Codeunit "Dime DS Web Service Management";

procedure SyncSalespersons(var Salesperson: Record "Salesperson/Purchaser")
begin
if Salesperson.FindSet() then
repeat
CODEUNIT.Run(CODEUNIT::"DS Send Sales Person Demo", Salesperson);
until Salesperson.Next() = 0;
end;
}

What happens here is a good demonstration what most of the Dime.Scheduler base app is about: fetching BC data, mapping it to the Dime.Scheduler data model, and invoking the API through the DSWebServMgt code unit.

Adding buttons

List

Create SalespersonExt.PageExt.al:

pageextension 2088005 "DS Salesperson Ext Demo" extends "Salespersons/Purchasers"
{
layout
{
}

actions
{
addlast(Processing)
{
group("DS Demo Actions")
{
Caption = 'Dime.Scheduler Demo';
Image = Planning;

Action("DS Send Selection Demo")
{
Caption = 'Send Selection';
ApplicationArea = All;
Image = RefreshLines;
ToolTip = 'Sends the selected record to Dime.Scheduler.';

trigger OnAction()
var
SalesPerson: Record "Salesperson/Purchaser";
DsSendResource: codeunit "DS Send Sales Person Demo";

begin
CurrPage.SETSELECTIONFILTER(SalesPerson);
DsSendResource.SyncSalespersons(SalesPerson);
end;
}

Action("DS Send All Demo")
{
Caption = 'Send All';
ApplicationArea = All;
Image = RefreshPlanningLine;
Tooltip = 'Sends all records to Dime.Scheduler.';

trigger OnAction()
var
SalesPerson: Record "Salesperson/Purchaser";
DsSendResource: codeunit "DS Send Sales Person Demo";
begin
DsSendResource.SyncSalespersons(SalesPerson);
end;
}
}
}
}
}

Card

Create SalespersonCardExt.PageExt.al

pageextension 2088004 "DS Salesperson Card Ext Demo" extends "Salesperson/Purchaser Card"
{
layout
{

}

actions
{
addlast(processing)
{
group("DS Demo Actions")
{
Caption = 'Dime.Scheduler Demo';

Action("DS Send Salesperson Demo")
{
Caption = 'Send Salesperson';
ApplicationArea = All;
Image = RefreshPlanningLine;
ToolTip = 'Sends the selected record to Dime.Scheduler.';

trigger OnAction()
begin
Codeunit.Run(codeunit::"DS Send Sales Person Demo", Rec);
end;
}
}
}
}
}

Step 4: Create the opportunity integration

The process is similar to the previous section: mapping data and providing the buttons to make opportunities available in Dime.Scheduler.

Sending opportunities

We considered two possible levels that one might be interested at: planning opportunties as 1 task, or planning the opportunity stages. We added a configuration flag Rec."DS Send Opportunity Header" which we'll cover right after this.

Create Opportunity/SendOpportunity.codeunit.al:

codeunit 2088000 "DS Send Opportunity Demo"
{
TableNo = Opportunity;

trigger OnRun()
var
Contact: Record Contact;
OpportunityStages: Record "Opportunity Entry";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSSetup: Record "Dime DS Setup";
DSWebservMgt: Codeunit "Dime DS Web Service Management";
begin
// Ensure setup records exist
if not DimeDSConnectorSetup.Get() then
exit;

if not DimeDSSetup.Get() then
exit;

if not Contact.get(Rec."Contact No.") then
exit;

DSWebservMgt.InitParameters();

// Mandatory fields
DSWebservMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebservMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::Opportunity));
DSWebservMgt.AddParameter('JobNo', Rec."No.");
DSWebservMgt.AddParameter('ShortDescription', Rec.Description);
DSWebservMgt.AddParameter('Description', Rec.Description);

DSWebservMgt.AddParameter('Type', Rec."Sales Cycle Code");
DSWebservMgt.AddParameter('CustomerNo', Rec."Contact No.");
DSWebservMgt.AddParameter('CustomerName', Rec."Contact Name");
DSWebservMgt.AddParameter('CustomerAddress', Contact.Address + ', ' + Contact."Address 2" + ', ' + Contact."Post Code" + ', ' + Contact.City);
DSWebservMgt.AddParameter('CustomerPhone', Contact."Phone No.");
DSWebservMgt.AddParameter('CustomerEmail', Contact."E-Mail");
DSWebservMgt.AddParameter('ContactNo', Contact."No.");
DSWebservMgt.AddParameter('ContactName', Contact.Name);

// The address shown on the map in Dime.Scheduler
DSWebservMgt.AddParameter('SiteAddress', Contact.Address + ', ' + Contact."Address 2" + ', ' + Contact."Post Code" + ', ' + Contact.City);

DSWebservMgt.AddParameter('SitePostcode', Contact."Post Code");
DSWebservMgt.AddParameter('SiteCity', Contact."City");
DSWebservMgt.AddParameter('SiteCounty', Contact."County");
DSWebservMgt.AddParameter('SiteCountry', DimeDSDimeSchedulerMgt.GetCountryCode(Contact."Country/Region Code"));
DSWebServMgt.AddParameter('CreationDateTime', DSWebServMgt.HandleDateTime(CREATEDATETIME(Rec."Creation Date", 0T)));
DSWebservMgt.AddParameter('Creator', CopyStr(UserId(), 1, 1024));

DSWebservMgt.AddParameter('FreeText1', format(Rec.Status));

// Call the Dime.Scheduler web service will insert or update the Job
DSWebservMgt.CallDimeSchedulerWS('mboc_upsertJob');

// Check if opportunity header should be sent as task
if Rec."DS Send Opportunity Header" then
this.SendOpportunityAsTask(Rec)
else begin
Clear(OpportunityStages);
OpportunityStages.SetRange("Opportunity No.", Rec."No.");
OpportunityStages.SetFilter("Sales Cycle Stage Description", '<>%1', '');
if OpportunityStages.FindSet() then
repeat
this.SendOpportunityCycleStages(OpportunityStages);
until OpportunityStages.Next() = 0;
end;

end;

local procedure SendOpportunityCycleStages(var OpportunityStages: Record "Opportunity Entry")
var
Opportunity: Record Opportunity;
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
RecRef: RecordRef;
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSSetup: Record "Dime DS Setup";
DSWebservMgt: Codeunit "Dime DS Web Service Management";
begin
DimeDSConnectorSetup.Get();
DimeDSSetup.Get();
Opportunity.Get(OpportunityStages."Opportunity No.");

DSWebservMgt.InitParameters();

// Mandatory fields
DSWebservMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebservMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::Opportunity));
DSWebservMgt.AddParameter('TaskNo', format(OpportunityStages."Sales Cycle Stage"));
DSWebservMgt.AddParameter('JobNo', Opportunity."No.");
DSWebservMgt.AddParameter('ShortDescription', OpportunityStages."Sales Cycle Stage Description");
DSWebservMgt.AddParameter('Description', OpportunityStages."Sales Cycle Stage Description");

// Fields that affect default Duration and Capacity
DSWebservMgt.AddParameter('DurationInSeconds', DimeDSDimeSchedulerMgt.ConvertDecimaltoSeconds(4)); // Fixed 4h

// Sending Links with the Task - up to 3 are available in mboc_upsertTask, use mboc_upsertTaskUrl for more
RecRef.GetTable(Opportunity);
DSWebservMgt.AddParameter('url1', DimeDSDimeSchedulerMgt.GeneratePageUrl(RecRef, 5124));
DSWebservMgt.AddParameter('urldesc1', 'Opportunity Card');

// Call the Dime.Scheduler web service will insert or update the Task
DSWebservMgt.CallDimeSchedulerWS('mboc_upsertTask');
end;

local procedure SendOpportunityAsTask(var Opportunity: Record Opportunity)
var
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
RecRef: RecordRef;
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSSetup: Record "Dime DS Setup";
DSWebservMgt: Codeunit "Dime DS Web Service Management";
begin
DimeDSConnectorSetup.Get();
DimeDSSetup.Get();

DSWebservMgt.InitParameters();

// Mandatory fields
DSWebservMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebservMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::Opportunity));
DSWebservMgt.AddParameter('TaskNo', Opportunity."No.");
DSWebservMgt.AddParameter('JobNo', Opportunity."No.");
DSWebservMgt.AddParameter('ShortDescription', Opportunity.Description);
DSWebservMgt.AddParameter('Description', Opportunity.Description);

// Fields that affect default Duration and Capacity
DSWebservMgt.AddParameter('DurationInSeconds', DimeDSDimeSchedulerMgt.ConvertDecimaltoSeconds(4)); // Fixed 4h

// Sending Links with the Task - up to 3 are available in mboc_upsertTask, use mboc_upsertTaskUrl for more
RecRef.GetTable(Opportunity);
DSWebservMgt.AddParameter('url1', DimeDSDimeSchedulerMgt.GeneratePageUrl(RecRef, 5124));
DSWebservMgt.AddParameter('urldesc1', 'Opportunity Card');

// Call the Dime.Scheduler web service will insert or update the Task
DSWebservMgt.CallDimeSchedulerWS('mboc_upsertTask');
end;

procedure DeleteOpportunity(Opportunity: Record Opportunity)
var
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSSetup: Record "Dime DS Setup";
DSWebservMgt: Codeunit "Dime DS Web Service Management";
begin
DimeDSConnectorSetup.Get();
DimeDSSetup.Get();

if Opportunity."No." <> '' then begin
DSWebservMgt.InitParameters();

DSWebservMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebservMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::Opportunity));
DSWebservMgt.AddParameter('JobNo', Opportunity."No.");
DSWebservMgt.AddParameter('CheckAppointments', DSWebservMgt.HandleBool(DimeDSSetup."Check Appointment on Delete"));

DSWebservMgt.CallDimeSchedulerWS('mboc_deleteJob');
end;
end;
}

Extend the opportunity

To make the planning level of this opportunity configurable, you could consider adding a flag to the opportunity table and card. To do so, following these instructions:

Table

Create OpportunityExt.TableExt.al:

tableextension 2088008 "DS Opportunity Ext Demo" extends Opportunity
{
fields
{
field(2088000; "DS Send Opportunity Header"; Boolean)
{
Caption = 'Send opportunity header as task';
DataClassification = CustomerContent;
InitValue = true;
}
}
}

Card

Create Opportunity/OpportunityCardExt.pageext.al:

pageextension 2088001 "DS OpportunityCardExt Demo" extends "Opportunity Card"
{
layout
{
addlast(Content)
{
group("DS Dime.Scheduler Tab")
{
Caption = 'Dime.Scheduler';

field("DS Send Opportunity Header Tab"; Rec."DS Send Opportunity Header")
{
ApplicationArea = All;
ToolTip = 'Specifies whether to send the opportunity header to Dime.Scheduler.';
}
}
}
}

actions
{
addlast(processing)
{
group("DS Demo Actions")
{
Caption = 'Dime.Scheduler Demo';
Image = Planning;

action("DS Send to Dime.Scheduler Demo")
{
Caption = 'Send to Dime.Scheduler';
Image = Planning;
ApplicationArea = All;

trigger OnAction()
var
DsSendOpportunity: Codeunit "DS Send Opportunity Demo";
begin
DsSendOpportunity.Run(Rec);
end;
}
action("DS DeleteFromDimeScheduler Demo")
{
Caption = 'Delete from Dime.Scheduler';
Image = RemoveLine;
ApplicationArea = All;

trigger OnAction()
var
DsSendOpportunity: Codeunit "DS Send Opportunity Demo";
begin
DsSendOpportunity.DeleteOpportunity(Rec);
end;
}
}
}
}
}

Besides adding the configuration flag, this code adds the button to send (and delete) the opportunity to Dime.Scheduler.

Create the handle opportunity codeunit

To write back planning data received from Dime.Scheduler, we need to write a code unit that can do this. What happens with this planning data is up to you. We decided to create tasks for each planned appointment.

Create Opportunity/HandleOpportunity.codeunit.al:

codeunit 2088002 "DS Handle Opportunity Demo"
{
TableNo = "Dime DS Appointment";

trigger OnRun()
begin
PerformAllocation(Rec);
end;

local procedure PerformAllocation(DimeDSAppointment: Record "Dime DS Appointment")
var
DimeDSAppointmentResource: Record "Dime DS Appointment Resource";
DimeDSOrderLineLink: Record "Dime DS Order Line Link";
DimeDSSetup: Record "Dime DS Setup";
Opportunity: Record Opportunity;
ToDo: Record "To-do";
LineNo: Text;
begin
if not Opportunity.Get(DimeDSAppointment."Job No.") then
exit; // No Opportunity found, nothing to do

Evaluate(LineNo, DimeDSAppointment."Task No.");

DimeDSSetup.Get();

// New appointment: create new task + create link entry between Dime.Scheduler appointment and BC task
if DimeDSAppointment."Database Action" = 'I' then begin
DimeDSAppointmentResource.Reset();
DimeDSAppointmentResource.SetRange("Entry No.", DimeDSAppointment."Entry No.");
if DimeDSAppointmentResource.FindSet() then
repeat
if (DimeDSAppointment."Sent From Backoffice") then
InsertDSTodoLink(DimeDSAppointment, DimeDSAppointmentResource)
else
CreateToDo(DimeDSAppointment, DimeDSAppointmentResource, Opportunity);
until DimeDSAppointmentResource.Next() = 0;
end;

// Updated appointment: update task (lookup in link table)
if DimeDSAppointment."Database Action" = 'U' then begin
// First check if Resources planned in Dime.Scheduler are allocated in BC
DimeDSAppointmentResource.Reset();
DimeDSAppointmentResource.SetRange("Entry No.", DimeDSAppointment."Entry No.");
if DimeDSAppointmentResource.FindSet() then
repeat
if (DimeDSAppointment."Sent From Backoffice") then
UpdateDSTodoLink(DimeDSAppointment, DimeDSAppointmentResource)
else begin
DimeDSOrderLineLink.SetRange("Appointment Id", DimeDSAppointment."Appointment Id");
DimeDSOrderLineLink.SetRange("Resource No.", DimeDSAppointmentResource."Resource No.");
if DimeDSOrderLineLink.FindFirst() then begin
UpdateTodo(DimeDSAppointment, DimeDSAppointmentResource, DimeDSOrderLineLink);
end else begin
CreateToDo(DimeDSAppointment, DimeDSAppointmentResource, Opportunity);
end;
end;
until DimeDSAppointmentResource.Next() = 0;

// Then check if resources already allocated in BC have been deleted in Dime.Scheduler
DimeDSOrderLineLink.Reset();
DimeDSOrderLineLink.SetRange("Appointment Id", DimeDSAppointment."Appointment Id");
if DimeDSOrderLineLink.FindSet() then
repeat
DimeDSAppointmentResource.Reset();
DimeDSAppointmentResource.SetRange("Entry No.", DimeDSAppointment."Entry No.");
DimeDSAppointmentResource.SetRange("Resource No.", DimeDSOrderLineLink."Resource No.");
if DimeDSAppointmentResource.IsEmpty() then begin // Resource has been removed in Dime.Scheduler
if ToDo.Get(DimeDSOrderLineLink."Document No.") then
ToDo.Delete(true);
DimeDSOrderLineLink.Delete(true);
end;
until DimeDSOrderLineLink.Next() = 0;
end;

// Delete task and link entry between Dime.Scheduler appointment and BC task
if DimeDSAppointment."Database Action" = 'D' then begin
DimeDSOrderLineLink.Reset();
DimeDSOrderLineLink.SetRange("Appointment Id", DimeDSAppointment."Appointment Id");
if DimeDSOrderLineLink.FindSet() then
repeat
if Todo.Get(DimeDSOrderLineLink."Document No.") then
Todo.Delete(true);
DimeDSOrderLineLink.Delete(true);
until DimeDSOrderLineLink.Next() = 0;
end;
end;

local procedure CreateToDo(DimeDSAppointment: Record "Dime DS Appointment"; DimeDSAppointmentResource: Record "Dime DS Appointment Resource"; Opportunity: Record Opportunity)
var
Salesperson: Record "Salesperson/Purchaser";
Todo: Record "To-do";
DimeDSOrderLineLink: Record "Dime DS Order Line Link";
DimeDSEvents: Codeunit "Dime DS Events";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
begin
if DimeDSAppointmentResource."Resource No." <> '' then
Salesperson.Get(DimeDSAppointmentResource."Resource No.");

Todo.Init();
Todo."Opportunity No." := Opportunity."No.";
Todo.Description := DimeDSAppointment.Subject;
Todo.Validate("Contact No.", Opportunity."Contact No.");
Todo.Insert(true);

Todo.Date := DimeDSDimeSchedulerMgt.DateTime2Date(DimeDSAppointment.Start);
Todo."Ending Date" := DimeDSDimeSchedulerMgt.DateTime2Date(DimeDSAppointment."End");
Todo."Salesperson Code" := Salesperson.Code;
Todo."Ending Time" := 0T;
Todo."Start Time" := DimeDSDimeSchedulerMgt.DateTime2Time(DimeDSAppointment.Start);
Todo."Ending Time" := DimeDSDimeSchedulerMgt.DateTime2Time(DimeDSAppointment."End");
Todo.Duration := DimeDSAppointment."End" - DimeDSAppointment.Start;

Todo.Modify(true);

DimeDSOrderLineLink.Init();
DimeDSOrderLineLink."Appointment Id" := DimeDSAppointment."Appointment Id";
DimeDSOrderLineLink."Resource No." := Salesperson.Code;
DimeDSOrderLineLink."Document No." := Todo."No.";
DimeDSOrderLineLink.Insert(true);
end;

local procedure UpdateTodo(DimeDSAppointment: Record "Dime DS Appointment"; DimeDSAppointmentResource: Record "Dime DS Appointment Resource"; DimeDSOrderLineLink: Record "Dime DS Order Line Link")
var
Todo: Record "To-do";
DimeDSEvents: Codeunit "Dime DS Events";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
begin
if Todo.Get(DimeDSOrderLineLink."Document No.") then begin
Todo.Date := DimeDSDimeSchedulerMgt.DateTime2Date(DimeDSAppointment.Start);
Todo."Ending Date" := DimeDSDimeSchedulerMgt.DateTime2Date(DimeDSAppointment."End");
Todo."Salesperson Code" := DimeDSAppointmentResource."Resource No.";
Todo."Ending Time" := 0T;
Todo."Start Time" := DimeDSDimeSchedulerMgt.DateTime2Time(DimeDSAppointment.Start);
Todo."Ending Time" := DimeDSDimeSchedulerMgt.DateTime2Time(DimeDSAppointment."End");
Todo.Duration := DimeDSAppointment."End" - DimeDSAppointment.Start;
Todo.Modify(true);
end;
end;

local procedure InsertDSTodoLink(DimeDSAppointment: Record "Dime DS Appointment"; DimeDSAppointmentResource: Record "Dime DS Appointment Resource")
var
Todo: Record "To-do";
DimeDSOrderLineLink: Record "Dime DS Order Line Link";
EntryNo: Integer;
begin
Todo.Get(DimeDSAppointment."Backoffice Id");
Evaluate(EntryNo, Todo."No.");

DimeDSOrderLineLink.Init();
DimeDSOrderLineLink."Appointment Id" := DimeDSAppointment."Appointment Id";
DimeDSOrderLineLink."Resource No." := CopyStr(DimeDSAppointmentResource."Resource No.", 1, 20);
DimeDSOrderLineLink."Document No." := DimeDSAppointment."Backoffice Id";
DimeDSOrderLineLink.Insert(true);
end;

local procedure UpdateDSTodoLink(DimeDSAppointment: Record "Dime DS Appointment"; DimeDSAppointmentResource: Record "Dime DS Appointment Resource")
var
Todo: Record "To-do";
DimeDSOrderLineLink: Record "Dime DS Order Line Link";
EntryNo: Integer;
begin
if not Todo.Get(DimeDSAppointment."Backoffice Id") then
exit;
Evaluate(EntryNo, DimeDSAppointment."Backoffice Id");

DimeDSOrderLineLink.SetRange("Appointment Id", DimeDSAppointment."Appointment Id");
DimeDSOrderLineLink.SetRange("Document No.", Todo."No.");
DimeDSOrderLineLink.FindFirst();
DimeDSOrderLineLink.Rename(DimeDSAppointment."Appointment Id", DimeDSAppointmentResource."Resource No.");
end;
}

For the most part, this is boilerplate code. A task is created, after which this new entry is linked to the Dime.Scheduler appointment through the "Dime DS Order Line Link" table. Subsequent updates in either Dime.Scheduler and Business Central will be reflected in the other system as well, thanks to this staging table.

Step 5: Build and run

Run the extension. Check whether the source types are available for the salespersons and opportunities.

Send salespersons

  • Hit ALT + Q and look for Salespersons/Purchasers
  • Select one or more salespersons
  • Navigate to the ribbon -> Actions -> Send to Dime.Scheduler

In Dime.Scheduler, you'll now have a list of resources with a new resource type of Salesperson.

Send opportunity

  1. Hit ALT + Q and look for Opportunities (list)
  2. Open an opportunity
  3. Click Send to Dime.Scheduler action
  4. Check Dime.Scheduler to see the new task in the open tasks list

Plan

  • Drag and drop the new opportunity to a salesperson.
  • Select the new appointment on the planning board, hit "L" on your keyboard, and open the opportunity card
  • In the opportunity, go to the tasks list.
  • You should see a new task that corresponds with the appointment you just created in Dime.Scheduler.

Troubleshooting

Check out this page for common issues with the integration to and from Business Central.