Codeunit 9660 Report Layouts Impl., source in 29
Source29
src/Layers/W1/BaseApp/Foundation/Reporting/ReportLayoutsImpl.codeunit.al1219 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.Shared.Report;
using Microsoft.EServices.EDocument;
using Microsoft.Foundation.Reporting;
using System;
using System.Environment;
using System.Environment.Configuration;
using System.IO;
using System.Reflection;
using System.Security.AccessControl;
using System.Telemetry;
using System.Utilities;
/// <summary>
/// This code unit supports the 'Report Layouts' page and provides implementations for adding/deleting/editing user and extension defined report layouts.
/// </summary>
codeunit 9660 "Report Layouts Impl."
{
Access = Internal;
Permissions = tabledata "Tenant Report Layout" = rimd,
tabledata "Tenant Report Layout Selection" = rimd,
tabledata "Tenant Report Layout Override" = rimd,
tabledata Company = r;
var
TenantReportLayoutSelection: Record "Tenant Report Layout Selection";
SelectedCompany: Text[30];
EmptyGuid: Guid;
ImportWordTxt: Label 'Choose Word layout file';
ImportRdlcTxt: Label 'Choose RDLC layout file';
ImportExcelTxt: Label 'Choose Excel layout file';
ImportExternalTxt: Label 'Choose External layout file';
DefaultLayoutDeleteTxt: Label 'You are about to delete the currently selected default layout "%1", for report "%2". Do you want to continue? A new default layout must be selected manually from the Report Layout Selection page.', Comment = '%1 = Layout Name, %2 = Report Name';
DefaultLayoutSetTxt: Label '"%1" has been set as the default layout for Report "%2"', Comment = '%1 = Layout Name, %2 = Report Name';
FileFilterWordTxt: Label 'Word Files (*.docx)|*.docx', Comment = '{Split=r''\|''}{Locked=s''1''}';
ImportWordTemplateTxt: Label 'Choose Word template file';
ImportWordHeaderFooterTxt: Label 'Choose header/footer Word file';
FileFilterWordTemplateTxt: Label 'Word Templates (*.dotx)|*.dotx', Comment = '{Split=r''\|''}{Locked=s''1''}';
FileFilterRdlcTxt: Label 'SQL Report Builder (*.rdl;*.rdlc)|*.rdl;*.rdlc', Comment = '{Split=r''\|''}{Locked=s''1''}';
FileFilterExcelTxt: Label 'Excel Files (*.xlsx)|*.xlsx', Comment = '{Split=r''\|''}{Locked=s''1''}';
FileFilterExternalTxt: Label 'All Files (*.*)|*.*', Comment = '{Split=r''\|''}{Locked=s''1''}';
EmptyLayoutNameTxt: Label 'A layout name must be specified.';
CannotUpdateLayoutTxt: Label 'The Layout could not be updated for export. The exported file will contain the original layout.';
LayoutAlreadyExistsErr: Label 'A layout named "%1" already exists.', Comment = '%1 = Layout Name';
MixedScopeErr: Label 'The selected layouts have different scopes. Some apply to all companies and some only to the current company. Select layouts of a single scope and try again.';
EmptyLayoutFileErr: Label 'The file "%1" is empty. Choose a file that contains a layout and try again.', Comment = '%1 = File Name';
EmptyGeneratedLayoutErr: Label 'A blank layout could not be generated for this report. Clear the blank layout option and choose a layout file instead.';
CannotChangeStatusOfDefaultErr: Label 'You cannot set "%1" to %2 while it is the default layout for report "%3". Select another default layout first.', Comment = '%1 = Layout Name, %2 = New Status, %3 = Report Name';
CannotChangeStatusOfDefaultInCompanyErr: Label 'You cannot set "%1" to %2 because it is the default layout for report "%3" in company "%4", and the status applies to all companies. Select another default layout in that company first.', Comment = '%1 = Layout Name, %2 = New Status, %3 = Report Name, %4 = Company Name';
DefaultFallbackTxt: Label '"%1" is the layout that report "%2" falls back to, and it is no longer approved. The report keeps using it until you select another default layout.', Comment = '%1 = Layout Name, %2 = Report Name';
internal procedure SetSelectedCompany(NewCompanyName: Text)
begin
SelectedCompany := CopyStr(NewCompanyName, 1, MaxStrLen(SelectedCompany));
end;
local procedure OverrideCompany(): Text[30]
begin
// Falls back: "Report Theme and Header/Footer" calls SetLayoutStatusBatch without SetSelectedCompany.
if SelectedCompany <> '' then
exit(SelectedCompany);
exit(CopyStr(CompanyName(), 1, MaxStrLen(SelectedCompany)));
end;
/// <summary>
/// Sets the status for a layout. Extension-installed layouts live in the read-only App database,
/// so their status is written as an override record rather than by copying the layout.
/// </summary>
/// <param name="ReportLayoutList">The layout record from the virtual table</param>
/// <param name="NewStatus">The new status to set</param>
/// <returns>True if the status was updated, false if the user-defined layout was not found</returns>
internal procedure SetLayoutStatus(ReportLayoutList: Record "Report Layout List"; NewStatus: Enum "Report Layout Status"): Boolean
var
TenantReportLayout: Record "Tenant Report Layout";
begin
if not ReportLayoutList."User Defined" then begin
UpsertLayoutOverride(ReportLayoutList, LayoutStatusIsGlobalScope(ReportLayoutList), false, '', true, NewStatus, false, false);
exit(true);
end;
if TenantReportLayout.Get(ReportLayoutList."Report ID", ReportLayoutList."Name", EmptyGuid) then begin
TenantReportLayout."Layout Status" := NewStatus;
TenantReportLayout.Modify(true);
exit(true);
end;
exit(false);
end;
local procedure ErrorIfLayoutIsDefault(ReportLayoutList: Record "Report Layout List"; NewStatus: Enum "Report Layout Status")
var
TenantReportLayoutSelectionLocal: Record "Tenant Report Layout Selection";
begin
if NewStatus = Enum::"Report Layout Status"::Approved then
exit;
// A default that is already unapproved loses nothing by moving between the other statuses, and blocking
// that would strand it outside the approval flow with Approved as its only reachable status.
if ReportLayoutList."Layout Status" <> Enum::"Report Layout Status"::Approved then
exit;
// One read covers the common case, where the layout is nobody's explicit default.
TenantReportLayoutSelectionLocal.SetRange("Report ID", ReportLayoutList."Report ID");
TenantReportLayoutSelectionLocal.SetRange("User ID", EmptyGuid);
TenantReportLayoutSelectionLocal.SetRange("Layout Name", ReportLayoutList."Name");
TenantReportLayoutSelectionLocal.SetRange("App ID", ReportLayoutList."Application ID");
if not TenantReportLayoutSelectionLocal.FindSet() then
exit;
repeat
if TenantReportLayoutSelectionLocal."Company Name" = OverrideCompany() then
Error(CannotChangeStatusOfDefaultErr, ReportLayoutList.Caption, Format(NewStatus), ReportLayoutList."Report Name");
until TenantReportLayoutSelectionLocal.Next() = 0;
// A company-scoped override moves the status in this company only, so no other company can be left behind.
if not StatusChangeIsGlobalScope(ReportLayoutList) then
exit;
TenantReportLayoutSelectionLocal.FindSet();
repeat
if OtherCompanyLosesItsDefault(ReportLayoutList, TenantReportLayoutSelectionLocal."Company Name") then
Error(CannotChangeStatusOfDefaultInCompanyErr, ReportLayoutList.Caption, Format(NewStatus), ReportLayoutList."Report Name", TenantReportLayoutSelectionLocal."Company Name");
until TenantReportLayoutSelectionLocal.Next() = 0;
end;
/// <summary>
/// Tells whether a global status change would leave the given company with an unapproved default layout.
/// </summary>
local procedure OtherCompanyLosesItsDefault(ReportLayoutList: Record "Report Layout List"; SelectionCompanyName: Text[30]): Boolean
var
Company: Record Company;
TenantReportLayoutOverride: Record "Tenant Report Layout Override";
begin
if SelectionCompanyName = OverrideCompany() then
exit(false);
// A selection row can outlive the company it names. Naming a company the user cannot open would block the
// status change with no way to act on the message.
if not Company.Get(SelectionCompanyName) then
exit(false);
// That company keeps its own status for this layout, so the global write never reaches it.
if TenantReportLayoutOverride.Get(ReportLayoutList."Report ID", ReportLayoutList."Name", ReportLayoutList."Runtime Package ID", SelectionCompanyName) then
if TenantReportLayoutOverride."Override Layout Status" then
exit(false);
exit(true);
end;
/// <summary>
/// Tells whether a status change on this layout applies to every company. An extension-installed layout is
/// company-scoped only while an "Override Layout Status" row exists for the current company; a user-defined
/// layout is company-scoped only when the layout itself belongs to a single company.
/// </summary>
local procedure StatusChangeIsGlobalScope(ReportLayoutList: Record "Report Layout List"): Boolean
var
TenantReportLayout: Record "Tenant Report Layout";
begin
if not ReportLayoutList."User Defined" then
exit(LayoutStatusIsGlobalScope(ReportLayoutList));
// No layout row means SetLayoutStatus will not change anything, so nothing can be left behind either.
if not TenantReportLayout.Get(ReportLayoutList."Report ID", ReportLayoutList."Name", EmptyGuid) then
exit(false);
exit(TenantReportLayout."Company Name" = '');
end;
local procedure LayoutStatusIsGlobalScope(ReportLayoutList: Record "Report Layout List"): Boolean
var
TenantReportLayoutOverride: Record "Tenant Report Layout Override";
begin
// Field-granular: only an "Override Layout Status" row makes the change company-scoped, and the
// UI no longer writes one - these come from an earlier version or a vendor install codeunit.
if TenantReportLayoutOverride.Get(ReportLayoutList."Report ID", ReportLayoutList."Name", ReportLayoutList."Runtime Package ID", OverrideCompany()) then
if TenantReportLayoutOverride."Override Layout Status" then
exit(false);
exit(true);
end;
local procedure UpsertLayoutOverride(ReportLayoutList: Record "Report Layout List"; MakeGlobal: Boolean; ApplyDescription: Boolean; NewDescription: Text[250]; ApplyStatus: Boolean; NewStatus: Enum "Report Layout Status"; ApplyObsolete: Boolean; NewIsObsolete: Boolean)
var
TenantReportLayoutOverride: Record "Tenant Report Layout Override";
OverrideCompanyName: Text[30];
OverrideExists: Boolean;
begin
if MakeGlobal then
OverrideCompanyName := ''
else
OverrideCompanyName := OverrideCompany();
OverrideExists := TenantReportLayoutOverride.Get(ReportLayoutList."Report ID", ReportLayoutList."Name", ReportLayoutList."Runtime Package ID", OverrideCompanyName);
if not OverrideExists then begin
TenantReportLayoutOverride.Init();
TenantReportLayoutOverride."Report ID" := ReportLayoutList."Report ID";
TenantReportLayoutOverride."Name" := ReportLayoutList."Name";
TenantReportLayoutOverride."Runtime Package ID" := ReportLayoutList."Runtime Package ID";
TenantReportLayoutOverride."Company Name" := OverrideCompanyName;
end;
if ApplyDescription then begin
TenantReportLayoutOverride.Description := NewDescription;
TenantReportLayoutOverride."Override Description" := true;
end;
if ApplyStatus then begin
TenantReportLayoutOverride."Layout Status" := NewStatus;
TenantReportLayoutOverride."Override Layout Status" := true;
end;
// One-way: only ever mark obsolete; never write false over a metadata-obsolete layout.
if ApplyObsolete and NewIsObsolete then begin
TenantReportLayoutOverride.IsObsolete := true;
TenantReportLayoutOverride."Override IsObsolete" := true;
end;
if OverrideExists then
TenantReportLayoutOverride.Modify(true)
else
TenantReportLayoutOverride.Insert(true);
end;
/// <summary>
/// Sets the status for multiple selected layouts.
/// Use this from page actions when user selects multiple layouts.
/// </summary>
/// <param name="ReportLayoutList">Record set with selected layouts (filtered/marked)</param>
/// <param name="NewStatus">The new status to set</param>
/// <returns>Number of layouts updated — user-defined layouts in place, extension-installed layouts
/// through a "Tenant Report Layout Override" record</returns>
internal procedure SetLayoutStatusBatch(var ReportLayoutList: Record "Report Layout List"; NewStatus: Enum "Report Layout Status"): Integer
var
CustomDimensions: Dictionary of [Text, Text];
UpdateCount: Integer;
HasGlobalScope: Boolean;
HasCompanyScope: Boolean;
FallbackMessages: List of [Text];
begin
if not ReportLayoutList.FindSet() then
exit(0);
// First pass: classify scope. User-defined layouts update in place and do not affect it.
repeat
ErrorIfLayoutIsDefault(ReportLayoutList, NewStatus);
if IsMetadataDefaultLayout(ReportLayoutList, NewStatus) then
FallbackMessages.Add(StrSubstNo(DefaultFallbackTxt, ReportLayoutList.Caption, ReportLayoutList."Report Name"));
if not ReportLayoutList."User Defined" then
if LayoutStatusIsGlobalScope(ReportLayoutList) then
HasGlobalScope := true
else
HasCompanyScope := true;
until ReportLayoutList.Next() = 0;
if HasGlobalScope and HasCompanyScope then
Error(MixedScopeErr);
ReportLayoutList.FindSet();
repeat
if SetLayoutStatus(ReportLayoutList, NewStatus) then
UpdateCount += 1;
until ReportLayoutList.Next() = 0;
if UpdateCount > 0 then begin
CustomDimensions.Add('NewStatus', Format(NewStatus));
CustomDimensions.Add('UpdateCount', Format(UpdateCount));
Log('0000RTN', 'Report layout status changed by user', CustomDimensions);
NotifyDefaultFallback(FallbackMessages);
end;
exit(UpdateCount);
end;
local procedure NotifyDefaultFallback(FallbackMessages: List of [Text])
var
FallbackNotification: Notification;
FallbackMessage: Text;
begin
foreach FallbackMessage in FallbackMessages do begin
Clear(FallbackNotification);
FallbackNotification.Message(FallbackMessage);
FallbackNotification.Scope(NotificationScope::LocalScope);
FallbackNotification.Send();
end;
end;
local procedure IsMetadataDefaultLayout(ReportLayoutList: Record "Report Layout List"; NewStatus: Enum "Report Layout Status"): Boolean
var
TenantReportLayoutSelectionLocal: Record "Tenant Report Layout Selection";
DefaultReportLayoutList: Record "Report Layout List";
begin
if NewStatus = Enum::"Report Layout Status"::Approved then
exit(false);
if TenantReportLayoutSelectionLocal.Get(ReportLayoutList."Report ID", OverrideCompany(), EmptyGuid) then
exit(false);
if not GetDefaultReportLayoutSelection(ReportLayoutList."Report ID", DefaultReportLayoutList) then
exit(false);
if DefaultReportLayoutList.Name <> ReportLayoutList.Name then
exit(false);
exit(DefaultReportLayoutList."Application ID" = ReportLayoutList."Application ID");
end;
internal procedure RunCustomReport(SelectedReportLayoutList: Record "Report Layout List")
var
DesignTimeReportSelection: codeunit "Design-time Report Selection";
begin
if SelectedReportLayoutList."Report ID" = 0 then
exit;
DesignTimeReportSelection.SetSelectedLayout(SelectedReportLayoutList.Name, SelectedReportLayoutList."Application ID");
Commit(); // Since we run the report modally, we cannot have any active transactions.
if TryRunCustomReport(SelectedReportLayoutList) then
DesignTimeReportSelection.ClearLayoutSelection()
else begin
DesignTimeReportSelection.ClearLayoutSelection();
Error(GetLastErrorText());
end;
end;
[TryFunction]
local procedure TryRunCustomReport(SelectedReportLayoutList: Record "Report Layout List")
begin
Report.RunModal(SelectedReportLayoutList."Report ID");
end;
local procedure AddLayoutSelection(SelectedReportLayoutList: Record "Report Layout List"; UserId: Guid): Boolean
begin
TenantReportLayoutSelection.Init();
TenantReportLayoutSelection."App ID" := SelectedReportLayoutList."Application ID";
TenantReportLayoutSelection."Company Name" := SelectedCompany;
TenantReportLayoutSelection."Layout Name" := SelectedReportLayoutList."Name";
TenantReportLayoutSelection."Report ID" := SelectedReportLayoutList."Report ID";
TenantReportLayoutSelection."User ID" := UserId;
if not TenantReportLayoutSelection.Insert(true) then
TenantReportLayoutSelection.Modify(true);
end;
internal procedure CreateNewReportLayout(SelectedReportLayoutList: Record "Report Layout List"; var ReturnReportID: Integer; var ReturnLayoutName: Text)
begin
CreateNewReportLayout(SelectedReportLayoutList, Enum::"Report Layout Subtype"::Default, ReturnReportID, ReturnLayoutName);
end;
internal procedure CreateNewReportLayout(SelectedReportLayoutList: Record "Report Layout List"; ImpliedSubtype: Enum "Report Layout Subtype"; var ReturnReportID: Integer; var ReturnLayoutName: Text)
var
ReportLayoutNewDialog: Page "Report Layout New Dialog";
EffectiveSubtype: Enum "Report Layout Subtype";
begin
ReportLayoutNewDialog.SetReportID(SelectedReportLayoutList."Report ID");
if ImpliedSubtype <> Enum::"Report Layout Subtype"::Default then
ReportLayoutNewDialog.SetImpliedSubtype(ImpliedSubtype);
if ReportLayoutNewDialog.RunModal() = Action::OK then begin
EffectiveSubtype := ReportLayoutNewDialog.SelectedLayoutSubtype();
case true of
ReportLayoutNewDialog.SelectedAddCustomLayout():
InsertNewLayout(
ReportLayoutNewDialog.SelectedReportID(), ReportLayoutNewDialog.SelectedLayoutName(),
ReportLayoutNewDialog.SelectedLayoutDescription(), SelectedReportLayoutList."Layout Format"::Custom,
ReportLayoutNewDialog.SelectedLayoutIsGlobal(),
ReportLayoutNewDialog.SelectedCreateEmptyLayout(),
ReportLayoutNewDialog.SelectedExcelMultipleDataSheets(),
EffectiveSubtype,
ReturnReportID, ReturnLayoutName);
ReportLayoutNewDialog.SelectedAddWordLayout():
InsertNewLayout(
ReportLayoutNewDialog.SelectedReportID(), ReportLayoutNewDialog.SelectedLayoutName(),
ReportLayoutNewDialog.SelectedLayoutDescription(), SelectedReportLayoutList."Layout Format"::Word,
ReportLayoutNewDialog.SelectedLayoutIsGlobal(),
ReportLayoutNewDialog.SelectedCreateEmptyLayout(),
ReportLayoutNewDialog.SelectedExcelMultipleDataSheets(),
EffectiveSubtype,
ReturnReportID, ReturnLayoutName);
ReportLayoutNewDialog.SelectedAddRDLCLayout():
InsertNewLayout(
ReportLayoutNewDialog.SelectedReportID(), ReportLayoutNewDialog.SelectedLayoutName(),
ReportLayoutNewDialog.SelectedLayoutDescription(), SelectedReportLayoutList."Layout Format"::RDLC,
ReportLayoutNewDialog.SelectedLayoutIsGlobal(),
ReportLayoutNewDialog.SelectedCreateEmptyLayout(),
ReportLayoutNewDialog.SelectedExcelMultipleDataSheets(),
Enum::"Report Layout Subtype"::Default,
ReturnReportID, ReturnLayoutName);
ReportLayoutNewDialog.SelectedAddExcelLayout():
InsertNewLayout(
ReportLayoutNewDialog.SelectedReportID(), ReportLayoutNewDialog.SelectedLayoutName(),
ReportLayoutNewDialog.SelectedLayoutDescription(), SelectedReportLayoutList."Layout Format"::Excel,
ReportLayoutNewDialog.SelectedLayoutIsGlobal(),
ReportLayoutNewDialog.SelectedCreateEmptyLayout(),
ReportLayoutNewDialog.SelectedExcelMultipleDataSheets(),
Enum::"Report Layout Subtype"::Default,
ReturnReportID, ReturnLayoutName);
end;
end;
end;
internal procedure SetDefaultReportLayoutSelection(SelectedReportLayoutList: Record "Report Layout List"; ShowMessage: Boolean)
var
ReportLayoutSelection: Record "Report Layout Selection";
CustomDimensions: Dictionary of [Text, Text];
begin
// Add to TenantReportLayoutSelection table with an Empty Guid.
AddLayoutSelection(SelectedReportLayoutList, EmptyGuid);
// Add to the report layout selection table
if ReportLayoutSelection.get(SelectedReportLayoutList."Report ID", SelectedCompany) then begin
ReportLayoutSelection.Type := GetReportLayoutSelectionCorrespondingEnum(SelectedReportLayoutList);
ReportLayoutSelection.Modify(true);
end else begin
ReportLayoutSelection."Report ID" := SelectedReportLayoutList."Report ID";
ReportLayoutSelection."Company Name" := SelectedCompany;
ReportLayoutSelection."Custom Report Layout Code" := '';
ReportLayoutSelection.Type := GetReportLayoutSelectionCorrespondingEnum(SelectedReportLayoutList);
ReportLayoutSelection.Insert(true);
end;
InitReportLayoutListDimensions(SelectedReportLayoutList, CustomDimensions);
AddReportLayoutDimensionsAction('SetDefault', CustomDimensions);
Log('0000N0D', 'Report layout default changed by user', CustomDimensions);
if ShowMessage then
Message(DefaultLayoutSetTxt, SelectedReportLayoutList."Caption", SelectedReportLayoutList."Report Name");
end;
internal procedure GetDefaultReportLayoutSelection(ReportId: Integer; var DefaultReportLayoutList: Record "Report Layout List"): Boolean
var
ReportMetadata: Record "Report Metadata";
begin
TenantReportLayoutSelection.Init();
DefaultReportLayoutList.Init();
if TenantReportLayoutSelection.Get(ReportId, OverrideCompany(), EmptyGuid) then begin
// Filter Default Report Layout List by the layout name and application id and report id
DefaultReportLayoutList.SetRange("Name", TenantReportLayoutSelection."Layout Name");
DefaultReportLayoutList.SetRange("Application ID", TenantReportLayoutSelection."App ID");
DefaultReportLayoutList.SetRange("Report ID", ReportId);
// Retrive the record based on filters
if DefaultReportLayoutList.FindFirst() then
exit(true);
end else
if ReportMetadata.Get(ReportId) then begin
DefaultReportLayoutList.SetRange("Name", ReportMetadata."DefaultLayoutName");
DefaultReportLayoutList.SetFilter("Application ID", '<>%1', EmptyGuid);
DefaultReportLayoutList.SetRange("Report ID", ReportId);
if DefaultReportLayoutList.FindFirst() then
exit(true);
end;
exit(false);
end;
internal procedure UpdateDefaultLayoutSelectionName(SelectedReportLayoutList: Record "Report Layout List"; NewLayoutName: Text[250]): Boolean
begin
if TenantReportLayoutSelection.Get(SelectedReportLayoutList."Report ID", SelectedCompany, EmptyGuid) then
if TenantReportLayoutSelection."Layout Name" = SelectedReportLayoutList."Name" then begin
TenantReportLayoutSelection."Layout Name" := NewLayoutName;
TenantReportLayoutSelection.Modify(true);
end;
end;
internal procedure ConfirmDeleteDefaultLayoutSelection(SelectedReportLayoutList: Record "Report Layout List"; TenantReportLayoutSelectionParm: Record "Tenant Report Layout Selection"): Boolean
var
ReportLayoutSelection: Record "Report Layout Selection";
begin
if Dialog.Confirm(StrSubstNo(DefaultLayoutDeleteTxt, SelectedReportLayoutList.Caption, SelectedReportLayoutList."Report Name"), false) then begin
// Clear the selection from the Tenant Report Layout Selection table.
if (TenantReportLayoutSelectionParm."Layout Name" = SelectedReportLayoutList."Name") then
TenantReportLayoutSelectionParm.Delete(true);
// Clear the selection from Report Layout Selection table and let platform set the new default layout for this report.
if ReportLayoutSelection.get(SelectedReportLayoutList."Report ID", SelectedCompany) then
ReportLayoutSelection.Delete(true);
exit(true);
end;
end;
local procedure GetReportLayoutSelectionCorrespondingEnum(SelectedReportLayoutList: Record "Report Layout List"): Integer
begin
case SelectedReportLayoutList."Layout Format" of
SelectedReportLayoutList."Layout Format"::RDLC:
exit(0);
SelectedReportLayoutList."Layout Format"::Word:
exit(1);
SelectedReportLayoutList."Layout Format"::Excel:
exit(3);
SelectedReportLayoutList."Layout Format"::Custom:
exit(4);
end
end;
internal procedure InsertNewLayout(ReportID: Integer; LayoutName: Text[250]; LayoutDescription: Text[250]; LayoutFormat: Option; LayoutIsGlobal: Boolean; CreateEmptyLayout: Boolean; ExcelSheetConfiguration: Enum "Excel Sheet Configuration"; var ReturnReportID: Integer; var ReturnLayoutName: Text)
begin
InsertNewLayout(ReportID, LayoutName, LayoutDescription, LayoutFormat, LayoutIsGlobal, CreateEmptyLayout, ExcelSheetConfiguration, Enum::"Report Layout Subtype"::Default, ReturnReportID, ReturnLayoutName);
end;
internal procedure InsertNewLayout(ReportID: Integer; LayoutName: Text[250]; LayoutDescription: Text[250]; LayoutFormat: Option; LayoutIsGlobal: Boolean; CreateEmptyLayout: Boolean; ExcelSheetConfiguration: Enum "Excel Sheet Configuration"; LayoutSubtype: Enum "Report Layout Subtype"; var ReturnReportID: Integer; var ReturnLayoutName: Text)
begin
InsertNewLayout(ReportID, LayoutName, LayoutDescription, LayoutFormat, LayoutIsGlobal, CreateEmptyLayout, ExcelSheetConfiguration, LayoutSubtype, false, ReturnReportID, ReturnLayoutName);
end;
internal procedure InsertNewLayout(ReportID: Integer; LayoutName: Text[250]; LayoutDescription: Text[250]; LayoutFormat: Option; LayoutIsGlobal: Boolean; CreateEmptyLayout: Boolean; ExcelSheetConfiguration: Enum "Excel Sheet Configuration"; LayoutSubtype: Enum "Report Layout Subtype"; ReplaceExisting: Boolean; var ReturnReportID: Integer; var ReturnLayoutName: Text)
var
TenantReportLayout: Record "Tenant Report Layout";
FileManagement: Codeunit "File Management";
DocumentReportManagement: Codeunit "Document Report Mgt.";
TempBlob: Codeunit "Temp Blob";
UploadedBlob: Codeunit "Temp Blob";
FileFilterTxt: Text;
DialogCaption: Text;
NVInStream: InStream;
EmptyLayoutStream: OutStream;
EmptyLayoutCreated: Boolean;
UploadResult: Boolean;
UploadFileName: Text;
ErrorMessage: Text;
CustomDimensions: Dictionary of [Text, Text];
begin
if ReportID = 0 then
exit;
if LayoutName = '' then begin
Message(EmptyLayoutNameTxt);
exit;
end;
if not ReplaceExisting then
if TenantReportLayout.Get(ReportID, LayoutName, EmptyGuid) then
Error(LayoutAlreadyExistsErr, LayoutName);
TenantReportLayout.Init();
TenantReportLayout."Report ID" := ReportID;
TenantReportLayout."Name" := LayoutName;
if LayoutIsGlobal then
TenantReportLayout."Company Name" := ''
else
TenantReportLayout."Company Name" := SelectedCompany;
TenantReportLayout."Layout Format" := LayoutFormat;
TenantReportLayout."Description" := LayoutDescription;
TenantReportLayout."Layout Subtype" := LayoutSubtype;
TenantReportLayout.ExcelLayoutMultipleDataSheets := ExcelSheetConfiguration;
TenantReportLayout."Layout Status" := Enum::"Report Layout Status"::Draft;
if CreateEmptyLayout then begin
EmptyLayoutCreated := false;
// RDLC documents are created with UTF8 encoding.
if TenantReportLayout."Layout Format" = TenantReportLayout."Layout Format"::RDLC then begin
EmptyLayoutStream := TempBlob.CreateOutStream(TextEncoding::UTF8);
NVInStream := TempBlob.CreateInStream(TextEncoding::UTF8);
end
else begin
EmptyLayoutStream := TempBlob.CreateOutStream();
NVInStream := TempBlob.CreateInStream();
end;
case TenantReportLayout."Layout Format" of
TenantReportLayout."Layout Format"::Word:
begin
DocumentReportManagement.NewWordLayout(ReportID, EmptyLayoutStream);
EmptyLayoutCreated := true;
UploadFileName := 'EmptyLayout.docx';
end;
TenantReportLayout."Layout Format"::RDLC:
begin
EmptyLayoutCreated := false;
DocumentReportManagement.NewRDLCLayout(ReportID, EmptyLayoutStream);
EmptyLayoutCreated := true;
UploadFileName := 'EmptyLayout.rdlc';
end;
TenantReportLayout."Layout Format"::Excel:
begin
EmptyLayoutCreated := false;
DocumentReportManagement.NewExcelLayout(ReportID, ExcelSheetConfiguration, EmptyLayoutStream);
EmptyLayoutCreated := true;
UploadFileName := 'EmptyLayout.xlsx';
end;
end;
if EmptyLayoutCreated then
if TempBlob.Length() = 0 then
Error(EmptyGeneratedLayoutErr);
end;
if (not EmptyLayoutCreated) then begin
case TenantReportLayout."Layout Format" of
TenantReportLayout."Layout Format"::Word:
case LayoutSubtype of
Enum::"Report Layout Subtype"::Theme:
begin
DialogCaption := ImportWordTemplateTxt;
FileFilterTxt := FileFilterWordTemplateTxt;
end;
Enum::"Report Layout Subtype"::HeaderFooter:
begin
DialogCaption := ImportWordHeaderFooterTxt;
FileFilterTxt := FileFilterWordTxt;
end;
else begin
DialogCaption := ImportWordTxt;
FileFilterTxt := FileFilterWordTxt;
end;
end;
TenantReportLayout."Layout Format"::RDLC:
begin
DialogCaption := ImportRdlcTxt;
FileFilterTxt := FileFilterRdlcTxt;
end;
TenantReportLayout."Layout Format"::Excel:
begin
DialogCaption := ImportExcelTxt;
FileFilterTxt := FileFilterExcelTxt;
end;
TenantReportLayout."Layout Format"::Custom:
begin
DialogCaption := ImportExternalTxt;
FileFilterTxt := FileFilterExternalTxt;
end;
end;
UploadFileName := TenantReportLayout."Name";
OnBeforeUpload(UploadResult, UploadFileName, NVInStream);
if (not UploadResult) then begin
ClearLastError();
UploadResult := UploadIntoStream(DialogCaption, '', FileFilterTxt, UploadFileName, NVInStream);
end;
if not UploadResult then begin
ErrorMessage := GetLastErrorText();
//When upload is cancelled by user, don't emit an error.
if ErrorMessage <> '' then
Error(ErrorMessage);
exit;
end;
// Custom layouts files are treated as unknown streams and don't need validation.
if TenantReportLayout."Layout Format" <> TenantReportLayout."Layout Format"::Custom then
FileManagement.ValidateFileExtension(UploadFileName, FileFilterTxt);
CopyStream(UploadedBlob.CreateOutStream(), NVInStream);
if UploadedBlob.Length() = 0 then
Error(EmptyLayoutFileErr, UploadFileName);
NVInStream := UploadedBlob.CreateInStream();
end;
if ReplaceExisting then
if TenantReportLayout.Get(TenantReportLayout."Report ID", TenantReportLayout."Name", TenantReportLayout."App ID") then
TenantReportLayout.Delete(true);
TenantReportLayout."Layout".ImportStream(NVInStream, TenantReportLayout."Description");
TenantReportLayout."MIME Type" := CreateLayoutMime(UploadFileName);
TenantReportLayout.Insert(true);
InitReportLayoutDimensions(TenantReportLayout, CustomDimensions);
AddReportLayoutDimensionsDescription(LayoutDescription, CustomDimensions);
AddReportLayoutDimensionsFormat(LayoutFormat, CustomDimensions);
AddReportLayoutDimensionsAction('New', CustomDimensions);
AddReportLayoutDimensionsExcelConfiguration(ExcelSheetConfiguration, CustomDimensions);
Log('0000N0E', 'Report layout added by user', CustomDimensions);
ReturnReportID := TenantReportLayout."Report ID";
ReturnLayoutName := TenantReportLayout."Name";
end;
internal procedure DeleteReportLayout(TenantReportLayout: Record "Tenant Report Layout")
var
CustomDimensions: Dictionary of [Text, Text];
begin
TenantReportLayout.Delete(true);
InitReportLayoutDimensions(TenantReportLayout, CustomDimensions);
AddReportLayoutDimensionsAction('Delete', CustomDimensions);
Log('0000N0F', 'Report layout deleted by user', CustomDimensions);
end;
internal procedure UpdateReportLayoutDescription(ReportID: Integer; LayoutName: Text[250]; NewDescription: Text[250])
var
TenantReportLayout: Record "Tenant Report Layout";
EmptyGuid: Guid;
begin
if not TenantReportLayout.Get(ReportID, LayoutName, EmptyGuid) then
exit;
TenantReportLayout.Description := NewDescription;
TenantReportLayout.Modify(true);
end;
internal procedure ReplaceLayout(ReportID: Integer; LayoutName: Text[250]; LayoutDescription: Text[250]; LayoutFormat: Option; var ReturnReportID: Integer; var ReturnLayoutName: Text)
var
TenantReportLayout: Record "Tenant Report Layout";
CustomDimensions: Dictionary of [Text, Text];
begin
TenantReportLayout."Report ID" := ReportID;
TenantReportLayout."Name" := LayoutName;
if TenantReportLayout.Get(ReportID, LayoutName, TenantReportLayout."App ID") then begin
InsertNewLayout(ReportID, LayoutName, LayoutDescription, LayoutFormat, TenantReportLayout."Company Name" = '', false, TenantReportLayout.ExcelLayoutMultipleDataSheets, TenantReportLayout."Layout Subtype", true, ReturnReportID, ReturnLayoutName);
InitReportLayoutDimensions(TenantReportLayout, CustomDimensions);
AddReportLayoutDimensionsDescription(LayoutDescription, CustomDimensions);
AddReportLayoutDimensionsFormat(LayoutFormat, CustomDimensions);
AddReportLayoutDimensionsAction('Replace', CustomDimensions);
Log('0000N0G', 'Report layout replaced by user', CustomDimensions);
end;
end;
internal procedure ValidateLayout(SelectedReportLayoutList: Record "Report Layout List")
var
RdlcReportManager: DotNet RdlcReportManager;
WordReportManager: DotNet WordReportManager;
ExcelReportManager: DotNet ExcelReportManager;
IsValid: Boolean;
ErrorMessage: Text;
CompositeWarnings: Text;
ValidLayoutLbl: Label 'The report layout is valid.';
ValidLayoutWithWarningsMsg: Label 'The report layout is valid.\%1', Comment = '%1 = composite part warnings';
LayoutFormatNotSupportedLbl: Label 'Layout validation is not supported for this layout format.';
begin
case SelectedReportLayoutList."Layout Format" of
SelectedReportLayoutList."Layout Format"::RDLC:
IsValid := RdlcReportManager.ValidateReportLayout(SelectedReportLayoutList."Report ID", SelectedReportLayoutList.SystemId, ErrorMessage);
SelectedReportLayoutList."Layout Format"::Word:
IsValid := WordReportManager.ValidateReportLayout(SelectedReportLayoutList."Report ID", SelectedReportLayoutList.SystemId, ErrorMessage);
SelectedReportLayoutList."Layout Format"::Excel:
IsValid := ExcelReportManager.ValidateReportLayout(SelectedReportLayoutList."Report ID", SelectedReportLayoutList.SystemId, ErrorMessage);
else begin
IsValid := false;
ErrorMessage := LayoutFormatNotSupportedLbl;
end;
end;
if IsValid then begin
CompositeWarnings := ValidateCompositePartReferences(SelectedReportLayoutList);
if CompositeWarnings <> '' then
Message(ValidLayoutWithWarningsMsg, CompositeWarnings)
else
Message(ValidLayoutLbl);
end else
Error(ErrorMessage);
end;
local procedure ValidateCompositePartReferences(SelectedReportLayoutList: Record "Report Layout List"): Text
var
TenantReportLayoutCfg: Record "Tenant Report Layout Cfg";
Warnings: TextBuilder;
PartNotFoundTxt: Label 'Header/Footer part "%1" was not found in Report Layout List.', Comment = '%1 = composite part name';
ThemeNotFoundTxt: Label 'Theme part "%1" was not found in Report Layout List.', Comment = '%1 = composite part name';
PartObsoleteTxt: Label 'Header/Footer part "%1" is marked as obsolete.', Comment = '%1 = composite part name';
ThemeObsoleteTxt: Label 'Theme part "%1" is marked as obsolete.', Comment = '%1 = composite part name';
begin
TenantReportLayoutCfg.SetRange("Report ID", SelectedReportLayoutList."Report ID");
if not TenantReportLayoutCfg.FindSet() then begin
TenantReportLayoutCfg.SetRange("Report ID", 0);
if not TenantReportLayoutCfg.FindSet() then
exit('');
end;
repeat
if TenantReportLayoutCfg."Header Part Name" <> '' then
ValidatePartReference(
TenantReportLayoutCfg."Header Part Name",
Enum::"Report Layout Subtype"::HeaderFooter,
PartNotFoundTxt, PartObsoleteTxt, Warnings);
if TenantReportLayoutCfg."Theme Part Name" <> '' then
ValidatePartReference(
TenantReportLayoutCfg."Theme Part Name",
Enum::"Report Layout Subtype"::Theme,
ThemeNotFoundTxt, ThemeObsoleteTxt, Warnings);
until TenantReportLayoutCfg.Next() = 0;
exit(Warnings.ToText());
end;
local procedure ValidatePartReference(CompositeName: Text; ExpectedSubtype: Enum "Report Layout Subtype"; NotFoundFormat: Text; ObsoleteFormat: Text; var Warnings: TextBuilder)
var
ReportLayoutList: Record "Report Layout List";
LookupHelper: Codeunit "Composite Layout Lookup Helper";
AppId: Guid;
LayoutName: Text;
begin
LayoutName := LookupHelper.DecodeLayoutName(CompositeName);
AppId := LookupHelper.DecodeAppId(CompositeName);
ReportLayoutList.SetRange("Name", LayoutName);
ReportLayoutList.SetRange("Layout Subtype", ExpectedSubtype);
// The composite reference identifies the part by app as well as name; filter on the App ID too so a
// same-named part of the same subtype in a different app is not matched by mistake.
if not IsNullGuid(AppId) then
ReportLayoutList.SetRange("Application ID", AppId);
if not ReportLayoutList.FindFirst() then begin
if Warnings.Length > 0 then
Warnings.Append('\');
Warnings.Append(StrSubstNo(NotFoundFormat, LayoutName));
exit;
end;
if ReportLayoutList.IsObsolete then begin
if Warnings.Length > 0 then
Warnings.Append('\');
Warnings.Append(StrSubstNo(ObsoleteFormat, LayoutName));
end;
end;
local procedure CreateLayoutMime(FileNameWithExtension: Text) MimeType: Text[255]
var
FileManagement: Codeunit "File Management";
FileExtension: Text;
begin
FileExtension := FileManagement.GetExtension(FileNameWithExtension);
MimeType := 'reportlayout/' + FileExtension;
end;
internal procedure EditReportLayout(SelectedReportLayoutList: Record "Report Layout List"; var NewEditedLayoutName: Text)
var
TenantReportLayout: Record "Tenant Report Layout";
TempBlob: Codeunit "Temp Blob";
ReportLayoutEditDialog: Page "Report Layout Edit Dialog";
NewDescription: Text[250];
NewLayoutName: Text[250];
CompanyName: Text[30];
CreateCopy: Boolean;
NewLayoutInStream: InStream;
SourceLayoutOutStream: OutStream;
AllCompaniesTxt: Label '';
AvailableInAllCompanies: Boolean;
NewIsObsolete: Boolean;
ApplyDescription: Boolean;
ApplyObsolete: Boolean;
CustomDimensions: Dictionary of [Text, Text];
begin
if SelectedReportLayoutList."User Defined" then begin
if TenantReportLayout.Get(SelectedReportLayoutList."Report ID", SelectedReportLayoutList.Name, EmptyGuid) then
CompanyName := TenantReportLayout."Company Name";
end else
CompanyName := SelectedCompany;
ReportLayoutEditDialog.SetupDialog(SelectedReportLayoutList, SelectedCompany);
begin
if ReportLayoutEditDialog.RunModal() <> Action::OK then
exit;
NewDescription := ReportLayoutEditDialog.SelectedLayoutDescription();
NewLayoutName := ReportLayoutEditDialog.SelectedLayoutName();
CreateCopy := ReportLayoutEditDialog.CopyOperationEnabled();
AvailableInAllCompanies := ReportLayoutEditDialog.SelectedAvailableInAllCompanies();
NewIsObsolete := ReportLayoutEditDialog.SelectedIsObsolete();
if (not SelectedReportLayoutList."User Defined") and (not CreateCopy) then begin
// All companies, which is what the read-only Yes in the dialog states.
AvailableInAllCompanies := true;
NewEditedLayoutName := SelectedReportLayoutList.Name;
ApplyDescription := NewDescription <> SelectedReportLayoutList."Description";
ApplyObsolete := NewIsObsolete and (not SelectedReportLayoutList.IsObsolete);
if not (ApplyDescription or ApplyObsolete) then
exit;
UpsertLayoutOverride(SelectedReportLayoutList, AvailableInAllCompanies, ApplyDescription, NewDescription, false, Enum::"Report Layout Status"::Draft, ApplyObsolete, NewIsObsolete);
CustomDimensions.Add('ReportId', Format(SelectedReportLayoutList."Report ID"));
CustomDimensions.Add('LayoutName', SelectedReportLayoutList.Name);
CustomDimensions.Add('DescriptionChanged', Format(ApplyDescription));
CustomDimensions.Add('ObsoleteSet', Format(ApplyObsolete));
CustomDimensions.Add('OverrideScope', 'AllCompanies');
AddReportLayoutDimensionsAction('EditOverride', CustomDimensions);
Log('0000RTQ', 'Report layout properties overridden by user', CustomDimensions);
exit;
end;
// Check if a layout having NewLayoutName already exists
if TenantReportLayout.Get(SelectedReportLayoutList."Report ID", NewLayoutName, EmptyGuid) then
if CreateCopy or (SelectedReportLayoutList.Name <> NewLayoutName) then
Error(LayoutAlreadyExistsErr, NewLayoutName);
// Check if the layout should be made available for all companies
if AvailableInAllCompanies then
CompanyName := AllCompaniesTxt;
if CreateCopy then begin
// If create-copy is used to create a layout bound to the current company
if SelectedReportLayoutList."User Defined" and (not AvailableInAllCompanies) and (CompanyName = '') then
CompanyName := SelectedCompany;
TenantReportLayout.Init();
TenantReportLayout.Name := NewLayoutName;
TenantReportLayout.Description := NewDescription;
TenantReportLayout."Report ID" := SelectedReportLayoutList."Report ID";
TenantReportLayout."Company Name" := CompanyName;
// Copy media stream
SourceLayoutOutStream := TempBlob.CreateOutStream();
SelectedReportLayoutList."Layout".ExportStream(SourceLayoutOutStream);
NewLayoutInStream := TempBlob.CreateInStream();
TenantReportLayout."Layout".ImportStream(NewLayoutInStream, NewDescription);
TenantReportLayout."Layout Format" := SelectedReportLayoutList."Layout Format";
TenantReportLayout."MIME Type" := SelectedReportLayoutList."MIME Type";
TenantReportLayout.IsObsolete := SelectedReportLayoutList.IsObsolete;
TenantReportLayout.ExcelLayoutMultipleDataSheets := SelectedReportLayoutList.ExcelLayoutMultipleDataSheets;
TenantReportLayout."Layout Status" := Enum::"Report Layout Status"::Draft;
TenantReportLayout.Insert(true);
end else begin
TenantReportLayout.Get(SelectedReportLayoutList."Report ID", SelectedReportLayoutList."Name", EmptyGuid);
TenantReportLayout."Company Name" := CompanyName;
TenantReportLayout.Rename(SelectedReportLayoutList."Report ID", NewLayoutName, EmptyGuid);
TenantReportLayout.Description := NewDescription;
TenantReportLayout.IsObsolete := NewIsObsolete;
TenantReportLayout.Modify(true);
end;
NewEditedLayoutName := NewLayoutName;
// If the layout name was updated, we check if this layout is the default layout
// and update its reference in the tenant report layout selection table.
if not CreateCopy then
if (SelectedReportLayoutList.Name <> NewLayoutName) then
UpdateDefaultLayoutSelectionName(SelectedReportLayoutList, NewLayoutName);
CustomDimensions.Add('ReportId', Format(TenantReportLayout."Report ID"));
CustomDimensions.Add('OldLayoutName', TenantReportLayout.Name);
CustomDimensions.Add('OldLayoutDescription', TenantReportLayout.Description);
CustomDimensions.Add('NewLayoutName', NewLayoutName);
CustomDimensions.Add('NewLayoutDescription', NewDescription);
AddReportLayoutDimensionsAction('Edit', CustomDimensions);
Log('0000N0H', 'Report layout properties changed by user', CustomDimensions);
end;
end;
internal procedure ExportReportLayout(SelectedReportLayoutList: Record "Report Layout List"; UpdateOnOnexport: Boolean): Text
var
SourceTempBlob: Codeunit "Temp Blob";
TargetTempBlob: Codeunit "Temp Blob";
FileManagement: Codeunit "File Management";
FileName: Text;
MediaOutStream: OutStream;
LayoutSourceStream: InStream;
LayoutTargetStream: InStream;
LayoutTargetOutStream: OutStream;
CustomDimensions: Dictionary of [Text, Text];
begin
MediaOutStream := SourceTempBlob.CreateOutStream(TextEncoding::UTF8);
SelectedReportLayoutList."Layout".ExportStream(MediaOutStream);
FileName := GetFileName(SelectedReportLayoutList);
if UpdateOnOnexport then begin
LayoutSourceStream := SourceTempBlob.CreateInStream(TextEncoding::UTF8);
LayoutTargetStream := TargetTempBlob.CreateInStream(TextEncoding::UTF8);
LayoutTargetOutStream := TargetTempBlob.CreateOutStream(TextEncoding::UTF8);
if Report.ValidateAndPrepareLayout(SelectedReportLayoutList."Report ID", LayoutSourceStream, LayoutTargetStream, GetLayoutType(SelectedReportLayoutList)) then begin
CopyStream(LayoutTargetOutStream, LayoutTargetStream);
exit(FileManagement.BLOBExportWithEncoding(TargetTempBlob, FileName, true, TextEncoding::UTF8));
end else
Message(CannotUpdateLayoutTxt);
end;
InitReportLayoutListDimensions(selectedReportLayoutList, CustomDimensions);
if (UpdateOnOnexport) then
AddReportLayoutDimensionsAction('UpdateAndExport', CustomDimensions)
else
AddReportLayoutDimensionsAction('Export', CustomDimensions);
Log('0000N0I', 'Report layout exported by user', CustomDimensions);
exit(FileManagement.BLOBExport(SourceTempBlob, FileName, true));
end;
internal procedure ShowInfoDialog(SelectedReportLayoutList: Record "Report Layout List")
var
InfoDialogLbl: Label 'Report ID: %1\Report Name: %2\Layout Name: %3\Description: %4\Type: %5\System ID: %6\Created Date: %7\Created By: %8\Last Modified Date: %9\Last Modified By: %10',
Comment = 'Text to build the message displayed on the layout info dialog. %1 = Repport ID, %2 = Report Name, %3 = Layout Name, %4 = Description, %5 = Layout Type, %6 = System ID, %7 = Created Date, %8 = Created By, %9 = Last Modified Date, %10 = Last Modified By';
begin
// We need to use this format with % and variable passed to the Message function so that backslahes in the variables are not replaced with new lines.
Message(InfoDialogLbl,
SelectedReportLayoutList."Report ID".ToText(),
SelectedReportLayoutList."Report Name",
SelectedReportLayoutList.Caption,
SelectedReportLayoutList.Description,
Format(SelectedReportLayoutList."Layout Format"),
Format(SelectedReportLayoutList.SystemId, 0, 4),
SelectedReportLayoutList.SystemCreatedAt.ToText(),
GetUserDisplayName(SelectedReportLayoutList.SystemCreatedBy),
SelectedReportLayoutList.SystemModifiedAt.ToText(),
GetUserDisplayName(SelectedReportLayoutList.SystemModifiedBy));
end;
local procedure GetUserDisplayName(UserGuid: Guid): Text
var
User: Record "User";
begin
if not User.ReadPermission() then
exit('');
if (UserGuid <> EmptyGuid) and User.Get(UserGuid) then
exit(User."User Name");
exit('');
end;
internal procedure OpenInOneDrive(SelectedReportLayoutList: Record "Report Layout List")
var
TenantReportLayout: Record "Tenant Report Layout";
DocumentServiceMgt: Codeunit "Document Service Management";
TempBlob: Codeunit "Temp Blob";
FileName: Text;
FileExtension: Text;
MediaInStream: InStream;
MediaOutStream: OutStream;
begin
if not TenantReportLayout.Get(SelectedReportLayoutList."Report ID", SelectedReportLayoutList."Name", EmptyGuid) then
exit;
FileName := GetFileName(SelectedReportLayoutList);
FileExtension := GetFileExtension(SelectedReportLayoutList);
MediaOutStream := TempBlob.CreateOutStream();
TenantReportLayout."Layout".ExportStream(MediaOutStream);
MediaInStream := TempBlob.CreateInStream();
DocumentServiceMgt.OpenInOneDrive(FileName, FileExtension, MediaInStream);
end;
internal procedure ShareWithOneDrive(SelectedReportLayoutList: Record "Report Layout List")
var
TenantReportLayout: Record "Tenant Report Layout";
DocumentServiceMgt: Codeunit "Document Service Management";
TempBlob: Codeunit "Temp Blob";
FileName: Text;
FileExtension: Text;
MediaInStream: InStream;
MediaOutStream: OutStream;
begin
if not TenantReportLayout.Get(SelectedReportLayoutList."Report ID", SelectedReportLayoutList."Name", EmptyGuid) then
exit;
FileName := GetFileName(SelectedReportLayoutList);
FileExtension := GetFileExtension(SelectedReportLayoutList);
MediaOutStream := TempBlob.CreateOutStream();
TenantReportLayout."Layout".ExportStream(MediaOutStream);
MediaInStream := TempBlob.CreateInStream();
DocumentServiceMgt.ShareWithOneDrive(FileName, FileExtension, MediaInStream);
end;
internal procedure EditInOneDrive(SelectedReportLayoutList: Record "Report Layout List")
var
TenantReportLayout: Record "Tenant Report Layout";
DocumentServiceMgt: Codeunit "Document Service Management";
TempBlob: Codeunit "Temp Blob";
FileName: Text;
FileExtension: Text;
MediaInStream: InStream;
MediaOutStream: OutStream;
begin
if not TenantReportLayout.Get(SelectedReportLayoutList."Report ID", SelectedReportLayoutList."Name", EmptyGuid) then
exit;
FileName := GetFileName(SelectedReportLayoutList);
FileExtension := GetFileExtension(SelectedReportLayoutList);
MediaOutStream := TempBlob.CreateOutStream();
TenantReportLayout."Layout".ExportStream(MediaOutStream);
if DocumentServiceMgt.EditInOneDrive(FileName, FileExtension, TempBlob) then begin
MediaInStream := TempBlob.CreateInStream();
TenantReportLayout."Layout".ImportStream(MediaInStream, TenantReportLayout."Description");
if not TenantReportLayout.Insert(true) then
TenantReportLayout.Modify(true);
end;
end;
procedure ExportReportSchema(SelectedReportLayoutList: Record "Report Layout List"; DefaultFileName: Text; ShowFileDialog: Boolean) Result: Text
var
TempBlob: Codeunit "Temp Blob";
FileMgt: Codeunit "File Management";
begin
if DefaultFileName = '' then
DefaultFileName := '*.xml';
GetWordXMLPart(SelectedReportLayoutList."Report ID", TempBlob);
if TempBlob.HasValue() then
exit(FileMgt.BLOBExport(TempBlob, DefaultFileName, ShowFileDialog));
end;
local procedure GetWordXMLPart(reportId: Integer; var TempBlob: Codeunit "Temp Blob")
var
OutStr: OutStream;
begin
TempBlob.CreateOutStream(OutStr, TEXTENCODING::UTF16);
OutStr.WriteText(REPORT.WordXmlPart(reportId));
end;
local procedure GetFileName(SelectedReportLayoutList: Record "Report Layout List"): Text
var
CurrentExt: Text;
CurrentLayoutName: Text;
begin
CurrentLayoutName := SelectedReportLayoutList."Name";
CurrentExt := GetFileExtension(SelectedReportLayoutList);
if StrPos(CurrentLayoutName, '.' + CurrentExt) = 0 then
exit(CurrentLayoutName + '.' + CurrentExt);
exit(CurrentLayoutName);
end;
local procedure GetFileExtension(SelectedReportLayoutList: Record "Report Layout List") FileExt: Text
begin
// If MIME Type is present, use that to create file-extension.
if (SelectedReportLayoutList."MIME Type" <> '') and SelectedReportLayoutList."MIME Type".ToLower().Contains('reportlayout/') then begin
FileExt := SelectedReportLayoutList."MIME Type".Split('/').Get(2);
exit;
end;
case SelectedReportLayoutList."Layout Format" of
SelectedReportLayoutList."Layout Format"::Word:
FileExt := 'docx';
SelectedReportLayoutList."Layout Format"::RDLC:
FileExt := 'rdl';
SelectedReportLayoutList."Layout Format"::Excel:
FileExt := 'xlsx';
SelectedReportLayoutList."Layout Format"::Custom:
FileExt := '';
end;
end;
local procedure GetLayoutType(SelectedReportLayoutList: Record "Report Layout List") LayoutType: ReportLayoutType
begin
case SelectedReportLayoutList."Layout Format" of
SelectedReportLayoutList."Layout Format"::Word:
LayoutType := ReportLayoutType::Word;
SelectedReportLayoutList."Layout Format"::RDLC:
LayoutType := ReportLayoutType::RDLC;
SelectedReportLayoutList."Layout Format"::Excel:
LayoutType := ReportLayoutType::Excel;
SelectedReportLayoutList."Layout Format"::Custom:
LayoutType := ReportLayoutType::Custom;
end;
end;
local procedure InitReportLayoutDimensions(TenantReportLayout: Record "Tenant Report Layout"; var CustomDimensions: Dictionary of [Text, Text])
begin
CustomDimensions.Set('ReportId', Format(TenantReportLayout."Report ID"));
CustomDimensions.Set('LayoutName', TenantReportLayout."Name");
end;
local procedure InitReportLayoutListDimensions(ReportLayoutList: Record "Report Layout List"; var CustomDimensions: Dictionary of [Text, Text])
begin
CustomDimensions.Set('ReportId', Format(ReportLayoutList."Report ID"));
CustomDimensions.Set('LayoutName', ReportLayoutList."Name");
end;
local procedure AddReportLayoutDimensionsFormat(LayoutFormat: Option; var CustomDimensions: Dictionary of [Text, Text])
var
TenantReportLayout: Record "Tenant Report Layout";
begin
TenantReportLayout."Layout Format" := LayoutFormat;
// Format this option with the correct string value using metadata from the table.
CustomDimensions.Add('LayoutFormat', format(TenantReportLayout."Layout Format"));
end;
local procedure AddReportLayoutDimensionsDescription(Description: Text; var CustomDimensions: Dictionary of [Text, Text])
begin
CustomDimensions.Add('LayoutDescription', Description);
end;
local procedure AddReportLayoutDimensionsAction(Action: Text; var CustomDimensions: Dictionary of [Text, Text])
begin
CustomDimensions.Add('Action', Action);
end;
local procedure AddReportLayoutDimensionsExcelConfiguration(Configuration: enum "Excel Sheet Configuration"; var CustomDimensions: Dictionary of [Text, Text])
begin
CustomDimensions.Add('ExcelConfiguration', format(Configuration));
end;
[EventSubscriber(ObjectType::Page, Page::"Report Layout Selection", 'OnSelectReportLayout', '', false, false)]
local procedure SelectReportLayout(var ReportLayoutList: Record "Report Layout List"; var Handled: Boolean)
begin
if Handled = true then
exit;
if Page.RunModal(Page::"Report Layouts", ReportLayoutList) = ACTION::LookupOK then
Handled := true;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::ReportManagement, 'OnSelectReportLayout', '', false, false)]
local procedure SelectReportLayoutUI(var ReportLayoutList: Record "Report Layout List"; var Handled: Boolean)
begin
if Page.RunModal(Page::"Report Layouts", ReportLayoutList) = ACTION::LookupOK then
Handled := true;
end;
[InternalEvent(false, false)]
local procedure OnBeforeUpload(var AlreadyUploaded: Boolean; var UploadFileName: Text; var FileInStream: InStream)
begin
end;
local procedure Log(EventId: Text; AuditMessage: Text; CustomDimensions: Dictionary of [Text, Text])
var
AuditLog: Codeunit "Audit Log";
begin
Session.LogMessage(EventId, AuditMessage, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::All, CustomDimensions);
AuditLog.LogAuditMessage(AuditMessage, SecurityOperationResult::Success, AuditCategory::Other, 7, 0, CustomDimensions);
end;
}