Page 7100 Contact Sync, source in 29
Source29
src/Layers/W1/BaseApp/CRM/Outlook/ContactSync.Page.al728 lines, Copyright (c) Microsoft Corporation. MIT
namespace Microsoft.CRM.Outlook;
using Microsoft.CRM.Contact;
page 7100 "Contact Sync"
{
PageType = NavigatePage;
ApplicationArea = All;
UsageCategory = Administration;
Caption = 'Synchronize contacts';
layout
{
area(content)
{
group(Step1)
{
Caption = 'Welcome';
Visible = Step = Step::Welcome;
group(WelcomeGroup)
{
Caption = '';
field(WelcomeText; WelcomeTextTxt)
{
ApplicationArea = All;
Caption = '';
Editable = false;
MultiLine = true;
ShowCaption = false;
Style = Standard;
}
}
}
#if not CLEAN29
group(AuthGroup)
{
Caption = '';
InstructionalText = 'Click on Next to authenticate with Microsoft 365 and retrieve contact folders.';
ObsoleteReason = 'Removed due to Contact Sync redesign, will be deleted in future release.';
ObsoleteState = Pending;
ObsoleteTag = '29.0';
Visible = false;
}
#endif
group(Step2)
{
Caption = 'Contact Filter';
Visible = Step = Step::ContactFilter;
group(ContactFilterGroup)
{
Caption = '';
InstructionalText = 'You can synchronize all person contacts from the current company in Business Central, or filter the list to focus on specific person contacts.';
field(ContactFilter; ContactFilterText)
{
ApplicationArea = All;
Caption = 'Choose contacts';
Editable = false;
ToolTip = 'Specify filters on different fields, such as salesperson code, to reduce the list of contacts that will be synchronized. Leave this blank to synchronize all contacts.';
trigger OnAssistEdit()
var
ContactRec: Record "Contact";
FilterPageBuilder: FilterPageBuilder;
ContactTxt: Text;
begin
ContactTxt := ContactRec.TableCaption();
FilterPageBuilder.AddTable('Contact', Database::Contact);
FilterPageBuilder.ADdField(ContactTxt, ContactRec."Territory Code");
FilterPageBuilder.ADdField(ContactTxt, ContactRec."Company No.");
FilterPageBuilder.ADdField(ContactTxt, ContactRec."Salesperson Code");
FilterPageBuilder.ADdField(ContactTxt, ContactRec.City);
FilterPageBuilder.ADdField(ContactTxt, ContactRec.County);
FilterPageBuilder.ADdField(ContactTxt, ContactRec."Post Code");
FilterPageBuilder.ADdField(ContactTxt, ContactRec."Country/Region Code");
if ContactFilterText <> '' then
FilterPageBuilder.SetView('Contact', ContactFilterText);
if FilterPageBuilder.RunModal() then begin
ContactFilterText := FilterPageBuilder.GetView('Contact', false);
ContactRec.SetView(ContactFilterText);
Message(TotalRecordsMessageLbl, ContactRec.Count);
end;
end;
}
field(FolderListField; SelectedFolderName)
{
ApplicationArea = All;
Caption = 'Contacts folder';
ToolTip = 'Specifies the name of the folder in Outlook and Teams which will be used for synchronization. This helps segregate your business contacts from any other contacts.';
Lookup = true;
ShowMandatory = true;
QuickEntry = false;
trigger OnLookup(var Text: Text): Boolean
var
TempFolder: Record "Contact Sync Folder" temporary;
begin
TempFolder.Copy(TempSyncFolder, true);
TempFolder.Reset();
if Page.RunModal(Page::"Folder Lookup", TempFolder) = Action::LookupOK then begin
SelectedFolderId := TempFolder."Folder ID";
SelectedFolderName := TempFolder."Display Name";
Text := SelectedFolderName;
PreviousFolderName := SelectedFolderName; // Store last selection
exit(true);
end;
exit(false);
end;
trigger OnValidate()
begin
// Trigger validation even if user clears the field
if SelectedFolderName <> PreviousFolderName then begin
if SelectedFolderName = '' then
Error(ErrorFolderEmptyMsg);
TempSyncFolder.Reset();
TempSyncFolder.SetRange("Display Name", SelectedFolderName);
if not TempSyncFolder.FindFirst() then
Error(ErrorInvalidFolderMsg);
PreviousFolderName := SelectedFolderName; // Update tracker
end;
end;
}
field(PreviewText; PreviewTextLbl)
{
ApplicationArea = All;
Caption = '';
Editable = false;
MultiLine = false;
ShowCaption = false;
Style = Subordinate;
}
field(LastsyncDateFolderTime; GetLastSyncDateTime())
{
ApplicationArea = All;
Caption = '';
Editable = false;
MultiLine = false;
ShowCaption = false;
Style = Subordinate;
}
group(AdvancedGroup)
{
Caption = 'Advanced';
group(ClearSyncDetailsGroup)
{
Caption = '';
field(fullSyncField; FullSyncOption)
{
ApplicationArea = All;
Caption = 'Force full sync';
ToolTip = 'When this is enabled, Business Central will synchronize contacts no matter when they were last modified. This may take longer, but can overcome some synchronization issues.';
}
field(ClearSyncDetailsField; ClearSyncDetailsLbl)
{
ApplicationArea = All;
Caption = '';
Editable = false;
ShowCaption = false;
Style = StandardAccent;
StyleExpr = true;
trigger OnDrillDown()
begin
if Confirm(ClearLastSyncConfirmMsg) then begin
ClearSyncRecordsForCurrentUser();
Message(LastSyncClearedMsg);
GoToStep(Step::Welcome);
end;
end;
}
}
}
}
}
group(Step3)
{
Caption = 'Synchronize Contacts';
Visible = Step = Step::SyncOptions;
group(SyncOptionsGroup)
{
Caption = '';
InstructionalText = 'We found some contacts that match your criteria. Choose Synchronize to begin. This can take a few minutes if you have many contacts.';
field(SyncDirectionField; SyncDirection)
{
ApplicationArea = All;
Caption = 'What to update';
ToolTip = 'Specifies the direction of synchronization. If you only update Outlook and Teams, no changes will be made to the contact list in Business Central. If you update both, the contacts from the specified folder in Outlook and Teams will also be copied back to Business Central and other users may be able to see them.';
ShowMandatory = true;
QuickEntry = false;
}
field(SyncToM365Button; GetContactsToAddOutlookLabel())
{
ApplicationArea = All;
Caption = '';
Editable = false;
ShowCaption = false;
Style = StandardAccent;
StyleExpr = true;
trigger OnDrillDown()
var
TempFilteredQueue: Record "Contact Sync Queue" temporary;
SyncQueueDialog: Page "Contact Sync Queue Dialog";
Caption: Text;
begin
TempFilteredQueue.Copy(TempSyncContacts, true);
TempFilteredQueue.Reset();
TempFilteredQueue.SetRange("Sync Direction", 0);
SyncQueueDialog.SetData(TempFilteredQueue);
Caption := CaptionToSyncO365Txt;
SyncQueueDialog.setCaption(Caption);
SyncQueueDialog.RunModal();
end;
}
field(SyncToBCButton; GetContactsToAddBCLabel())
{
ApplicationArea = All;
Caption = '';
Editable = false;
ShowCaption = false;
Style = StandardAccent;
StyleExpr = true;
trigger OnDrillDown()
var
TempFilteredQueue: Record "Contact Sync Queue" temporary;
SyncQueueDialog: Page "Contact Sync Queue Dialog";
caption: Text;
begin
TempFilteredQueue.Copy(TempSyncContacts, true);
TempFilteredQueue.Reset();
TempFilteredQueue.SetRange("Sync Direction", 1);
SyncQueueDialog.SetData(TempFilteredQueue);
caption := CaptionToSyncBCTxt;
SyncQueueDialog.setCaption(caption);
SyncQueueDialog.RunModal();
end;
}
}
}
#if not CLEAN29
group(Step6)
{
Caption = 'Synchronize Contacts';
Visible = false;
ObsoleteReason = 'Removed due to Contact Sync redesign, will be deleted in future release.';
ObsoleteState = Pending;
ObsoleteTag = '29.0';
}
group(Step7)
{
Caption = 'Completed';
Visible = false;
ObsoleteReason = 'Removed due to Contact Sync redesign, will be deleted in future release.';
ObsoleteState = Pending;
ObsoleteTag = '29.0';
}
#endif
group(Step4)
{
Caption = 'Completed';
Visible = Step = Step::Finish;
#if not CLEAN29
group(ReadyGroup)
{
Caption = '';
InstructionalText = '';
Visible = false;
ObsoleteReason = 'Removed due to Contact Sync redesign, will be deleted in future release.';
ObsoleteState = Pending;
ObsoleteTag = '29.0';
field(ReadyText; ReadyTextTxt)
{
ApplicationArea = All;
Caption = '';
Editable = false;
MultiLine = true;
ShowCaption = false;
Visible = false;
ObsoleteReason = 'Removed due to Contact Sync redesign, will be deleted in future release.';
ObsoleteState = Pending;
ObsoleteTag = '29.0';
}
}
#endif
group(FinishGroupFinal)
{
Caption = '';
InstructionalText = 'Synchronization has completed successfully.';
Visible = not NoContactsToSync;
field(SyncTimeField; GetSyncTimeLabel())
{
ApplicationArea = All;
Caption = '';
Editable = false;
ToolTip = 'Specifies the time taken to complete the synchronization.';
}
field(ContactsSentToM365Field; GetContactsSentToM365Label())
{
ApplicationArea = All;
Caption = '';
Editable = false;
ToolTip = 'Specifies the number of contacts sent to Outlook and Teams.';
}
field(ContactsSentToBCField; GetContactsSentToBCLabel())
{
ApplicationArea = All;
Caption = '';
Editable = false;
ToolTip = 'Specifies the number of contacts sent to Business Central.';
}
field(ContactsFailedField; GetContactsFailedLabel())
{
ApplicationArea = All;
Caption = '';
Editable = false;
ShowCaption = false;
ToolTip = 'Specifies the number of contacts that failed to synchronize.';
Style = Unfavorable;
StyleExpr = ContactsFailedCount > 0;
Visible = ContactsFailedCount > 0;
trigger OnDrillDown()
var
TempFilteredQueue: Record "Contact Sync Queue" temporary;
SyncQueueDialog: Page "Contact Sync Queue Dialog";
caption: Text;
begin
TempFilteredQueue.Copy(TempSyncContacts, true);
TempFilteredQueue.Reset();
TempFilteredQueue.SetRange("Sync Status", TempFilteredQueue."Sync Status"::Error);
SyncQueueDialog.SetData(TempFilteredQueue);
caption := FailedSyncLbl;
SyncQueueDialog.setCaption(caption);
SyncQueueDialog.RunModal();
end;
}
}
group(FinishGroupFinalNosync)
{
Caption = '';
InstructionalText = 'No contacts available to synchronize. You can close this wizard now.';
Visible = NoContactsToSync;
}
}
}
}
actions
{
area(Processing)
{
action(ActionBack)
{
ApplicationArea = All;
Caption = 'Back';
Image = PreviousRecord;
InFooterBar = true;
Enabled = BackEnabled;
trigger OnAction()
begin
GoToStep(Step - 1);
end;
}
action(ActionNext)
{
ApplicationArea = All;
Caption = 'Next';
Image = NextRecord;
InFooterBar = true;
Enabled = NextEnabled;
trigger OnAction()
var
GraphMgt: Codeunit "O365 Bidirectional Sync";
O365GraphAuth: Codeunit "O365 Graph Authentication";
begin
if Step = Step::Welcome then begin
O365GraphAuth.GetAccessToken(AccessToken);
if AccessToken.IsEmpty() then begin
Session.LogMessage('0000QU2', ErrTelTxt, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CategoryTok, CategoryLbl);
Error(ErrorObtainingTokenMsg);
end;
// Populate folder options
PopulateFolderOptions(TempSyncFolder);
GraphMgt.GetContactFolders(AccessToken, TempSyncFolder);
if (TempSyncFolder.Count() = 0) then begin
Session.LogMessage('0000QU3', NoFolderMsg, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CategoryTok, CategoryLbl);
GraphMgt.CreateFolderinO365(AccessToken, TempSyncFolder, FolderName);
end;
if (SelectedFolderName = '') and (TempSyncFolder.FindFirst()) then begin
SelectedFolderId := TempSyncFolder."Folder ID";
SelectedFolderName := TempSyncFolder."Display Name";
PreviousFolderName := SelectedFolderName;
end;
// Update delta link for each folder
TempSyncFolder.Reset();
TempSyncFolder.SetLoadFields("Folder ID", "Display Name");
if TempSyncFolder.FindSet() then
repeat
UpdateFolderId(TempSyncFolder."Folder ID", '', TempSyncFolder."Display Name");
until TempSyncFolder.Next() = 0;
end;
if Step = Step::ContactFilter then
if SelectedFolderId = '' then
Error(ErrorSelectFolderMsg);
if Step = Step::ContactFilter then begin
// Fetch contacts when moving from Ready step
Deltalink := '';
TempSyncContacts.DeleteAll();
GraphMgt.GetContacts(AccessToken, TempSyncContacts, ContactFilterText, SelectedFolderId, FullSyncOption, Deltalink);
end;
GoToStep(Step + 1);
end;
}
action(ActionFetch)
{
ApplicationArea = All;
Caption = 'Synchronize';
Image = Approve;
InFooterBar = true;
Enabled = FinishEnabled;
trigger OnAction()
var
SyncProcessor: Codeunit "Contact Sync Processor";
BCcount: Integer;
M365count: Integer;
StartTime: DateTime;
begin
case Step of
Step::SyncOptions:
begin
BCcount := GetSyncToBCCount();
M365count := GetSyncToM365Count();
Session.LogMessage('0000QU4', StrSubstNo(SyncCountsTelLbl, BCcount, M365count), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CategoryTok, CategoryLbl);
if (BCcount + M365count) = 0 then begin
Session.LogMessage('0000QU5', NoContactsToSyncTelLbl, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CategoryTok, CategoryLbl);
Error(NoSyncMsg);
end;
if (SyncDirection = SyncDirection::"Full Sync") and not (BCcount = 0) then
if Confirm(DisclaimerLbl) then
Session.LogMessage('0000RRF', StrSubstNo(SyncAcknowledgementTelTxt), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CategoryTok, CategoryLbl)
else
exit;
if (SyncDirection = SyncDirection::"Sync from BC to M365") and (M365count = 0) then begin
Session.LogMessage('0000QU6', NoContactsBCToM365TelLbl, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CategoryTok, CategoryLbl);
Error(NoSyncMsg);
end
else begin
Session.LogMessage('0000QU7', StrSubstNo(StartingSyncTelLbl, SyncDirection), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CategoryTok, CategoryLbl);
StartTime := CurrentDateTime();
SyncProcessor.ProcessBidirectionalSync(TempSyncContacts, accessToken, SelectedFolderId, SyncDirection);
SyncTimeText := Format(CurrentDateTime() - StartTime);
Session.LogMessage('0000QU8', StrSubstNo(SyncCompletedTelLbl, SyncTimeText), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CategoryTok, CategoryLbl);
end;
UpdateFolderId(SelectedFolderId, Deltalink, SelectedFolderName);
UpdateSyncCounts();
Session.LogMessage('0000QU9', StrSubstNo(SyncResultsTelLbl, ContactsSentToM365Count, ContactsSentToBCCount, ContactsFailedCount), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CategoryTok, CategoryLbl);
GoToStep(Step::Finish);
end;
end;
end;
}
action(ActionClose)
{
ApplicationArea = All;
Caption = 'Close';
Image = Close;
InFooterBar = true;
Visible = Step = Step::Finish;
trigger OnAction()
begin
CurrPage.Close();
end;
}
}
}
trigger OnOpenPage()
begin
NewLineChar := 10;
Step := Step::Welcome;
WelcomeTextTxt := WelcomeTextLbl;
FolderName := 'Business Central';
SyncDirection := SyncDirection::"Sync from BC to M365";
ReadyTextTxt := '';
UpdateControls();
end;
local procedure UpdateSyncCounts()
var
TempQueue: Record "Contact Sync Queue" temporary;
begin
ContactsSentToM365Count := 0;
ContactsSentToBCCount := 0;
ContactsFailedCount := 0;
TempQueue.Copy(TempSyncContacts, true);
TempQueue.Reset();
TempQueue.SetRange("Sync Status", TempQueue."Sync Status"::Processed);
TempQueue.SetRange("Sync Direction", 0);
ContactsSentToM365Count := TempQueue.Count();
TempQueue.Reset();
TempQueue.SetRange("Sync Status", TempQueue."Sync Status"::Processed);
TempQueue.SetRange("Sync Direction", 1);
ContactsSentToBCCount := TempQueue.Count();
TempQueue.Reset();
TempQueue.SetRange("Sync Status", TempQueue."Sync Status"::Error);
ContactsFailedCount := TempQueue.Count();
end;
local procedure UpdateFolderId(FolderId: Text; NewDeltaLink: Text; FolderName: Text)
var
ContactSyncUserRec: Record "Contact Sync User";
begin
ContactSyncUserRec.Reset();
ContactSyncUserRec.SetRange("User ID", CopyStr(UserId(), 1, 50));
ContactSyncUserRec.SetRange("Folder ID", CopyStr(FolderId, 1, 250));
ContactSyncUserRec.SetLoadFields("Delta Url", "Folder Name", "Folder ID", "Last Sync Date Time");
if not ContactSyncUserRec.FindFirst() then begin
ContactSyncUserRec.Init();
ContactSyncUserRec."User ID" := CopyStr(UserId(), 1, 50);
ContactSyncUserRec."Folder ID" := CopyStr(FolderId, 1, 250);
ContactSyncUserRec."Folder Name" := CopyStr(FolderName, 1, 250);
ContactSyncUserRec.Insert(true);
end
else
if not (NewDeltaLink = '') then begin
ContactSyncUserRec."Last Sync Date Time" := CurrentDateTime();
ContactSyncUserRec.SetDeltaUrl(CopyStr(NewDeltaLink, 1, 2048));
end;
end;
local procedure ClearSyncRecordsForCurrentUser()
var
ContactSyncUserRec: Record "Contact Sync User";
begin
ContactSyncUserRec.SetRange("User ID", CopyStr(UserId(), 1, 50));
if not ContactSyncUserRec.IsEmpty() then
ContactSyncUserRec.DeleteAll();
end;
local procedure GoToStep(NewStep: Option)
begin
Step := NewStep;
UpdateControls();
end;
local procedure GetLastSyncDateTime(): Text
var
ContactSyncUserRec: Record "Contact Sync User";
begin
ContactSyncUserRec.Reset();
ContactSyncUserRec.SetRange("User ID", CopyStr(UserId(), 1, 50));
ContactSyncUserRec.SetRange("Folder ID", CopyStr(SelectedFolderId, 1, 250));
ContactSyncUserRec.SetFilter("Last Sync Date Time", '<>%1', 0DT);
ContactSyncUserRec.SetLoadFields("Last Sync Date Time", "Folder Name");
ContactSyncUserRec.SetCurrentKey("Last Sync Date Time");
ContactSyncUserRec.Ascending(true);
if ContactSyncUserRec.FindLast() then
exit(Format(ContactSyncUserRec."Folder Name" + ' ' + SyncedAtTxt + ' ' + Format(ContactSyncUserRec."Last Sync Date Time")))
else
exit(NoSyncFoundTxt);
end;
local procedure UpdateControls()
begin
BackEnabled := (Step > Step::Welcome) and (Step <= Step::SyncOptions);
NextEnabled := Step < Step::SyncOptions;
FinishEnabled := (Step = Step::SyncOptions);
NoContactsToSync := TempSyncContacts.Count() = 0;
end;
procedure PopulateFolderOptions(var TempFolder: Record "Contact Sync Folder" temporary)
begin
// Clear existing folders
TempFolder.Reset();
TempFolder.DeleteAll();
NextEntryNo := 0;
end;
local procedure GetSyncToBCCount(): Integer
var
TempQueue: Record "Contact Sync Queue" temporary;
begin
TempQueue.Copy(TempSyncContacts, true);
TempQueue.SetRange("Sync Direction", 1);
exit(TempQueue.Count());
end;
local procedure GetSyncToM365Count(): Integer
var
TempQueue: Record "Contact Sync Queue" temporary;
begin
TempQueue.Copy(TempSyncContacts, true);
TempQueue.SetRange("Sync Direction", 0);
exit(TempQueue.Count());
end;
procedure AddFolderOption(var TempFolder: Record "Contact Sync Folder" temporary; FolderId: Text; DisplayName: Text)
begin
NextEntryNo += 1;
TempFolder."Entry No." := NextEntryNo;
TempFolder."Folder ID" := CopyStr(FolderId, 1, 2048);
TempFolder."Display Name" := CopyStr(DisplayName, 1, 250);
TempFolder.Insert();
end;
procedure GetSelectedFolderId(): Text
begin
exit(SelectedFolderId);
end;
local procedure GetContactsToAddOutlookLabel(): Text
begin
exit(StrSubstNo(ContactsToAddOutlookLbl, GetSyncToM365Count()));
end;
local procedure GetContactsToAddBCLabel(): Text
begin
exit(StrSubstNo(ContactsToAddBCLbl, GetSyncToBCCount()));
end;
local procedure GetSyncTimeLabel(): Text
begin
exit(StrSubstNo(SyncTimeLbl, SyncTimeText));
end;
local procedure GetContactsSentToM365Label(): Text
begin
exit(StrSubstNo(ContactsSentToM365Lbl, ContactsSentToM365Count));
end;
local procedure GetContactsSentToBCLabel(): Text
begin
exit(StrSubstNo(ContactsSentToBCLbl, ContactsSentToBCCount));
end;
local procedure GetContactsFailedLabel(): Text
begin
exit(StrSubstNo(ContactsFailedLbl, ContactsFailedCount));
end;
var
TempSyncContacts: Record "Contact Sync Queue" temporary;
TempSyncFolder: Record "Contact Sync Folder" temporary;
PreviousFolderName: Text;
SelectedFolderId: Text;
SelectedFolderName: Text;
NextEntryNo: Integer;
FolderName: Text;
Step: Option Welcome,ContactFilter,SyncOptions,Finish;
AccessToken: SecretText;
ContactFilterText: Text;
WelcomeTextTxt: Text;
SyncTimeText: Text;
BackEnabled: Boolean;
NextEnabled: Boolean;
FinishEnabled: Boolean;
NoContactsToSync: Boolean;
FullSyncOption: Boolean;
NewLineChar: Char;
Deltalink: Text;
SyncDirection: Enum "ContactSyncDirection";
ClearLastSyncConfirmMsg: Label 'This will delete the internal log of your last synchronization, but will not modify any contacts you have synchronized to date. Do you want to proceed?';
ClearSyncDetailsLbl: Label 'Clear last sync details';
LastSyncClearedMsg: Label 'Your last synchronization details were cleared. Choose ok to start over.';
ContactsSentToM365Count: Integer;
ContactsSentToBCCount: Integer;
ContactsFailedCount: Integer;
WelcomeTextLbl: Label 'This guide will help you synchronize contacts from Business Central, with your contacts stored in Microsoft 365. \\This makes business contacts easily reachable from Microsoft Teams and Outlook apps on your desktop or mobile devices.\\Choose Next to continue.';
ErrorObtainingTokenMsg: Label 'There was an error obtaining the access token. Try contacting your administrator.';
ErrTelTxt: Label 'Empty access token for Contact Synch encountered', Locked = true;
ErrorSelectFolderMsg: Label 'Please select a contact folder before proceeding.';
ErrorFolderEmptyMsg: Label 'Folder cannot be empty. Please select a folder using lookup.';
PreviewTextLbl: Label 'Choose Next to preview which contacts will be synchronized.';
ErrorInvalidFolderMsg: Label 'Please select a valid folder from the list.';
TotalRecordsMessageLbl: Label 'Total records matching the filter: %1', Comment = '%1 = Number of records';
NoFolderMsg: Label 'No contact folders were found. A default folder will be created.', Locked = true;
CategoryLbl: Label 'Contact Sync', Locked = true;
CategoryTok: Label 'Category', Locked = true;
SyncCountsTelLbl: Label 'Sync counts - BC: %1, M365: %2', Locked = true;
NoContactsToSyncTelLbl: Label 'No contacts to sync', Locked = true;
NoContactsBCToM365TelLbl: Label 'No contacts available for BC to M365 sync', Locked = true;
StartingSyncTelLbl: Label 'Starting sync - Direction: %1', Locked = true;
SyncCompletedTelLbl: Label 'Sync completed in %1', Locked = true;
SyncResultsTelLbl: Label 'Sync results - M365: %1, BC: %2, Failed: %3', Locked = true;
NoSyncMsg: Label 'No contacts to synchronize.';
FailedSyncLbl: Label 'Failed Synchronizations';
SyncedAtTxt: Label 'synchronized at';
NoSyncFoundTxt: Label 'No previous synchronization found for the selected folder.';
CaptionToSyncBCTxt: Label 'Contacts to Sync to Business Central';
CaptionToSyncO365Txt: Label 'Contacts to Sync to Microsoft 365';
ContactsToAddOutlookLbl: Label 'Contacts to add to Outlook: %1', Comment = '%1 = a number';
ContactsToAddBCLbl: Label 'Contacts to add to Business Central: %1', Comment = '%1 = a number';
SyncTimeLbl: Label 'Synchronization time: %1', Comment = '%1 = time duration';
ContactsSentToM365Lbl: Label 'Contacts sent to Outlook and Teams: %1', Comment = '%1 = a number';
ContactsSentToBCLbl: Label 'Contacts sent to Business Central: %1', Comment = '%1 = a number';
ContactsFailedLbl: Label 'Contacts failed to synchronize: %1', Comment = '%1 = a number';
ReadyTextTxt: Text;
DisclaimerLbl: Label 'Updating Business Central will copy all contacts from the selected Outlook or Teams folder and make them visible to other Business Central users. This may include any personal contacts stored in that folder. Do you want to continue?';
SyncAcknowledgementTelTxt: Label 'User acknowledged disclaimer for sync direction: Full Sync', Locked = true;
}