Table 1650 Curr. Exch. Rate Update Setup
- App
- Base Application
- Namespace
- Microsoft.Finance.Currency
- Versions
- 17-28
Fields, 8Keys, 1Procedures, 8Events, 5
Versions171819202122232425262728
Source29
Source in 29
src/Layers/W1/BaseApp/Finance/Currency/CurrExchRateUpdateSetup.Table.al448 lines, Copyright (c) Microsoft Corporation. MIT
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Finance.Currency;
using System.Integration;
using System.IO;
using System.Privacy;
using System.Telemetry;
using System.Threading;
/// <summary>
/// Manages configuration for automated currency exchange rate update services.
/// Enables integration with third-party exchange rate providers for automatic daily updates.
/// </summary>
/// <remarks>
/// Integrates with Data Exchange Framework, Job Queue, and Customer Consent Management.
/// Supports extensibility through OnBeforeSetupCurrencyExchRateService, OnAfterGetWebServiceURL,
/// OnBeforeAutoUpdateExchangeRates, and OnBeforeEnsureURLIsHttpAndValidUri events.
/// </remarks>
table 1650 "Curr. Exch. Rate Update Setup"
{
Caption = 'Curr. Exch. Rate Update Setup';
DataCaptionFields = "Code", Description;
DataClassification = CustomerContent;
fields
{
/// <summary>
/// Unique identifier for the currency exchange rate service configuration.
/// Automatically creates a data exchange definition when entered.
/// </summary>
field(1; "Code"; Code[20])
{
Caption = 'Code';
ToolTip = 'Specifies the setup of a service to update currency exchange rates.';
NotBlank = true;
trigger OnValidate()
var
DataExchCode: Code[20];
begin
if "Data Exch. Def Code" = '' then begin
DataExchCode := SuggestDataExchangeCode();
CreateDataExchangeDefinition(DataExchCode);
Validate("Data Exch. Def Code", DataExchCode);
end;
end;
}
/// <summary>
/// Human-readable description of the exchange rate service and its purpose.
/// Used in user interfaces and job queue descriptions for service identification.
/// </summary>
field(2; Description; Text[250])
{
Caption = 'Description';
ToolTip = 'Specifies the setup of a service to update currency exchange rates.';
}
/// <summary>
/// Stores the complete web service URL for the exchange rate provider endpoint.
/// BLOB storage protects URLs containing API keys or authentication parameters.
/// </summary>
field(3; "Web Service URL"; BLOB)
{
Caption = 'Service URL';
}
/// <summary>
/// Controls whether automatic exchange rate updates are active for this service.
/// Validates user consent, service configuration, and manages job queue scheduling.
/// </summary>
field(5; Enabled; Boolean)
{
Caption = 'Enabled';
ToolTip = 'Specifies if the currency exchange rate service is enabled.';
trigger OnValidate()
var
AuditLog: Codeunit "Audit Log";
CustomerConsentMgt: Codeunit "Customer Consent Mgt.";
CurrExchRateUpdateConsentProvidedLbl: Label 'Curr. Exch. Rate Update Setup - consent provided by UserSecurityId %1.', Locked = true;
begin
if not xRec."Enabled" and Rec."Enabled" then
Rec."Enabled" := CustomerConsentMgt.ConfirmUserConsent();
if Rec.Enabled then begin
VerifyServiceURL();
VerifyDataExchangeLineDefinition();
AutoUpdateExchangeRates();
LogTelemetryWhenServiceEnabled();
AuditLog.LogAuditMessage(StrSubstNo(CurrExchRateUpdateConsentProvidedLbl, UserSecurityId()), SecurityOperationResult::Success, AuditCategory::ApplicationManagement, 4, 0);
end else
LogTelemetryWhenServiceDisabled();
end;
}
/// <summary>
/// Name of the external service provider supplying exchange rate data.
/// Used for user identification and support contact purposes.
/// </summary>
field(10; "Service Provider"; Text[30])
{
Caption = 'Service Provider';
ToolTip = 'Specifies the name of the service provider.';
}
/// <summary>
/// URL to the service provider's terms of service or usage agreement.
/// Enables users to review legal terms before service activation.
/// </summary>
field(11; "Terms of Service"; Text[250])
{
Caption = 'Terms of Service';
ToolTip = 'Specifies the URL of the service provider''s terms of service.';
ExtendedDatatype = URL;
}
/// <summary>
/// Links to the Data Exchange Definition that defines how to parse and map service responses.
/// Auto-created during setup but can be customized for specific service formats.
/// </summary>
field(20; "Data Exch. Def Code"; Code[20])
{
Caption = 'Data Exch. Def Code';
TableRelation = "Data Exch. Def".Code;
}
/// <summary>
/// Enables detailed logging of web service requests and responses for troubleshooting.
/// Should be used temporarily for debugging service integration issues.
/// </summary>
field(21; "Log Web Requests"; Boolean)
{
Caption = 'Log Web Requests';
ToolTip = 'Specifies whether to log exceptions that occur when connecting to the service. The log is located in the server Temp folder.';
}
}
keys
{
key(Key1; "Code")
{
Clustered = true;
}
}
fieldgroups
{
}
trigger OnDelete()
var
DataExchDef: Record "Data Exch. Def";
begin
if DataExchDef.Get("Data Exch. Def Code") then
DataExchDef.Delete(true);
end;
trigger OnInsert()
begin
LogTelemetryWhenServiceCreated();
end;
var
DataExchangeCurrencyNosTxt: Label 'CURRENCY0001', Comment = 'Used to populate the Code field in Table 1222. It''s value must start or end with a number';
DataExchangeLineDefCodeTxt: Label 'CurrExchange', Locked = true;
DataExchangeLineDefNameTxt: Label 'Parent Node for Currency Code';
DataExchangeMappingDefNameTxt: Label 'Mapping for Currency Exchange';
MissingDataLineTagErr: Label '%1 for %2 must not be blank.', Comment = '%1 - source XML node; %2 - parent node for caption code';
MissingServiceURLErr: Label 'The %1 field must not be blank.', Comment = '%1 - Service URL';
DailyUpdateQst: Label 'A job queue entry for daily update of exchange rates has been created.\\Do you want to open the Job Queue Entries window?';
ExchRateServiceCreatedTxt: Label 'The user started setting up a currency exchange rate service.', Locked = true;
ExchRateServiceEnabledTxt: Label 'The user enabled a currency exchange rate service.', Locked = true;
ExchRateServiceDisabledTxt: Label 'The user disabled a currency exchange rate service.', Locked = true;
TelemetryCategoryTok: Label 'AL Exchange Rate Service', Locked = true;
JobQueueEntryDescriptionTxt: Label '%1 - recurring update of exchange rates', Comment = '%1 - the code of the exchange rate setup';
UnableToSetupCurrExchangeRateServiceTxt: Label 'An error has occured when trying to setup Currency Exchange Service. Error %1, Callstack %2', Comment = '%1 - Error Message, %2 - Callstack', Locked = true;
/// <summary>
/// Retrieves the web service URL from BLOB storage with extensibility for URL transformation.
/// Triggers OnAfterGetWebServiceURL event for custom URL modification.
/// </summary>
/// <param name="ServiceURL">Output parameter containing the retrieved URL</param>
/// <returns>The web service URL as stored in the BLOB field</returns>
procedure GetWebServiceURL(var ServiceURL: Text) WebServiceURL: Text
var
InStream: InStream;
begin
CalcFields("Web Service URL");
if "Web Service URL".HasValue() then begin
"Web Service URL".CreateInStream(InStream);
InStream.Read(ServiceURL);
end;
WebServiceURL := ServiceURL;
OnAfterGetWebServiceURL(ServiceURL);
end;
/// <summary>
/// Validates and stores a web service URL in BLOB field after security validation.
/// Ensures URLs meet HTTP/HTTPS protocol requirements.
/// </summary>
/// <param name="ServiceURL">The URL to validate and store</param>
procedure SetWebServiceURL(ServiceURL: Text)
var
OutStream: OutStream;
begin
EnsureURLIsHttpAndValidUri(ServiceURL);
"Web Service URL".CreateOutStream(OutStream);
OutStream.Write(ServiceURL);
Modify();
end;
local procedure EnsureURLIsHttpAndValidUri(ServiceURL: Text)
var
WebRequestHelper: Codeunit "Web Request Helper";
IsHandled: Boolean;
begin
IsHandled := false;
OnBeforeEnsureURLIsHttpAndValidUri(ServiceURL, IsHandled);
if IsHandled then
exit;
WebRequestHelper.IsValidUri(ServiceURL);
WebRequestHelper.IsHttpUrl(ServiceURL);
end;
local procedure SuggestDataExchangeCode() NewDataExchCode: Code[20]
var
DataExchDef: Record "Data Exch. Def";
begin
NewDataExchCode := DataExchangeCurrencyNosTxt;
while DataExchDef.Get(NewDataExchCode) do begin
if NewDataExchCode = IncStr(NewDataExchCode) then
exit(Code);
NewDataExchCode := IncStr(NewDataExchCode);
end;
end;
/// <summary>
/// Creates complete Data Exchange Definition setup for currency exchange rate import.
/// Establishes Data Exchange Definition, Line Definition, and Field Mapping records.
/// </summary>
/// <param name="DataExchCode">Unique code for the new data exchange definition</param>
procedure CreateDataExchangeDefinition(DataExchCode: Code[20])
var
DataExchDef: Record "Data Exch. Def";
DataExchLineDef: Record "Data Exch. Line Def";
DataExchMapping: Record "Data Exch. Mapping";
begin
if DataExchDef.Get(DataExchCode) then
exit;
DataExchDef.Init();
DataExchDef.Code := DataExchCode;
DataExchDef.Name := Code;
DataExchDef.Type := DataExchDef.Type::"Generic Import";
DataExchDef."Reading/Writing Codeunit" := CODEUNIT::"Import XML File to Data Exch.";
DataExchDef.Insert(true);
DataExchLineDef.Init();
DataExchLineDef."Data Exch. Def Code" := DataExchDef.Code;
DataExchLineDef.Code := DataExchangeLineDefCodeTxt;
DataExchLineDef.Name := DataExchangeLineDefNameTxt;
DataExchLineDef.Insert(true);
DataExchMapping.Init();
DataExchMapping."Data Exch. Def Code" := DataExchDef.Code;
DataExchMapping."Data Exch. Line Def Code" := DataExchangeLineDefCodeTxt;
DataExchMapping.Name := DataExchangeMappingDefNameTxt;
DataExchMapping."Table ID" := DATABASE::"Currency Exchange Rate";
DataExchMapping."Mapping Codeunit" := CODEUNIT::"Map Currency Exchange Rate";
DataExchMapping.Insert(true);
end;
/// <summary>
/// Analyzes XML structure of web service response for data exchange mapping setup.
/// Populates XML Buffer with node hierarchy for configuration purposes.
/// </summary>
/// <param name="XMLBuffer">Buffer to store the parsed XML structure</param>
/// <param name="ServiceURL">URL of the web service to analyze</param>
[TryFunction]
[Scope('OnPrem')]
procedure GetXMLStructure(var XMLBuffer: Record "XML Buffer"; ServiceURL: Text)
var
XMLBufferWriter: Codeunit "XML Buffer Writer";
begin
XMLBufferWriter.GenerateStructureFromPath(XMLBuffer, ServiceURL);
end;
local procedure AutoUpdateExchangeRates()
var
JobQueueEntry: Record "Job Queue Entry";
DummyRecId: RecordID;
IsHandled: Boolean;
begin
IsHandled := false;
OnBeforeAutoUpdateExchangeRates(Rec, IsHandled);
if IsHandled then
exit;
if Enabled then begin
JobQueueEntry.ScheduleRecurrentJobQueueEntryWithFrequency(JobQueueEntry."Object Type to Run"::Codeunit,
CODEUNIT::"Update Currency Exchange Rates", DummyRecId, 24 * 60, 3, 3600, 0T);
JobQueueEntry.Description := StrSubstNo(JobQueueEntryDescriptionTxt, GetDescription());
JobQueueEntry.Modify();
if Confirm(DailyUpdateQst) then
PAGE.Run(PAGE::"Job Queue Entry Card", JobQueueEntry);
end else
if JobQueueEntry.FindJobQueueEntry(JobQueueEntry."Object Type to Run"::Codeunit,
CODEUNIT::"Update Currency Exchange Rates")
then
JobQueueEntry.Cancel();
end;
/// <summary>
/// Validates that data exchange definition has proper XML node configuration.
/// Ensures Data Line Tag is configured before enabling service.
/// </summary>
procedure VerifyDataExchangeLineDefinition()
var
DataExchLineDef: Record "Data Exch. Line Def";
DataExchFieldMappingBuf: Record "Data Exch. Field Mapping Buf.";
begin
DataExchLineDef.SetRange("Data Exch. Def Code", "Data Exch. Def Code");
DataExchLineDef.SetRange("Parent Code", '');
DataExchLineDef.FindFirst();
if DataExchLineDef."Data Line Tag" = '' then
Error(MissingDataLineTagErr, DataExchFieldMappingBuf.FieldCaption(Source), DataExchangeLineDefNameTxt);
end;
/// <summary>
/// Validates that web service URL is configured before enabling service.
/// </summary>
procedure VerifyServiceURL()
begin
if not "Web Service URL".HasValue() then
Error(MissingServiceURLErr, FieldCaption("Web Service URL"));
end;
/// <summary>
/// Opens Job Queue Entry card for monitoring scheduled currency updates.
/// Supports extensibility through OnShowJobQueueEntryOnBeforeJobQueueEntryFindFirst event.
/// </summary>
procedure ShowJobQueueEntry()
var
JobQueueEntry: Record "Job Queue Entry";
IsHandled: Boolean;
begin
JobQueueEntry.SetRange("Object Type to Run", JobQueueEntry."Object Type to Run"::Codeunit);
JobQueueEntry.SetRange("Object ID to Run", CODEUNIT::"Update Currency Exchange Rates");
IsHandled := false;
OnShowJobQueueEntryOnBeforeJobQueueEntryFindFirst(Rec, JobQueueEntry, IsHandled);
if IsHandled then
exit;
if JobQueueEntry.FindFirst() then
PAGE.Run(PAGE::"Job Queue Entry Card", JobQueueEntry);
end;
/// <summary>
/// Initializes exchange rate service configuration when no services exist.
/// Runs setup codeunit with error handling and telemetry logging.
/// </summary>
procedure SetupService()
begin
OnBeforeSetupCurrencyExchRateService(Rec);
if IsEmpty() then begin
Commit();
if not CODEUNIT.Run(CODEUNIT::"Set Up Curr Exch Rate Service") then
Session.LogMessage('0000COD', StrSubstNo(UnableToSetupCurrExchangeRateServiceTxt, GetLastErrorText(), GetLastErrorCallStack()), Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTok);
end;
end;
local procedure GetDescription(): Text
begin
if Description <> '' then
exit(Description);
exit(Code);
end;
/// <summary>
/// Allows customization of web service URLs after retrieval from storage.
/// </summary>
/// <param name="ServiceURL">The service URL that can be modified by subscribers</param>
[IntegrationEvent(true, false)]
local procedure OnAfterGetWebServiceURL(var ServiceURL: Text)
begin
end;
/// <summary>
/// Enables customization of currency exchange rate service setup process.
/// </summary>
/// <param name="CurrExchRateUpdateSetup">The setup record that can be modified during setup</param>
[IntegrationEvent(false, false)]
procedure OnBeforeSetupCurrencyExchRateService(var CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup")
begin
end;
/// <summary>
/// Provides extensibility for custom URL validation logic before storing service URLs.
/// </summary>
/// <param name="ServiceURL">The URL being validated</param>
/// <param name="IsHandled">Set to true to skip standard validation</param>
[IntegrationEvent(false, false)]
local procedure OnBeforeEnsureURLIsHttpAndValidUri(ServiceURL: Text; var IsHandled: Boolean)
begin
end;
/// <summary>
/// Allows customization of job queue entry lookup when displaying scheduling information.
/// </summary>
/// <param name="CurrExchRateUpdateSetup">The setup record requesting job queue display</param>
/// <param name="JobQueueEntry">The job queue entry record that can be filtered or modified</param>
/// <param name="IsHandled">Set to true to skip standard job queue entry lookup</param>
[IntegrationEvent(false, false)]
local procedure OnShowJobQueueEntryOnBeforeJobQueueEntryFindFirst(var CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup"; var JobQueueEntry: Record "Job Queue Entry"; var IsHandled: Boolean)
begin
end;
/// <summary>
/// Enables customization of automatic exchange rate update scheduling and execution.
/// </summary>
/// <param name="CurrExchRateUpdateSetup">The setup record being processed for automatic updates</param>
/// <param name="IsHandled">Set to true to skip standard job queue scheduling</param>
[IntegrationEvent(false, false)]
local procedure OnBeforeAutoUpdateExchangeRates(var CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup"; var IsHandled: boolean)
begin
end;
local procedure LogTelemetryWhenServiceEnabled()
begin
Session.LogMessage('00008AE', ExchRateServiceEnabledTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTok);
end;
local procedure LogTelemetryWhenServiceDisabled()
begin
Session.LogMessage('00008AG', ExchRateServiceDisabledTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTok);
end;
local procedure LogTelemetryWhenServiceCreated()
begin
Session.LogMessage('00008AI', ExchRateServiceCreatedTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTok);
end;
}