Table 9650 Custom Report Layout, source in 29

Source29

src/Layers/W1/BaseApp/Foundation/Reporting/CustomReportLayout.Table.al1108 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.Foundation.Reporting;

using System;
using System.Environment;
using System.IO;
using System.Reflection;
using System.Security.AccessControl;
using System.Utilities;

table 9650 "Custom Report Layout"
{
    Caption = 'Custom Report Layout';
    DataPerCompany = false;
#if not CLEAN28
#pragma warning disable AL0432
    DrillDownPageID = "Custom Report Layouts";
    LookupPageID = "Custom Report Layouts";
#pragma warning restore AL0432
#endif
    Permissions = TableData "Custom Report Layout" = rimd;
    DataClassification = CustomerContent;

    fields
    {
        field(1; "Code"; Code[20])
        {
            Caption = 'Code';
            ToolTip = 'Specifies the Code.';
        }
        field(2; "Report ID"; Integer)
        {
            Caption = 'Report ID';
            ToolTip = 'Specifies the object ID of the report.';
            TableRelation = AllObjWithCaption."Object ID" where("Object Type" = const(Report));
        }
        field(3; "Report Name"; Text[250])
        {
            CalcFormula = lookup(AllObjWithCaption."Object Caption" where("Object Type" = const(Report),
                                                                           "Object ID" = field("Report ID")));
            Caption = 'Report Name';
            ToolTip = 'Specifies the name of the report.';
            Editable = false;
            FieldClass = FlowField;
        }
        field(4; "Company Name"; Text[30])
        {
            Caption = 'Company Name';
            ToolTip = 'Specifies the Business Central company that the report layout applies to. You to create report layouts that can only be used on reports when they are run for a specific to a company. If the field is blank, then the layout will be available for use in all companies.';
            TableRelation = Company;
        }
        field(6; Type; Enum "Custom Report Layout Type")
        {
            Caption = 'Type';
            ToolTip = 'Specifies the file type of the report layout. The following table includes the types that are available:';
            InitValue = Word;
        }
        field(7; "Layout"; BLOB)
        {
            Caption = 'Layout';
        }
        field(8; "Last Modified"; DateTime)
        {
            Caption = 'Last Modified';
            ToolTip = 'Specifies the date and time of the last change to the report layout entry.';
            Editable = false;
        }
        field(9; "Last Modified by User"; Code[50])
        {
            Caption = 'Last Modified by User';
            ToolTip = 'Specifies the user who made the last change to the report layout entry.';
            DataClassification = EndUserIdentifiableInformation;
            Editable = false;
            TableRelation = User."User Name";
            ValidateTableRelation = false;
        }
        field(10; "File Extension"; Text[30])
        {
            Caption = 'File Extension';
            Editable = false;
        }
        field(11; Description; Text[250])
        {
            Caption = 'Description';
            ToolTip = 'Specifies a description of the report layout.';
        }
        field(12; "Custom XML Part"; BLOB)
        {
            Caption = 'Custom XML Part';
        }
        field(13; "App ID"; Guid)
        {
            Caption = 'App ID';
            Editable = false;
        }
        field(14; "Built-In"; Boolean)
        {
            Caption = 'Built-In';
            ToolTip = 'Specifies if the report layout is built-in or not.';
            Editable = false;
        }
        field(15; "Layout Last Updated"; DateTime)
        {
            Caption = 'Layout Last Modified';
            Editable = false;
        }
    }

    keys
    {
        key(Key1; "Code")
        {
            Clustered = true;
        }
        key(Key2; "Report ID", "Company Name", Type)
        {
        }
    }

    fieldgroups
    {
        fieldgroup(DropDown; Description)
        {
        }
    }

    trigger OnDelete()
    begin
        if "Built-In" then
            Error(DeleteBuiltInLayoutErr);
    end;

    trigger OnInsert()
    begin
        TestField("Report ID");
        if Code = '' then
            Code := GetDefaultCode("Report ID");
        SetUpdated();
    end;

    trigger OnModify()
    begin
        TestField("Report ID");
        SetUpdated();
    end;

    var
        ImportWordTxt: Label 'Import Word Document';
        ImportRdlcTxt: Label 'Import Report Layout';
        FileFilterWordTxt: Label 'Word Files (*.docx)|*.docx', Comment = '{Split=r''\|''}{Locked=s''1''}';
        FileFilterRdlcTxt: Label 'SQL Report Builder (*.rdl;*.rdlc)|*.rdl;*.rdlc', Comment = '{Split=r''\|''}{Locked=s''1''}';
        NoRecordsErr: Label 'There is no record in the list.';
        BuiltInTxt: Label 'Built-in layout';
#pragma warning disable AA0470
        CopyOfTxt: Label 'Copy of %1';
#pragma warning restore AA0470
        NewLayoutTxt: Label 'New layout';
        ErrorInLayoutErr: Label 'The following issue has been found in the layout %1 for report ID  %2:\%3.', Comment = '%1=a name, %2=a number, %3=a sentence/error description.';
        TemplateValidationQst: Label 'The RDLC layout does not comply with the current report design (for example, fields are missing or the report ID is wrong).\The following errors were detected during the layout validation:\%1\Do you want to continue?', Comment = '%1 = an error message.';
#pragma warning disable AA0470
        TemplateValidationErr: Label 'The RDLC layout does not comply with the current report design (for example, fields are missing or the report ID is wrong).\The following errors were detected during the document validation:\%1\You must update the layout to match the current report design.';
#pragma warning restore AA0470
        AbortWithValidationErr: Label 'The RDLC layout action has been canceled because of validation errors.';
        ModifyBuiltInLayoutQst: Label 'This is a built-in custom report layout, and it cannot be modified.\\Do you want to modify a copy of the custom report layout instead?';
        NoLayoutSelectedMsg: Label 'You must specify if you want to insert a Word layout or an RDLC layout for the report.';
        DeleteBuiltInLayoutErr: Label 'This is a built-in custom report layout, and it cannot be deleted.';
        ModifyBuiltInLayoutErr: Label 'This is a built-in custom report layout, and it cannot be modified.';

    local procedure SetUpdated()
    begin
        "Last Modified" := RoundDateTime(CurrentDateTime);
        "Last Modified by User" := CopyStr(UserId(), 1, 50);
    end;

    procedure InitBuiltInLayout(ReportID: Integer; LayoutType: Option): Code[20]
    var
        CustomReportLayout: Record "Custom Report Layout";
        TempBlob: Codeunit "Temp Blob";
        DocumentReportMgt: Codeunit "Document Report Mgt.";
        OutStr: OutStream;
    begin
        if ReportID = 0 then
            exit;

        CustomReportLayout.Init();
        CustomReportLayout."Report ID" := ReportID;
        CustomReportLayout.Type := "Custom Report Layout Type".FromInteger(LayoutType);
        CustomReportLayout.Description := CopyStr(StrSubstNo(CopyOfTxt, BuiltInTxt), 1, MaxStrLen(Description));
        CustomReportLayout."Built-In" := false;
        CustomReportLayout.Code := GetDefaultCode(ReportID);
        CustomReportLayout.Insert(true);

        case LayoutType of
            CustomReportLayout.Type::Word.AsInteger():
                begin
                    if not LoadInternalWordLayout(ReportID, TempBlob) then begin
                        TempBlob.CreateOutStream(OutStr);
                        DocumentReportMgt.NewWordLayout(ReportID, OutStr);
                        CustomReportLayout.Description := CopyStr(NewLayoutTxt, 1, MaxStrLen(Description));
                    end;

                    CustomReportLayout.SetLayoutBlob(TempBlob);
                end;
            CustomReportLayout.Type::RDLC.AsInteger():
                if LoadInternalRdlcLayout(ReportID, TempBlob) then
                    CustomReportLayout.SetLayoutBlob(TempBlob);
            else
                OnInitBuiltInLayout(CustomReportLayout, ReportID, LayoutType);
        end;

        CustomReportLayout.SetDefaultCustomXmlPart();
        CustomReportLayout.SetLayoutLastUpdated();

        exit(CustomReportLayout.Code);
    end;

    /// <summary>
    /// Internal resplacement for the soon deprecated Report.WordLayout function. This function will load the internal Word layout for the given report using the virtual table Report Layout List.
    /// </summary>
    /// <param name="ReportID">Report Id.</param>
    /// <param name="TempBlob">Layout will be provided in the blob object if the procedure return true.</param>
    /// <returns>True if the layout was loaded.</returns>
    local procedure LoadInternalWordLayout(ReportID: Integer; var TempBlob: Codeunit "Temp Blob"): Boolean
    var
        ReportLayoutList: Record "Report Layout List";
        LayoutStream: OutStream;
        ExportStatus: Boolean;
    begin
        ReportLayoutList.SetFilter("Report ID", '=%1', ReportID);
        ReportLayoutList.SetFilter("Layout Format", '=%1', ReportLayoutList."Layout Format"::Word);
        if not ReportLayoutList.FindFirst() then
            exit(false);

        TempBlob.CreateOutStream(LayoutStream);
        ExportStatus := ReportLayoutList.Layout.ExportStream(LayoutStream);
        exit(ExportStatus)
    end;

    /// <summary>
    /// Internal resplacement for the soon deprecated Report.RdlcLayout function. This function will load the internal RDLC layout for the given report using the virtual table Report Layout List.
    /// </summary>
    /// <param name="ReportID">Report Id.</param>
    /// <param name="TempBlob">Layout will be provided in the blob object if the procedure return true.</param>
    /// <returns>True if the layout was loaded.</returns>
    local procedure LoadInternalRdlcLayout(ReportID: Integer; var TempBlob: Codeunit "Temp Blob"): Boolean
    var
        ReportLayoutList: Record "Report Layout List";
        LayoutStream: OutStream;
        ExportStatus: Boolean;
    begin
        ReportLayoutList.SetFilter("Report ID", '=%1', ReportID);
        ReportLayoutList.SetFilter("Layout Format", '=%1', ReportLayoutList."Layout Format"::RDLC);
        if not ReportLayoutList.FindFirst() then
            exit(false);

        TempBlob.CreateOutStream(LayoutStream);
        ExportStatus := ReportLayoutList.Layout.ExportStream(LayoutStream);
        exit(ExportStatus)
    end;

    procedure CopyBuiltInReportLayout()
    var
        ReportLayoutLookup: Page "Report Layout Lookup";
        ReportID: Integer;
        LayoutSelected: Boolean;
    begin
        FilterGroup(4);
        if GetFilter("Report ID") = '' then
            FilterGroup(0);
        if GetFilter("Report ID") <> '' then
            if Evaluate(ReportID, GetFilter("Report ID")) then
                ReportLayoutLookup.SetReportID(ReportID);
        FilterGroup(0);
        if ReportLayoutLookup.RunModal() = ACTION::OK then begin
            if ReportLayoutLookup.SelectedAddWordLayot() then
                InitBuiltInLayout(ReportLayoutLookup.SelectedReportID(), Type::Word.AsInteger());
            if ReportLayoutLookup.SelectedAddRdlcLayot() then
                InitBuiltInLayout(ReportLayoutLookup.SelectedReportID(), Type::RDLC.AsInteger());

            LayoutSelected := ReportLayoutLookup.SelectedAddWordLayot() or ReportLayoutLookup.SelectedAddRdlcLayot();
            if (not LayoutSelected) and (not ReportLayoutLookup.InitCustomTypeLayouts()) then
                MESSAGE(NoLayoutSelectedMsg);
        end;
    end;

    procedure CopyReportLayout(): Code[20]
    var
        CustomReportLayout: Record "Custom Report Layout";
        TempBlob: Codeunit "Temp Blob";
    begin
        if IsEmpty() then
            Error(NoRecordsErr);

        CalcFields(Layout, "Custom XML Part");
        CustomReportLayout := Rec;

        Description := CopyStr(StrSubstNo(CopyOfTxt, Description), 1, MaxStrLen(Description));
        Code := GetDefaultCode("Report ID");
        "Built-In" := false;
        OnCopyRecordOnBeforeInsertLayout(Rec, CustomReportLayout);
        Insert(true);

        if CustomReportLayout."Built-In" then begin
            CustomReportLayout.GetLayoutBlob(TempBlob);
            SetLayoutBlob(TempBlob);
        end;

        if not HasCustomXmlPart() then
            SetDefaultCustomXmlPart();

        exit(Code);
    end;

    procedure ImportReportLayout(DefaultFileName: Text)
    var
        TempBlob: Codeunit "Temp Blob";
        FileMgt: Codeunit "File Management";
        FileName: Text;
        FileFilterTxt: Text;
        ImportTxt: Text;
    begin
        if IsEmpty() then
            Error(NoRecordsErr);

        if not CanBeModified() then
            exit;

        case Type of
            Type::Word:
                begin
                    ImportTxt := ImportWordTxt;
                    FileFilterTxt := FileFilterWordTxt;
                end;
            Type::RDLC:
                begin
                    ImportTxt := ImportRdlcTxt;
                    FileFilterTxt := FileFilterRdlcTxt;
                end;
        end;

        OnImportLayoutSetFileFilter(Rec, FileFilterTxt);
        FileName := FileMgt.BLOBImportWithFilter(TempBlob, ImportTxt, DefaultFileName, FileFilterTxt, FileFilterTxt);
        if FileName = '' then
            exit;

        ImportLayoutBlob(TempBlob, CopyStr(UpperCase(FileMgt.GetExtension(FileName)), 1, 30));
    end;

    [Scope('OnPrem')]
    procedure ImportLayoutBlob(var TempBlob: Codeunit "Temp Blob"; FileExtension: Text[30])
    var
        OutputTempBlob: Codeunit "Temp Blob";
        DocumentReportMgt: Codeunit "Document Report Mgt.";
        DocumentInStream: InStream;
        DocumentOutStream: OutStream;
        ErrorMessage: Text;
        XmlPart: Text;
    begin
        // Layout is stored in the DocumentInStream (RDLC requires UTF8 encoding for which reason is stream is created in the case block.
        // Result is stored in the DocumentOutStream (..)
        TestField("Report ID");
        OutputTempBlob.CreateOutStream(DocumentOutStream);
        XmlPart := GetWordXmlPart("Report ID");

        case Type of
            Type::Word:
                begin
                    // Run update
                    TempBlob.CreateInStream(DocumentInStream);
                    ErrorMessage := DocumentReportMgt.TryUpdateWordLayout(DocumentInStream, DocumentOutStream, '', XmlPart);
                    // Validate the Word document layout against the layout of the current report
                    if ErrorMessage = '' then begin
                        CopyStream(DocumentOutStream, DocumentInStream);
                        DocumentReportMgt.ValidateWordLayout("Report ID", DocumentInStream, true, true);
                    end;
                end;
            Type::RDLC:
                begin
                    // Update the Rdlc document layout against the layout of the current report
                    TempBlob.CreateInStream(DocumentInStream, TEXTENCODING::UTF8);
                    ErrorMessage := DocumentReportMgt.TryUpdateRdlcLayout("Report ID", DocumentInStream, DocumentOutStream, '', XmlPart, false);
                end;
        end;

        OnImportLayoutBlob(Rec, TempBlob, FileExtension, XmlPart, DocumentOutStream);

        SetLayoutBlob(OutputTempBlob);

        if FileExtension <> '' then
            "File Extension" := FileExtension;
        SetDefaultCustomXmlPart();
        Modify(true);
        SetLayoutLastUpdated();
        Commit();

        if ErrorMessage <> '' then
            Message(ErrorMessage);
    end;

    procedure ExportReportLayout(DefaultFileName: Text; ShowFileDialog: Boolean): Text
    var
        TempBlob: Codeunit "Temp Blob";
        FileMgt: Codeunit "File Management";
    begin
        OnBeforeExportReportLayout(Rec, DefaultFileName, ShowFileDialog);

        // Update is needed in case of report layout mismatches word layout
        // Do not update build-in layout as it is read only
        if not "Built-In" then
            UpdateReportLayout(true, false); // Don't block on errors (return false) as we in all cases want to have an export file to edit.

        GetLayoutBlob(TempBlob);
        if not TempBlob.HasValue() then
            exit('');

        if DefaultFileName = '' then
            DefaultFileName := '*.' + GetFileExtension();

        exit(FileMgt.BLOBExport(TempBlob, DefaultFileName, ShowFileDialog));
    end;

    [Scope('OnPrem')]
    procedure ValidateLayout(useConfirm: Boolean; UpdateContext: Boolean): Boolean
    var
        TempBlob: Codeunit "Temp Blob";
        DocumentReportMgt: Codeunit "Document Report Mgt.";
        DocumentInStream: InStream;
        ValidationErrorFormat: Text;
    begin
        TestField("Report ID");
        GetLayoutBlob(TempBlob);
        if not TempBlob.HasValue() then
            exit;

        TempBlob.CreateInStream(DocumentInStream);

        case Type of
            Type::Word:
                exit(DocumentReportMgt.ValidateWordLayout("Report ID", DocumentInStream, useConfirm, UpdateContext));
            Type::RDLC:
                if not TryValidateRdlcReport(DocumentInStream) then begin
                    if useConfirm then begin
                        if not Confirm(TemplateValidationQst, false, GetLastErrorText) then
                            Error(AbortWithValidationErr);
                    end else begin
                        ValidationErrorFormat := TemplateValidationErr;
                        Error(ValidationErrorFormat, GetLastErrorText);
                    end;
                    exit(false);
                end;
        end;

        exit(true);
    end;

    procedure UpdateReportLayout(ContinueOnError: Boolean; IgnoreDelete: Boolean) LayoutUpdated: Boolean
    var
        ErrorMessage: Text;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeUpdateLayout(LayoutUpdated, IsHandled);
        if IsHandled then
            exit(LayoutUpdated);

        ErrorMessage := TryUpdateLayout(IgnoreDelete);

        if ErrorMessage = '' then begin
            if Type = Type::Word then
                exit(ValidateLayout(true, true));
            exit(true); // We have no validate for RDLC
        end;

        ErrorMessage := StrSubstNo(ErrorInLayoutErr, Description, "Report ID", ErrorMessage);
        if ContinueOnError then begin
            Message(ErrorMessage);
            exit(true);
        end;

        Error(ErrorMessage);
    end;

    [Scope('OnPrem')]
    procedure TryUpdateLayout(IgnoreDelete: Boolean): Text
    var
        InTempBlob: Codeunit "Temp Blob";
        OutTempBlob: Codeunit "Temp Blob";
        DocumentReportMgt: Codeunit "Document Report Mgt.";
        DocumentInStream: InStream;
        DocumentOutStream: OutStream;
        CurrentCustomXmlPart: Text;
        StoredCustomXmlPart: Text;
        ErrorMessage: Text;
    begin
        TestCustomXmlPart();
        TestField("Report ID");
        CurrentCustomXmlPart := GetWordXmlPart("Report ID");
        StoredCustomXmlPart := GetCustomXmlPart();

        if "Layout Last Updated" > "Last Modified" then
            if CurrentCustomXmlPart = StoredCustomXmlPart then
                exit(''); // no need to update

        GetLayoutBlob(InTempBlob);
        if not InTempBlob.HasValue() then
            exit('');
        InTempBlob.CreateInStream(DocumentInStream);

        case Type of
            Type::Word:
                begin
                    OutTempBlob.CreateOutStream(DocumentOutStream);
                    ErrorMessage := DocumentReportMgt.TryUpdateWordLayout(DocumentInStream, DocumentOutStream, StoredCustomXmlPart, CurrentCustomXmlPart);
                end;
            Type::RDLC:
                begin
                    OutTempBlob.CreateOutStream(DocumentOutStream, TEXTENCODING::UTF8);
                    ErrorMessage := DocumentReportMgt.TryUpdateRdlcLayout(
                        "Report ID", DocumentInStream, DocumentOutStream, StoredCustomXmlPart, CurrentCustomXmlPart, IgnoreDelete);
                end;
        end;

        SetCustomXmlPart(CurrentCustomXmlPart);

        if OutTempBlob.HasValue() then
            SetLayoutBlob(OutTempBlob);

        SetLayoutLastUpdated();

        exit(ErrorMessage);
    end;

    local procedure GetWordXML(var TempBlob: Codeunit "Temp Blob")
    var
        OutStr: OutStream;
    begin
        TestField("Report ID");
        TempBlob.CreateOutStream(OutStr, TEXTENCODING::UTF16);
        OutStr.WriteText(REPORT.WordXmlPart("Report ID"));
    end;

    [Scope('OnPrem')]
    procedure ExportSchema(DefaultFileName: Text; ShowFileDialog: Boolean) Result: Text
    var
        TempBlob: Codeunit "Temp Blob";
        FileMgt: Codeunit "File Management";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeExportSchema(Rec, DefaultFileName, ShowFileDialog, IsHandled);
        if IsHandled then
            exit(Result);

        TestField(Type, Type::Word);

        if DefaultFileName = '' then
            DefaultFileName := '*.xml';

        GetWordXML(TempBlob);
        if TempBlob.HasValue() then
            exit(FileMgt.BLOBExport(TempBlob, DefaultFileName, ShowFileDialog));
    end;

    procedure GetFileExtension() FileExt: Text[4]
    begin
        case Type of
            Type::Word:
                FileExt := 'docx';
            Type::RDLC:
                FileExt := 'rdl';
            else
                OnGetFileExtension(Rec, FileExt);
        end;
    end;

    procedure GetWordXmlPart(ReportID: Integer): Text
    var
        WordXmlPart: Text;
    begin
        // Store the current design as an extended WordXmlPart. This data is used for later updates / refactorings.
        WordXmlPart := REPORT.WordXmlPart(ReportID, true);
        exit(WordXmlPart);
    end;

    procedure RunCustomReport()
    var
        ReportLayoutSelection: Record "Report Layout Selection";
    begin
        if "Report ID" = 0 then
            exit;

        ReportLayoutSelection.SetTempLayoutSelected(Code);
        REPORT.RunModal("Report ID");
        ReportLayoutSelection.ClearTempLayoutSelected();
    end;

    [Scope('OnPrem')]
    procedure ApplyUpgrade(var ReportUpgrade: DotNet ReportUpgradeSet; var ReportChangeLogCollection: DotNet IReportChangeLogCollection; testOnly: Boolean)
    var
        InTempBlob: Codeunit "Temp Blob";
        OutTempBlob: Codeunit "Temp Blob";
        TempReportChangeLogCollection: DotNet IReportChangeLogCollection;
        DataInStream: InStream;
        DataOutStream: OutStream;
        ModifyLayout: Boolean;
    begin
        GetLayoutBlob(InTempBlob);
        if not InTempBlob.HasValue() then
            exit;

        if ReportUpgrade.ChangeCount < 1 then
            exit;

        Clear(DataInStream);
        Clear(DataOutStream);

        case Type of
            Type::Word:
                begin
                    InTempBlob.CreateInStream(DataInStream);
                    OutTempBlob.CreateOutStream(DataOutStream);
                end;
            Type::RDLC:
                begin
                    InTempBlob.CreateInStream(DataInStream, TEXTENCODING::UTF8);
                    OutTempBlob.CreateOutStream(DataOutStream, TEXTENCODING::UTF8);
                end;
        end;

        TempReportChangeLogCollection := ReportUpgrade.Upgrade(Description, DataInStream, DataOutStream);

        if not testOnly then begin
            if TempReportChangeLogCollection.Failures = 0 then begin
                SetDefaultCustomXmlPart();
                ModifyLayout := true;
            end;
            if OutTempBlob.HasValue() then begin
                SetLayoutBlob(OutTempBlob);
                ModifyLayout := true;
            end;
            if ModifyLayout then
                Commit();
        end;

        if TempReportChangeLogCollection.Count > 0 then
            if IsNull(ReportChangeLogCollection) then
                ReportChangeLogCollection := TempReportChangeLogCollection
            else
                ReportChangeLogCollection.AddRange(TempReportChangeLogCollection);
    end;

    [TryFunction]
    local procedure TryValidateRdlcReport(var InStr: InStream)
    var
        RdlcReportManager: DotNet RdlcReportManager;
        RdlcString: Text;
    begin
        InStr.Read(RdlcString);
        RdlcReportManager.ValidateReport("Report ID", RdlcString);
    end;

#if not CLEAN28
    local procedure FilterOnReport(ReportID: Integer)
    begin
        Reset();
        SetCurrentKey("Report ID", "Company Name", Type);
        SetFilter("Company Name", '%1|%2', '', StrSubstNo('@%1', CompanyName));
        SetRange("Report ID", ReportID);
        SetRange("Built-In", false);
    end;

    [Obsolete('This procedure is obsolete because the lookup page 9650 Custom Report Layouts is obsoleted.', '28.0')]
    procedure LookupLayoutOK(ReportID: Integer): Boolean
    begin
        FilterOnReport(ReportID);
        OnLookupLayoutOKOnBeforePageRun(Rec);
#pragma warning disable AL0432
        exit(PAGE.RunModal(PAGE::"Custom Report Layouts", Rec) = ACTION::LookupOK);
#pragma warning restore AL0432
    end;
#endif

    procedure GetDefaultCode(ReportID: Integer): Code[20]
    var
        CustomReportLayout: Record "Custom Report Layout";
        NewCode: Code[20];
    begin
        CustomReportLayout.SetRange("Report ID", ReportID);
        CustomReportLayout.SetFilter(Code, StrSubstNo('%1-*', ReportID));
        if CustomReportLayout.FindLast() then
            NewCode := IncStr(CustomReportLayout.Code)
        else
            NewCode := StrSubstNo('%1-000001', ReportID);

        exit(NewCode);
    end;

    [Scope('OnPrem')]
    procedure CanBeModified(): Boolean
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeCanBeModified(Rec, IsHandled);
        if IsHandled then
            exit;

        if not "Built-In" then
            exit(true);

        if not Confirm(ModifyBuiltInLayoutQst) then
            exit(false);

        CopyReportLayout();
        exit(true);
    end;

    [Scope('OnPrem')]
    procedure NewExtensionLayout(ExtensionAppId: Guid; LayoutDataTable: DotNet DataTable)
    var
        Row: DotNet DataRow;
        Version: Text;
    begin
        Row := LayoutDataTable.Rows.Item(0);
        if LayoutDataTable.Columns.Contains('NavApplicationVersion') then
            Version := Row.Item('NavApplicationVersion');

        case Version of
            else
                HandleW10Layout(ExtensionAppId, Row, LayoutDataTable);
        end;
    end;

    local procedure HandleW10Layout(ExtensionAppId: Guid; Row: DotNet DataRow; LayoutDataTable: DotNet DataTable)
    var
        CustomReportLayout: Record "Custom Report Layout";
        LayoutCode: Code[20];
    begin
        if not LayoutDataTable.Columns.Contains('Code') then begin
            LayoutCode := 'MS-EXT-0000000001';
            CustomReportLayout.SetFilter(Code, 'MS-EXT-*');
            if CustomReportLayout.FindLast() then
                LayoutCode := IncStr(CustomReportLayout.Code);
        end else
            LayoutCode := Row.Item('Code');

        CustomReportLayout.Reset();
        CustomReportLayout.Init();
        CustomReportLayout.Code := LayoutCode;
        CustomReportLayout."App ID" := ExtensionAppId;
        CustomReportLayout.Type := Row.Item('Type');
        CustomReportLayout."Custom XML Part" := Row.Item('CustomXMLPart');
        CustomReportLayout.Description := Row.Item('Description');
        CustomReportLayout.Layout := Row.Item('Layout');
        CustomReportLayout."Report ID" := Row.Item('ReportID');
        CustomReportLayout.CalcFields("Report Name");
        CustomReportLayout."Built-In" := true;
        CustomReportLayout.Insert();
    end;

    procedure HasLayout(): Boolean
    begin
        if "Built-In" then
            exit(HasBuiltInLayout());
        exit(HasNonBuiltInLayout());
    end;

    procedure HasCustomXmlPart(): Boolean
    begin
        if "Built-In" then
            exit(HasBuiltInCustomXmlPart());
        exit(HasNonBuiltInCustomXmlPart());
    end;

    procedure GetLayout(): Text
    begin
        if "Built-In" then
            exit(GetBuiltInLayout());
        exit(GetNonBuiltInLayout());
    end;

    procedure GetCustomXmlPart(): Text
    begin
        if "Built-In" then
            exit(GetBuiltInCustomXmlPart());
        exit(GetNonBuiltInCustomXmlPart());
    end;

    procedure GetLayoutBlob(var TempBlob: Codeunit "Temp Blob")
    var
        ReportLayout: Record "Report Layout";
    begin
        Clear(TempBlob);
        TempBlob.FromRecord(Rec, FieldNo(Layout));

        if "Built-In" then
            if not TempBlob.HasValue() then begin // not a built-in report from an extension
                ReportLayout.Get(Code);
                TempBlob.FromRecord(ReportLayout, ReportLayout.FieldNo(Layout));
            end;
    end;

    procedure ClearLayout()
    begin
        if "Built-In" then
            Error(ModifyBuiltInLayoutErr);
        SetNonBuiltInLayout('');
    end;

    procedure ClearCustomXmlPart()
    begin
        if "Built-In" then
            Error(ModifyBuiltInLayoutErr);
        SetNonBuiltInCustomXmlPart('');
    end;

    procedure CanModify(): Boolean
    var
        User: Record User;
        [SecurityFiltering(SecurityFilter::Ignored)]
        CustomReportLayout: Record "Custom Report Layout";
    begin
        if CurrentTransactionType() = TransactionType::Report then
            exit(false);
        if not CustomReportLayout.WritePermission() then
            exit(false);
        if not User.Get(UserSecurityId()) then
            exit(true);
        exit(User."License Type" <> User."License Type"::"Limited User");
    end;

    procedure TestLayout()
    var
        ReportLayout: Record "Report Layout";
    begin
        if not "Built-In" then begin
            CalcFields(Layout);
            TestField(Layout);
            exit;
        end;
        ReportLayout.Get(Code);
        ReportLayout.CalcFields(Layout);
        ReportLayout.TestField(Layout);
    end;

    procedure TestCustomXmlPart()
    var
        ReportLayout: Record "Report Layout";
    begin
        if not "Built-In" then begin
            CalcFields("Custom XML Part");
            TestField("Custom XML Part");
            exit;
        end;
        ReportLayout.Get(Code);
        ReportLayout.CalcFields("Custom XML Part");
        ReportLayout.TestField("Custom XML Part");
    end;

    procedure SetLayout(Content: Text)
    begin
        if "Built-In" then
            Error(ModifyBuiltInLayoutErr);
        SetNonBuiltInLayout(Content);
    end;

    procedure SetCustomXmlPart(Content: Text)
    begin
        if "Built-In" then
            Error(ModifyBuiltInLayoutErr);
        SetNonBuiltInCustomXmlPart(Content);
    end;

    procedure SetDefaultCustomXmlPart()
    begin
        SetCustomXmlPart(GetWordXmlPart("Report ID"));
    end;

    procedure SetLayoutBlob(var TempBlob: Codeunit "Temp Blob")
    var
        RecordRef: RecordRef;
    begin
        if "Built-In" then
            Error(ModifyBuiltInLayoutErr);
        Clear(Layout);
        if TempBlob.HasValue() then begin
            RecordRef.GetTable(Rec);
            TempBlob.ToRecordRef(RecordRef, FieldNo(Layout));
            RecordRef.SetTable(Rec);
        end;
        if CanModify() then
            Modify();
    end;

    local procedure HasNonBuiltInLayout(): Boolean
    begin
        CalcFields(Layout);
        exit(Layout.HasValue);
    end;

    local procedure HasNonBuiltInCustomXmlPart(): Boolean
    begin
        CalcFields("Custom XML Part");
        exit("Custom XML Part".HasValue);
    end;

    local procedure HasBuiltInLayout(): Boolean
    var
        ReportLayout: Record "Report Layout";
    begin
        if not ReportLayout.Get(Code) then
            exit(false);

        ReportLayout.CalcFields(Layout);
        exit(ReportLayout.Layout.HasValue);
    end;

    local procedure HasBuiltInCustomXmlPart(): Boolean
    var
        ReportLayout: Record "Report Layout";
    begin
        if not ReportLayout.Get(Code) then
            exit(false);

        ReportLayout.CalcFields("Custom XML Part");
        exit(ReportLayout."Custom XML Part".HasValue);
    end;

    local procedure GetNonBuiltInLayout(): Text
    var
        InStr: InStream;
        Content: Text;
    begin
        CalcFields(Layout);
        if not Layout.HasValue() then
            exit('');

        case Type of
            Type::RDLC:
                Layout.CreateInStream(InStr, TEXTENCODING::UTF8);
            Type::Word:
                Layout.CreateInStream(InStr);
            else
                OnGetNonBuiltInLayout(Rec, InStr);
        end;

        InStr.Read(Content);
        exit(Content);
    end;

    local procedure GetNonBuiltInCustomXmlPart(): Text
    var
        InStr: InStream;
        Content: Text;
    begin
        CalcFields("Custom XML Part");
        if not "Custom XML Part".HasValue() then
            exit('');

        "Custom XML Part".CreateInStream(InStr, TEXTENCODING::UTF16);
        InStr.Read(Content);
        exit(Content);
    end;

    local procedure GetBuiltInLayout(): Text
    var
        ReportLayout: Record "Report Layout";
        InStr: InStream;
        Content: Text;
    begin
        if not ReportLayout.Get(Code) then
            exit('');

        ReportLayout.CalcFields(Layout);
        if not ReportLayout.Layout.HasValue() then
            exit('');

        case Type of
            Type::RDLC:
                ReportLayout.Layout.CreateInStream(InStr, TEXTENCODING::UTF8);
            Type::Word:
                ReportLayout.Layout.CreateInStream(InStr);
        end;

        InStr.Read(Content);
        exit(Content);
    end;

    local procedure GetBuiltInCustomXmlPart(): Text
    var
        ReportLayout: Record "Report Layout";
        InStr: InStream;
        Content: Text;
    begin
        if not ReportLayout.Get(Code) then
            exit('');

        ReportLayout.CalcFields("Custom XML Part");
        if not ReportLayout."Custom XML Part".HasValue() then
            exit('');

        ReportLayout."Custom XML Part".CreateInStream(InStr, TEXTENCODING::UTF16);
        InStr.Read(Content);
        exit(Content);
    end;

    local procedure SetNonBuiltInLayout(Content: Text)
    var
        OutStr: OutStream;
    begin
        Clear(Layout);
        if Content <> '' then begin
            case Type of
                Type::RDLC:
                    Layout.CreateOutStream(OutStr, TEXTENCODING::UTF8);
                Type::Word:
                    Layout.CreateOutStream(OutStr);
            end;
            OutStr.Write(Content);
        end;
        if CanModify() then
            Modify();
    end;

    local procedure SetNonBuiltInCustomXmlPart(Content: Text)
    var
        OutStr: OutStream;
    begin
        Clear("Custom XML Part");
        if Content <> '' then begin
            "Custom XML Part".CreateOutStream(OutStr, TEXTENCODING::UTF16);
            OutStr.Write(Content);
        end;

        if CanModify() then
            Modify();
    end;

    internal procedure SetLayoutLastUpdated()
    begin
        "Layout Last Updated" := RoundDateTime(CurrentDateTime);

        if CanModify() then
            Rec.Modify();
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeCanBeModified(var CustomReportLayout: Record "Custom Report Layout"; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeUpdateLayout(var LayoutUpdated: Boolean; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeExportReportLayout(CustomReportLayout: Record "Custom Report Layout"; var DefaultFileName: Text; ShowFileDialog: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeExportSchema(CustomReportLayout: Record "Custom Report Layout"; var DefaultFileName: Text; ShowFileDialog: Boolean; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnCopyRecordOnBeforeInsertLayout(var ToCustomReportLayout: Record "Custom Report Layout"; FromCustomReportLayout: Record "Custom Report Layout")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGetNonBuiltInLayout(CustomReportLayout: Record "Custom Report Layout"; var InStream: InStream)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGetFileExtension(CustomReportLayout: Record "Custom Report Layout"; var FileExt: Text[4])
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnImportLayoutBlob(CustomReportLayout: Record "Custom Report Layout"; var TempBlob: Codeunit "Temp Blob"; FileExtension: Text[30]; XmlPart: Text; DocumentOutStream: OutStream)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnImportLayoutSetFileFilter(CustomReportLayout: Record "Custom Report Layout"; var FileFilterTxt: Text)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnInitBuiltInLayout(var CustomReportLayout: Record "Custom Report Layout"; ReportID: Integer; LayoutType: Option)
    begin
    end;

#if not CLEAN28
    [IntegrationEvent(false, false)]
    [Obsolete('This procedure is obsolete because the procedure LookupLayoutOK is obsoleted.', '28.0')]
    local procedure OnLookupLayoutOKOnBeforePageRun(var CustomReportLayout: Record "Custom Report Layout")
    begin
    end;
#endif
}