Table 483 Change Global Dim. Log Entry, source in 29

Source29

src/Layers/W1/BaseApp/Finance/Dimension/ChangeGlobalDimLogEntry.Table.al770 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.Dimension;

using Microsoft.Bank.Ledger;
using Microsoft.Finance.GeneralLedger.Ledger;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.FixedAssets.Insurance;
using Microsoft.FixedAssets.Ledger;
using Microsoft.FixedAssets.Maintenance;
using Microsoft.HumanResources.Payables;
using Microsoft.Inventory.Counting.Journal;
using Microsoft.Inventory.History;
using Microsoft.Inventory.Ledger;
using Microsoft.Projects.Project.Job;
using Microsoft.Projects.Project.Ledger;
using Microsoft.Projects.Project.WIP;
using Microsoft.Projects.Resources.Ledger;
using Microsoft.Purchases.History;
using Microsoft.Purchases.Payables;
using Microsoft.Sales.FinanceCharge;
using Microsoft.Sales.History;
using Microsoft.Sales.Receivables;
using Microsoft.Sales.Reminder;
using System.Environment;
using System.Reflection;
using System.Threading;

/// <summary>
/// Logs the progress and status of the Change Global Dimensions background process.
/// Tracks table-by-table progress, error counts, and processing statistics for dimension restructuring operations.
/// </summary>
/// <remarks>
/// Used by the Change Global Dimensions functionality to monitor multi-table dimension updates.
/// Provides audit trail and progress tracking for critical dimension restructuring operations.
/// Integrates with session management and background task processing systems.
/// </remarks>
table 483 "Change Global Dim. Log Entry"
{
    Caption = 'Change Global Dim. Log Entry';
    Permissions = TableData "G/L Entry" = rm,
                  TableData "Cust. Ledger Entry" = rm,
                  TableData "Vendor Ledger Entry" = rm,
                  TableData "Item Ledger Entry" = rm,
                  TableData "Sales Shipment Header" = rm,
                  TableData "Sales Shipment Line" = rm,
                  TableData "Sales Invoice Header" = rm,
                  TableData "Sales Invoice Line" = rm,
                  TableData "Sales Cr.Memo Header" = rm,
                  TableData "Sales Cr.Memo Line" = rm,
                  TableData "Purch. Rcpt. Header" = rm,
                  TableData "Purch. Rcpt. Line" = rm,
                  TableData "Purch. Inv. Header" = rm,
                  TableData "Purch. Inv. Line" = rm,
                  TableData "Purch. Cr. Memo Hdr." = rm,
                  TableData "Purch. Cr. Memo Line" = rm,
                  TableData "Job Ledger Entry" = rm,
                  TableData "Res. Ledger Entry" = rm,
                  TableData "Bank Account Ledger Entry" = rm,
                  TableData "Phys. Inventory Ledger Entry" = rm,
                  TableData "Issued Reminder Header" = rm,
                  TableData "Issued Fin. Charge Memo Header" = rm,
                  TableData "Detailed Cust. Ledg. Entry" = rm,
                  TableData "Detailed Vendor Ledg. Entry" = rm,
                  TableData "Job WIP G/L Entry" = rm,
                  TableData "Employee Ledger Entry" = rm,
                  TableData "Detailed Employee Ledger Entry" = rm,
#if not CLEAN28
                  TableData Microsoft.Manufacturing.Document."Production Order" = rm,
                  TableData Microsoft.Manufacturing.Document."Prod. Order Line" = rm,
                  TableData Microsoft.Manufacturing.Document."Prod. Order Component" = rm,
                  TableData Microsoft.Manufacturing.Document."Prod. Order Routing Line" = rm,
                  TableData Microsoft.Manufacturing.Document."Prod. Order Capacity Need" = rm,
                  TableData Microsoft.Manufacturing.Document."Prod. Order Routing Tool" = rm,
                  TableData Microsoft.Manufacturing.Document."Prod. Order Routing Personnel" = rm,
                  TableData Microsoft.Manufacturing.Document."Prod. Order Rtng Qlty Meas." = rm,
                  TableData Microsoft.Manufacturing.Document."Prod. Order Comment Line" = rm,
                  TableData Microsoft.Manufacturing.Document."Prod. Order Rtng Comment Line" = rm,
                  TableData Microsoft.Manufacturing.Document."Prod. Order Comp. Cmt Line" = rm,
#endif
                  TableData "Invt. Receipt Header" = rm,
                  TableData "Invt. Receipt Line" = rm,
                  TableData "Invt. Shipment Header" = rm,
                  TableData "Invt. Shipment Line" = rm,
                  TableData "FA Ledger Entry" = rm,
                  TableData "Maintenance Ledger Entry" = rm,
                  TableData "Ins. Coverage Ledger Entry" = rm,
                  TableData "Value Entry" = rm,
                  TableData Microsoft.Manufacturing.Capacity."Capacity Ledger Entry" = rm,
#if not CLEAN28
                  TableData Microsoft.Service.Document."Service Header" = rm,
                  TableData Microsoft.Service.Document."Service Line" = rm,
                  TableData Microsoft.Service.Document."Service Item Line" = rm,
                  TableData Microsoft.Service.Ledger."Service Ledger Entry" = rm,
                  TableData Microsoft.Service.Contract."Service Contract Header" = rm,
                  TableData Microsoft.Service.Contract."Service Contract Line" = rm,
                  TableData Microsoft.Service.History."Service Invoice Line" = rm,
                  tabledata Microsoft.Service.History."Service Cr.Memo Header" = rm,
                  tabledata Microsoft.Service.History."Service Cr.Memo Line" = rm,
                  tabledata Microsoft.Service.History."Service Invoice Header" = rm,
                  tabledata Microsoft.Service.History."Service Shipment Header" = rm,
                  tabledata Microsoft.Service.History."Service Shipment Line" = rm,
                  TableData Microsoft.Service.Contract."Filed Service Contract Header" = rm,
#endif
                  TableData "Return Shipment Header" = rm,
                  TableData "Return Shipment Line" = rm,
                  TableData "Return Receipt Header" = rm,
                  TableData "Return Receipt Line" = rm;
    ReplicateData = true;
    DataClassification = CustomerContent;

    fields
    {
        /// <summary>
        /// Unique identifier of the table being processed during dimension change operation.
        /// </summary>
        field(1; "Table ID"; Integer)
        {
            Caption = 'Table ID';
        }
        /// <summary>
        /// Name of the table being processed for display purposes.
        /// </summary>
        field(2; "Table Name"; Text[50])
        {
            Caption = 'Table Name';
        }
        /// <summary>
        /// Total number of records in the table that need to be processed.
        /// </summary>
        field(3; "Total Records"; Integer)
        {
            Caption = 'Total Records';
        }
        /// <summary>
        /// Number of records that have been successfully processed.
        /// </summary>
        field(4; "Completed Records"; Integer)
        {
            Caption = 'Completed Records';

            trigger OnValidate()
            begin
                CalcProgress();
            end;
        }
        /// <summary>
        /// Processing progress percentage for tracking completion status.
        /// </summary>
        field(5; Progress; Decimal)
        {
            AutoFormatType = 0;
            Caption = 'Progress';
            ExtendedDatatype = Ratio;
        }
        /// <summary>
        /// Current processing status of the table dimension change operation.
        /// </summary>
        field(6; Status; Option)
        {
            Caption = 'Status';
            OptionCaption = ' ,Scheduled,In Progress,Completed,Incomplete';
            OptionMembers = " ",Scheduled,"In Progress",Completed,Incomplete;
        }
        /// <summary>
        /// Unique identifier of the scheduled task processing this table.
        /// </summary>
        field(7; "Task ID"; Guid)
        {
            Caption = 'Task ID';
        }
        /// <summary>
        /// Session identifier for tracking the processing session.
        /// </summary>
        field(8; "Session ID"; Integer)
        {
            Caption = 'Session ID';
        }
        /// <summary>
        /// Type of change operation for global dimension 1 (None, Blank, Replace, or New).
        /// </summary>
        field(9; "Change Type 1"; Option)
        {
            Caption = 'Change Type 1';
            OptionCaption = 'None,Blank,Replace,New';
            OptionMembers = "None",Blank,Replace,New;
        }
        /// <summary>
        /// Type of change operation for global dimension 2 (None, Blank, Replace, or New).
        /// </summary>
        field(10; "Change Type 2"; Option)
        {
            Caption = 'Change Type 2';
            OptionCaption = 'None,Blank,Replace,New';
            OptionMembers = "None",Blank,Replace,New;
        }
        /// <summary>
        /// Field number for global dimension 1 in this table.
        /// </summary>
        field(11; "Global Dim.1 Field No."; Integer)
        {
            Caption = 'Global Dim.1 Field No.';
        }
        /// <summary>
        /// Field number for global dimension 2 in this table.
        /// </summary>
        field(12; "Global Dim.2 Field No."; Integer)
        {
            Caption = 'Global Dim.2 Field No.';
        }
        /// <summary>
        /// Field number for dimension set ID in this table.
        /// </summary>
        field(13; "Dim. Set ID Field No."; Integer)
        {
            Caption = 'Dim. Set ID Field No.';
        }
        /// <summary>
        /// Field number of the primary key field used for record processing.
        /// </summary>
        field(14; "Primary Key Field No."; Integer)
        {
            Caption = 'Primary Key Field No.';
        }
        /// <summary>
        /// Table ID of the parent table when processing related tables in hierarchy.
        /// </summary>
        field(15; "Parent Table ID"; Integer)
        {
            Caption = 'Parent Table ID';
        }
        /// <summary>
        /// Indicates whether this table is a parent table in the processing hierarchy.
        /// </summary>
        field(16; "Is Parent Table"; Boolean)
        {
            Caption = 'Is Parent Table';
        }
        /// <summary>
        /// Earliest date and time when processing can start for this table.
        /// </summary>
        field(17; "Earliest Start Date/Time"; DateTime)
        {
            Caption = 'Earliest Start Date/Time';
            ToolTip = 'Specifies the earliest date and time when the job should be run.';
        }
        /// <summary>
        /// Estimated remaining time to complete processing this table.
        /// </summary>
        field(18; "Remaining Duration"; Duration)
        {
            Caption = 'Remaining Duration';
            ToolTip = 'Specifies the remaining duration of the job.';
        }
        /// <summary>
        /// Identifier of the server instance processing this table.
        /// </summary>
        field(19; "Server Instance ID"; Integer)
        {
            Caption = 'Server Instance ID';
            DataClassification = SystemMetadata;
        }
    }

    keys
    {
        key(Key1; "Table ID")
        {
            Clustered = true;
        }
        key(Key2; Progress)
        {
        }
        key(Key3; "Parent Table ID")
        {
        }
    }

    fieldgroups
    {
    }

    var
        ErrorTraceTagMsg: Label 'Error on the task for table %1 (completed %2 of %3 records): %4.', Comment = '%1- table id; %2 ,%3 - integer values; %4 - error message';
        RerunTraceTagMsg: Label 'Rerun the task for table %1 (start from %2 of %3 records).', Comment = '%1- table id; %2 ,%3 - integer values';
        ScheduledTraceTagMsg: Label 'The task is scheduled for table %1 (%2 records) to start on %3.', Comment = '%1- table id; %2 - integer value; %3 - datetime';
        TaskStartTraceTagMsg: Label 'Change Global Dimensions task started for a table.', Locked = true;
        TagCategoryTxt: Label 'Change Global Dimensions';

    local procedure CalcProgress()
    begin
        Progress := 10000;
        if ("Total Records" <> 0) and ("Completed Records" <= "Total Records") then
            Progress := "Completed Records" / "Total Records" * 10000;
    end;

    /// <summary>
    /// Updates the progress of dimension change processing for this table entry.
    /// Returns true if the processing is complete, false if still in progress.
    /// </summary>
    /// <param name="CurrentRecNo">Current record number being processed</param>
    /// <param name="StartedFromRecord">Record number where processing started</param>
    /// <returns>True if processing is complete, false if still in progress</returns>
    procedure Update(CurrentRecNo: Integer; StartedFromRecord: Integer): Boolean
    begin
        if "Completed Records" = CurrentRecNo then
            exit(false);

        if CurrentRecNo >= "Total Records" then
            RecalculateTotalRecords();
        Validate("Completed Records", CurrentRecNo);
        case "Completed Records" of
            0:
                begin
                    Status := Status::Incomplete;
                    Clear("Remaining Duration");
                end;
            "Total Records":
                Status := Status::Completed;
            else
                if CurrentRecNo - StartedFromRecord <> 0 then
                    "Remaining Duration" :=
                      Round(
                        ("Total Records" - CurrentRecNo) / (CurrentRecNo - StartedFromRecord) *
                        (CurrentDateTime - "Earliest Start Date/Time"), 1);
        end;
        exit(Modify());
    end;

    /// <summary>
    /// Updates progress with automatic commit and returns completion status.
    /// </summary>
    /// <param name="CurrentRecNo">Current record number being processed</param>
    /// <param name="StartedFromRecord">Record number where processing started</param>
    /// <returns>True if processing is complete, false if still in progress</returns>
    procedure UpdateWithCommit(CurrentRecNo: Integer; StartedFromRecord: Integer) Completed: Boolean
    begin
        if Update(CurrentRecNo, StartedFromRecord) then
            Commit();
        Completed := Status = Status::Completed;
    end;

    /// <summary>
    /// Updates progress without committing transaction and returns completion status.
    /// </summary>
    /// <param name="CurrentRecNo">Current record number being processed</param>
    /// <param name="StartedFromRecord">Record number where processing started</param>
    /// <returns>True if processing is complete, false if still in progress</returns>
    procedure UpdateWithoutCommit(CurrentRecNo: Integer; StartedFromRecord: Integer) Completed: Boolean
    begin
        Update(CurrentRecNo, StartedFromRecord);
        Completed := Status = Status::Completed;
    end;

    /// <summary>
    /// Cancels the scheduled task associated with this dimension change operation.
    /// Clears the Task ID field after canceling the scheduled task.
    /// </summary>
    procedure CancelTask()
    var
        ScheduledTask: Record "Scheduled Task";
    begin
        if not IsNullGuid("Task ID") then begin
            if ScheduledTask.Get("Task ID") then
                TASKSCHEDULER.CancelTask("Task ID");
            Clear("Task ID");
        end;
    end;

    /// <summary>
    /// Changes the dimension value on a specific record field during dimension change processing.
    /// </summary>
    /// <param name="RecRef">Record reference to modify</param>
    /// <param name="DimNo">Dimension number (1 or 2)</param>
    /// <param name="GlobalDimFieldRef">Field reference for the global dimension field</param>
    /// <param name="OldDimValueCode">Previous dimension value code</param>
    procedure ChangeDimOnRecord(var RecRef: RecordRef; DimNo: Integer; GlobalDimFieldRef: FieldRef; OldDimValueCode: Code[20])
    var
        NewValue: Code[20];
    begin
        case GetChangeType(DimNo) of
            "Change Type 1"::New:
                NewValue := FindDimensionValueCode(RecRef, DimNo);
            "Change Type 1"::Blank:
                NewValue := '';
            "Change Type 1"::Replace:
                NewValue := OldDimValueCode;
            "Change Type 1"::None:
                exit;
        end;
        GlobalDimFieldRef.Value(NewValue);
    end;

    /// <summary>
    /// Retrieves field references and current dimension values for global dimension fields.
    /// </summary>
    /// <param name="RecRef">Record reference to examine</param>
    /// <param name="GlobalDimFieldRef">Array of field references for global dimensions</param>
    /// <param name="DimValueCode">Array of current dimension value codes</param>
    procedure GetFieldRefValues(RecRef: RecordRef; var GlobalDimFieldRef: array[2] of FieldRef; var DimValueCode: array[2] of Code[20])
    begin
        if "Global Dim.1 Field No." <> 0 then begin
            GlobalDimFieldRef[1] := RecRef.Field("Global Dim.1 Field No.");
            DimValueCode[1] := GlobalDimFieldRef[1].Value();
        end;
        if "Global Dim.2 Field No." <> 0 then begin
            GlobalDimFieldRef[2] := RecRef.Field("Global Dim.2 Field No.");
            DimValueCode[2] := GlobalDimFieldRef[2].Value();
        end;
    end;

    /// <summary>
    /// Locates and stores the field number for the Dimension Set ID field in the specified table.
    /// </summary>
    /// <param name="RecRef">Record reference to examine for dimension set ID field</param>
    /// <returns>True if dimension set ID field is found, false otherwise</returns>
    procedure FindDimensionSetIDField(RecRef: RecordRef): Boolean
    var
        "Field": Record "Field";
    begin
        if FindDefaultDimSetIDFieldNo(RecRef) then
            exit(true);
        Field.SetRange(TableNo, RecRef.Number);
        Field.SetRange(RelationTableNo, DATABASE::"Dimension Set Entry");
        Field.SetRange(FieldName, 'Dimension Set ID');
        Field.SetFilter(ObsoleteState, '<>%1', Field.ObsoleteState::Removed);
        if Field.FindFirst() then begin
            "Dim. Set ID Field No." := Field."No.";
            exit(true);
        end;
    end;

    local procedure FindDefaultDimSetIDFieldNo(RecRef: RecordRef) Found: Boolean
    begin
        // W1 "Dimension Set ID" fields must have ID = 480
        if RecRef.FieldExist(480) then begin
            "Dim. Set ID Field No." := 480;
            Found := true;
        end;
        OnAfterFindDefaultDimSetIDFieldNo(RecRef, Found);
    end;

    /// <summary>
    /// Finds the dimension value code for a specific dimension on a given record.
    /// </summary>
    /// <param name="RecRef">Record reference to examine</param>
    /// <param name="DimNo">Dimension number (1 or 2) to find value for</param>
    /// <returns>Dimension value code for the specified dimension</returns>
    procedure FindDimensionValueCode(RecRef: RecordRef; DimNo: Integer): Code[20]
    var
        GeneralLedgerSetup: Record "General Ledger Setup";
        DimensionCode: Code[20];
        DimensionValueCode: Code[20];
        IsHandled: Boolean;
    begin
        GeneralLedgerSetup.Get();
        case DimNo of
            1:
                DimensionCode := GeneralLedgerSetup."Global Dimension 1 Code";
            2:
                DimensionCode := GeneralLedgerSetup."Global Dimension 2 Code";
        end;
        if "Dim. Set ID Field No." = 0 then begin
            if RecRef.Number = DATABASE::"Job Task" then
                exit(FindJobTaskDimensionValueCode(RecRef, DimensionCode));
            IsHandled := false;
            OnBeforeFindDefaultDimensionValueCode(RecRef, DimensionCode, DimensionValueCode, IsHandled);
            if IsHandled then
                exit(DimensionValueCode);
            exit(FindDefaultDimensionValueCode(RecRef, DimensionCode));
        end;
        exit(FindDimSetDimensionValueCode(RecRef, DimensionCode));
    end;

    local procedure FindDefaultDimensionValueCode(RecRef: RecordRef; DimensionCode: Code[20]): Code[20]
    var
        DefaultDimension: Record "Default Dimension";
        PKFieldRef: FieldRef;
        PKValue: Text;
    begin
        PKFieldRef := RecRef.Field("Primary Key Field No.");
        PKValue := Format(PKFieldRef.Value());
        if StrLen(PKValue) > MaxStrLen(DefaultDimension."No.") then
            exit('');
        if DefaultDimension.Get(RecRef.Number, PKValue, DimensionCode) then
            exit(DefaultDimension."Dimension Value Code");
        exit('');
    end;

    local procedure FindDimSetDimensionValueCode(RecRef: RecordRef; DimensionCode: Code[20]): Code[20]
    var
        DimensionSetEntry: Record "Dimension Set Entry";
        DimSetIDFieldRef: FieldRef;
        DimSetID: Integer;
    begin
        DimSetIDFieldRef := RecRef.Field("Dim. Set ID Field No.");
        DimSetID := DimSetIDFieldRef.Value();
        if DimensionSetEntry.Get(DimSetID, DimensionCode) then
            exit(DimensionSetEntry."Dimension Value Code");
        exit('');
    end;

    local procedure FindJobTaskDimensionValueCode(RecRef: RecordRef; DimensionCode: Code[20]): Code[20]
    var
        JobTask: Record "Job Task";
        JobTaskDimension: Record "Job Task Dimension";
    begin
        RecRef.SetTable(JobTask);
        if JobTaskDimension.Get(JobTask."Job No.", JobTask."Job Task No.", DimensionCode) then
            exit(JobTaskDimension."Dimension Value Code");
        exit('');
    end;

    local procedure FindParentTable(RecRef: RecordRef): Integer
    var
        ParentKeyFieldRef: FieldRef;
    begin
        if RecRef.FieldExist(2) then begin // typical for Detailed Ledger Entry tables
            ParentKeyFieldRef := RecRef.Field(2);
            if ParentKeyFieldRef.Type = FieldType::Integer then
                exit(ParentKeyFieldRef.Relation);
        end;
    end;

    /// <summary>
    /// Initializes log entry data by analyzing the table structure and populating field numbers.
    /// </summary>
    /// <param name="RecRef">Record reference to analyze for dimension processing setup</param>
    procedure FillData(RecRef: RecordRef)
    var
        PKeyFieldRef: FieldRef;
    begin
        "Total Records" := RecRef.Count();
        if not FindDimensionSetIDField(RecRef) then begin
            GetPrimaryKeyFieldRef(RecRef, PKeyFieldRef);
            if PKeyFieldRef.Type = FieldType::Code then
                "Primary Key Field No." := PKeyFieldRef.Number
            else
                "Parent Table ID" := FindParentTable(RecRef);
        end;
        FindFieldIDs();
    end;

    local procedure FindFieldIDs()
    var
        "Field": Record "Field";
        DimensionManagement: Codeunit DimensionManagement;
    begin
        if DimensionManagement.FindDimFieldInTable("Table ID", 'Dimension 1 Code*|*Global Dim. 1', Field) then
            "Global Dim.1 Field No." := Field."No.";
        if DimensionManagement.FindDimFieldInTable("Table ID", 'Dimension 2 Code*|*Global Dim. 2', Field) then
            "Global Dim.2 Field No." := Field."No.";
    end;

    local procedure GetChangeType(DimNo: Integer): Integer
    begin
        if DimNo = 1 then
            exit("Change Type 1");
        exit("Change Type 2");
    end;

    /// <summary>
    /// Retrieves the primary key field reference for the table record.
    /// </summary>
    /// <param name="RecRef">Record reference to examine</param>
    /// <param name="PKeyFieldRef">Returns the primary key field reference</param>
    procedure GetPrimaryKeyFieldRef(RecRef: RecordRef; var PKeyFieldRef: FieldRef)
    var
        PKeyRef: KeyRef;
    begin
        PKeyRef := RecRef.KeyIndex(1);
        PKeyFieldRef := PKeyRef.FieldIndex(1);
    end;

    local procedure RecalculateTotalRecords()
    var
        RecRef: RecordRef;
    begin
        RecRef.Open("Table ID");
        "Total Records" := RecRef.Count();
        RecRef.Close();
    end;

    /// <summary>
    /// Logs error trace information for dimension change processing failures.
    /// </summary>
    procedure SendTraceTagOnError()
    begin
        Session.LogMessage('00001ZB', StrSubstNo(ErrorTraceTagMsg, "Table ID", "Completed Records", "Total Records", GetLastErrorText), Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, GetTraceTagCustomDimensions());
    end;

    /// <summary>
    /// Logs trace information when dimension change processing is rerun.
    /// </summary>
    procedure SendTraceTagOnRerun()
    begin
        Session.LogMessage('00001ZC', StrSubstNo(RerunTraceTagMsg, "Table ID", "Completed Records", "Total Records"), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, GetTraceTagCustomDimensions());
    end;

    /// <summary>
    /// Logs trace information when dimension change processing is scheduled.
    /// </summary>
    procedure SendTraceTagOnScheduling()
    begin
        Session.LogMessage('00001ZD', StrSubstNo(ScheduledTraceTagMsg, "Table ID", "Total Records", Format("Earliest Start Date/Time", 0, 9)), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, GetTraceTagCustomDimensions());
    end;

    /// <summary>
    /// Logs trace information when a scheduled session starts processing a table.
    /// </summary>
    procedure SendTraceTagOnTaskStart()
    begin
        Session.LogMessage('0000TMZ', TaskStartTraceTagMsg, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, GetTraceTagCustomDimensions());
    end;

    local procedure GetTraceTagCustomDimensions() CustomDimensions: Dictionary of [Text, Text]
    begin
        CustomDimensions.Add('Category', TagCategoryTxt);
        CustomDimensions.Add('TableId', Format("Table ID"));
        CustomDimensions.Add('TableName', "Table Name");
        CustomDimensions.Add('TaskId', Format("Task ID"));
        CustomDimensions.Add('ParentTableId', Format("Parent Table ID"));
        CustomDimensions.Add('IsParentTable', Format("Is Parent Table"));
        CustomDimensions.Add('CompletedRecords', Format("Completed Records"));
        CustomDimensions.Add('TotalRecords', Format("Total Records"));
        CustomDimensions.Add('EarliestStart', Format("Earliest Start Date/Time", 0, 9));
        CustomDimensions.Add('Status', Format(Status));
    end;

    /// <summary>
    /// Sets the session ID to track the current processing session.
    /// Marks the status as "In Progress" for active processing tracking.
    /// </summary>
    procedure SetSessionInProgress()
    begin
        "Session ID" := SessionId();
        "Server Instance ID" := ServiceInstanceId();
        Status := Status::"In Progress";
        Modify();
    end;

    /// <summary>
    /// Updates the processing status based on current task and session state.
    /// Returns the previous status value for comparison.
    /// </summary>
    /// <returns>Previous status value before the update</returns>
    procedure UpdateStatus() OldStatus: Integer
    begin
        OldStatus := Status;
        if IsNullGuid("Task ID") then
            Status := Status::" "
        else
            if "Completed Records" = "Total Records" then begin
                "Session ID" := -1; // to avoid match to real user sessions
                "Server Instance ID" := -1;
                Status := Status::Completed
            end else
                if "Session ID" = 0 then begin
                    if IsTaskScheduled() then
                        Status := Status::Scheduled
                    else
                        Status := Status::Incomplete;
                end else
                    if IsSessionActive() then
                        Status := Status::"In Progress"
                    else begin
                        Status := Status::Incomplete;
                        "Session ID" := -1;
                        "Server Instance ID" := -1;
                    end;
    end;

    /// <summary>
    /// Checks if the session associated with this log entry is currently active.
    /// Returns true if the session is running on the current server instance or another instance.
    /// </summary>
    /// <returns>True if the session is active, false if inactive or logged off</returns>
    local procedure IsSessionActive(): Boolean;
    var
        ActiveSession: Record "Active Session";
    begin
        if "Server Instance ID" = ServiceInstanceId() then
            exit(ActiveSession.Get("Server Instance ID", "Session ID"));
        if "Server Instance ID" <= 0 then
            exit(false);
        exit(not IsSessionLoggedOff());
    end;

    /// <summary>
    /// Checks if the session associated with this log entry has logged off.
    /// Searches session event records for logoff events after the earliest start date/time.
    /// </summary>
    /// <returns>True if the session has logged off, false if still active</returns>
    local procedure IsSessionLoggedOff(): Boolean;
    var
        SessionEvent: Record "Session Event";
    begin
        SessionEvent.SetRange("Server Instance ID", "Server Instance ID");
        SessionEvent.SetRange("Session ID", "Session ID");
        SessionEvent.SetRange("Event Type", SessionEvent."Event Type"::Logoff);
        SessionEvent.SetFilter("Event Datetime", '>%1', "Earliest Start Date/Time");
        SessionEvent.SetRange("User SID", UserSecurityId());
        exit(not SessionEvent.IsEmpty);
    end;

    /// <summary>
    /// Displays error details from job queue log entries for failed dimension change tasks.
    /// Opens the Job Queue Log Entries page filtered to show errors for this table.
    /// </summary>
    procedure ShowError()
    var
        JobQueueLogEntry: Record "Job Queue Log Entry";
    begin
        if IsNullGuid("Task ID") then begin
            JobQueueLogEntry.SetRange("Object Type to Run", JobQueueLogEntry."Object Type to Run"::Codeunit);
            JobQueueLogEntry.SetRange("Object ID to Run", CODEUNIT::"Change Global Dim Err. Handler");
            JobQueueLogEntry.SetRange(Description, "Table Name");
        end else
            JobQueueLogEntry.SetRange(ID, "Task ID");
        JobQueueLogEntry.SetRange(Status, JobQueueLogEntry.Status::Error);
        PAGE.RunModal(PAGE::"Job Queue Log Entries", JobQueueLogEntry);
    end;

    local procedure IsTaskScheduled() TaskExists: Boolean
    var
        ScheduledTask: Record "Scheduled Task";
    begin
        OnFindingScheduledTask("Task ID", TaskExists);
        if not TaskExists then
            exit(ScheduledTask.Get("Task ID"));
    end;

    /// <summary>
    /// Integration event raised after attempting to find default dimension set ID field number.
    /// Enables extensions to provide custom logic for identifying dimension set ID fields.
    /// </summary>
    /// <param name="RecRef">Record reference being examined</param>
    /// <param name="Found">Indicates whether the field was found by standard logic</param>
    [IntegrationEvent(true, false)]
    local procedure OnAfterFindDefaultDimSetIDFieldNo(RecRef: RecordRef; var Found: Boolean)
    begin
    end;

    /// <summary>
    /// Integration event raised when checking if a scheduled task exists.
    /// Enables extensions to provide custom task existence verification.
    /// </summary>
    /// <param name="TaskID">GUID of the task to check</param>
    /// <param name="IsTaskExist">Returns whether the task exists</param>
    [IntegrationEvent(false, false)]
    local procedure OnFindingScheduledTask(TaskID: Guid; var IsTaskExist: Boolean)
    begin
    end;

    /// <summary>
    /// Integration event raised before finding default dimension value code.
    /// Enables extensions to provide custom dimension value code lookup logic.
    /// </summary>
    /// <param name="RecRef">Record reference being examined</param>
    /// <param name="DimensionCode">Dimension code to find value for</param>
    /// <param name="DimensionValueCode">Returns the dimension value code</param>
    /// <param name="IsHandled">Set to true to skip standard lookup logic</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeFindDefaultDimensionValueCode(RecRef: RecordRef; DimensionCode: Code[20]; var DimensionValueCode: Code[20]; var IsHandled: Boolean)
    begin
    end;
}