Codeunit 1281 Update Currency Exchange Rates
- App
- Base Application
- Namespace
- Microsoft.Finance.Currency
- Versions
- 17-28
Versions171819202122232425262728
Source29
Source in 29
src/Layers/W1/BaseApp/Finance/Currency/UpdateCurrencyExchangeRates.Codeunit.al272 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 Microsoft.Utilities;
using System;
using System.Environment.Configuration;
using System.Integration;
using System.IO;
using System.Utilities;
using System.Xml;
/// <summary>
/// Manages automatic updates of currency exchange rates from external services.
/// Provides functionality to synchronize exchange rates, handle data transformations,
/// and maintain currency rate accuracy through automated scheduled updates.
/// </summary>
/// <remarks>
/// Integrates with Data Exchange Framework for rate import processing.
/// Supports multiple currency exchange rate services and includes error handling
/// and telemetry logging for monitoring service reliability.
/// </remarks>
codeunit 1281 "Update Currency Exchange Rates"
{
Permissions = TableData "Data Exch." = rimd;
trigger OnRun()
begin
SyncCurrencyExchangeRates();
end;
var
TempBlobResponse: Codeunit "Temp Blob";
HttpWebRequestMgt: Codeunit "Http Web Request Mgt.";
NoSyncCurrencyExchangeRatesSetupErr: Label 'There are no active Currency Exchange Rate Sync. Setup records.';
MissingExchRateNotificationNameTxt: Label 'Missing Exchange Rates';
MissingExchRateNotificationDescriptionTxt: Label 'Show warning to enter exchange rates when a new currency is created.';
NotificationActionDisableTxt: Label 'Don''t show me again';
NotificationActionOpenPageTxt: Label 'Do it now';
#pragma warning disable AA0470
NotificationMessageMsg: Label 'Exchange rates for %1 need to be configured.', Comment = 'Currency Code';
#pragma warning restore AA0470
ExchRatesUpdatedTxt: Label 'The user updated currency exchange rates via a currency exchange rate service.', Locked = true;
TelemetryCategoryTok: Label 'AL Exchange Rate Service', Locked = true;
local procedure SyncCurrencyExchangeRates()
var
CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup";
ResponseInStream: InStream;
SourceName: Text;
begin
CurrExchRateUpdateSetup.SetRange(Enabled, true);
OnSyncCurrencyExchangeRatesOnBeforeFindCurrExchRateUpdateSetup(CurrExchRateUpdateSetup);
if CurrExchRateUpdateSetup.FindSet() then
repeat
OnBeforeSyncCurrencyExchangeRatesLoop(CurrExchRateUpdateSetup);
GetCurrencyExchangeData(CurrExchRateUpdateSetup, ResponseInStream, SourceName);
UpdateCurrencyExchangeRates(CurrExchRateUpdateSetup, ResponseInStream, SourceName);
LogTelemetryWhenExchangeRateUpdated();
until CurrExchRateUpdateSetup.Next() = 0
else
Error(NoSyncCurrencyExchangeRatesSetupErr);
end;
[Scope('OnPrem')]
procedure UpdateCurrencyExchangeRates(CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup"; CurrencyExchRatesDataInStream: InStream; SourceName: Text)
var
DataExch: Record "Data Exch.";
DataExchDef: Record "Data Exch. Def";
begin
DataExchDef.Get(CurrExchRateUpdateSetup."Data Exch. Def Code");
CreateDataExchange(DataExch, DataExchDef, CurrencyExchRatesDataInStream, CopyStr(SourceName, 1, 250));
DataExchDef.ProcessDataExchange(DataExch);
DataExch.Delete(true);
end;
local procedure GetCurrencyExchangeData(var CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup"; var ResponseInStream: InStream; var SourceName: Text)
var
ServiceUrl: Text;
Handled: Boolean;
begin
Clear(TempBlobResponse);
OnBeforeGetCurrencyExchangeData(CurrExchRateUpdateSetup, ResponseInStream, SourceName, Handled, TempBlobResponse);
TempBlobResponse.CreateInStream(ResponseInStream);
if Handled then
exit;
ExecuteWebServiceRequest(CurrExchRateUpdateSetup, ResponseInStream);
CurrExchRateUpdateSetup.GetWebServiceURL(ServiceUrl);
SourceName := ServiceUrl;
end;
local procedure CreateDataExchange(var DataExch: Record "Data Exch."; DataExchDef: Record "Data Exch. Def"; ResponseInStream: InStream; SourceName: Text[250])
var
TempBlob: Codeunit "Temp Blob";
GetJsonStructure: Codeunit "Get Json Structure";
OutStream: OutStream;
BlankInStream: InStream;
begin
if DataExchDef."File Type" = DataExchDef."File Type"::Json then begin
TempBlob.CreateInStream(BlankInStream);
DataExch.InsertRec(SourceName, BlankInStream, DataExchDef.Code);
DataExch."File Content".CreateOutStream(OutStream);
if not GetJsonStructure.JsonToXML(ResponseInStream, OutStream) then
GetJsonStructure.JsonToXMLCreateDefaultRoot(ResponseInStream, OutStream);
DataExch.Modify(true);
end else
DataExch.InsertRec(SourceName, ResponseInStream, DataExchDef.Code);
CODEUNIT.Run(DataExchDef."Reading/Writing Codeunit", DataExch);
end;
local procedure ExecuteWebServiceRequest(CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup"; var ResponseInStream: InStream)
var
HttpStatusCode: DotNet HttpStatusCode;
ResponseHeaders: DotNet NameValueCollection;
URL: Text;
begin
CurrExchRateUpdateSetup.GetWebServiceURL(URL);
HttpWebRequestMgt.Initialize(URL);
HttpWebRequestMgt.SetReturnType('application/xml,text/xml');
if not GuiAllowed then
HttpWebRequestMgt.DisableUI();
HttpWebRequestMgt.SetTraceLogEnabled(CurrExchRateUpdateSetup."Log Web Requests");
if not HttpWebRequestMgt.GetResponse(ResponseInStream, HttpStatusCode, ResponseHeaders) then
ShowHttpError(CurrExchRateUpdateSetup, URL);
end;
procedure GenerateTempDataFromService(var TempCurrencyExchangeRate: Record "Currency Exchange Rate" temporary; CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup")
var
DataExch: Record "Data Exch.";
DataExchDef: Record "Data Exch. Def";
MapCurrencyExchangeRate: Codeunit "Map Currency Exchange Rate";
ResponseInStream: InStream;
SourceName: Text;
begin
GetCurrencyExchangeData(CurrExchRateUpdateSetup, ResponseInStream, SourceName);
DataExchDef.Get(CurrExchRateUpdateSetup."Data Exch. Def Code");
CreateDataExchange(DataExch, DataExchDef, ResponseInStream, CopyStr(SourceName, 1, 250));
MapCurrencyExchangeRate.MapCurrencyExchangeRates(DataExch, TempCurrencyExchangeRate);
DataExch.Delete(true);
end;
local procedure ShowHttpError(CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup"; WebServiceURL: Text)
var
ActivityLog: Record "Activity Log";
WebRequestHelper: Codeunit "Web Request Helper";
XMLDOMMgt: Codeunit "XML DOM Management";
WebException: DotNet WebException;
XmlNode: DotNet XmlNode;
ResponseInputStream: InStream;
ErrorText: Text;
begin
ErrorText := WebRequestHelper.GetWebResponseError(WebException, WebServiceURL);
ActivityLog.LogActivity(
CurrExchRateUpdateSetup, ActivityLog.Status::Failed, CurrExchRateUpdateSetup."Service Provider",
CurrExchRateUpdateSetup.Description, ErrorText);
if IsNull(WebException.Response) then
Error(ErrorText);
ResponseInputStream := WebException.Response.GetResponseStream();
XMLDOMMgt.LoadXMLNodeFromInStream(ResponseInputStream, XmlNode);
ErrorText := WebException.Message;
Error(ErrorText);
end;
/// <summary>
/// Event is raised before getting currency exchange data from web service.
/// </summary>
/// <param name="ResponseInStream">Do not use - InStream is empty and not writable. Use TempBlobResponse parameter instead.</param>
/// <param name="TempBlobResponse">TempBlob to write the currency exchange data to. Use CreateOutStream() to get OutStream for writing.</param>
[IntegrationEvent(false, false)]
local procedure OnBeforeGetCurrencyExchangeData(var CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup"; var ResponseInStream: InStream; var SourceName: Text; var Handled: Boolean; var TempBlobResponse: Codeunit "Temp Blob")
begin
end;
procedure OpenCurrencyExchangeRatesPageFromNotification(Notification: Notification)
var
CurrencyExchangeRate: Record "Currency Exchange Rate";
CurrencyCode: Code[10];
begin
CurrencyCode := Notification.GetData('Currency Code');
CurrencyExchangeRate.SetRange("Currency Code", CurrencyCode);
PAGE.RunModal(PAGE::"Currency Exchange Rates", CurrencyExchangeRate);
end;
procedure DisableMissingExchangeRatesNotification(Notification: Notification)
var
MyNotifications: Record "My Notifications";
begin
if not MyNotifications.Disable(Notification.Id) then
MyNotifications.InsertDefault(
Notification.Id,
MissingExchRateNotificationNameTxt,
MissingExchRateNotificationDescriptionTxt,
false);
end;
procedure ShowMissingExchangeRatesNotification(CurrencyCode: Code[10])
var
MyNotifications: Record "My Notifications";
Notification: Notification;
begin
if not MyNotifications.IsEnabled(GetMissingExchangeRatesNotificationID()) then
exit;
Notification.Id := GetMissingExchangeRatesNotificationID();
Notification.Message := StrSubstNo(NotificationMessageMsg, CurrencyCode);
Notification.SetData('Currency Code', CurrencyCode);
Notification.AddAction(NotificationActionOpenPageTxt, 1281, 'OpenCurrencyExchangeRatesPageFromNotification');
Notification.AddAction(NotificationActionDisableTxt, 1281, 'DisableMissingExchangeRatesNotification');
Notification.Send();
end;
procedure ExchangeRatesForCurrencyExist(Date: Date; CurrencyCode: Code[10]): Boolean
var
CurrencyExchangeRate: Record "Currency Exchange Rate";
begin
if Date = 0D then
Date := WorkDate();
CurrencyExchangeRate.SetRange("Currency Code", CurrencyCode);
CurrencyExchangeRate.SetRange("Starting Date", 0D, Date);
exit(CurrencyExchangeRate.FindLast());
end;
procedure OpenExchangeRatesPage(CurrencyCode: Code[10])
var
CurrencyExchangeRate: Record "Currency Exchange Rate";
begin
CurrencyExchangeRate.SetRange("Currency Code", CurrencyCode);
PAGE.RunModal(PAGE::"Currency Exchange Rates", CurrencyExchangeRate);
end;
procedure GetMissingExchangeRatesNotificationID(): Guid
begin
exit('911e69ab-73a1-4e08-931b-cf21f0d118f2');
end;
local procedure LogTelemetryWhenExchangeRateUpdated()
begin
Session.LogMessage('000089F', ExchRatesUpdatedTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTok);
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeSyncCurrencyExchangeRatesLoop(var CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup")
begin
end;
/// <summary>
/// Integration event raised after the enabled filter is applied to the currency exchange rate update setup and before the setup records are read.
/// Enables subscribers to apply additional filters to the setup before the synchronization loop runs.
/// </summary>
/// <param name="CurrExchRateUpdateSetup">Currency exchange rate update setup, passed by reference so subscribers can apply additional filters.</param>
[IntegrationEvent(false, false)]
local procedure OnSyncCurrencyExchangeRatesOnBeforeFindCurrExchRateUpdateSetup(var CurrExchRateUpdateSetup: Record "Curr. Exch. Rate Update Setup")
begin
end;
}