Table 700 Error Message, source in 29

Source29

src/Layers/W1/BaseApp/Modules/System/ErrorMessage/ErrorMessage.Table.al928 lines, Copyright (c) Microsoft Corporation. MIT

namespace System.Utilities;

using System.Reflection;

table 700 "Error Message"
{
    Caption = 'Error Message';
    DrillDownPageID = "Error Messages Part";
    LookupPageID = "Error Messages Part";
    DataClassification = CustomerContent;

    fields
    {
        field(1; ID; Integer)
        {
            AutoIncrement = true;
            Caption = 'ID';
        }
        field(2; "Record ID"; RecordID)
        {
            Caption = 'Record ID';
            DataClassification = CustomerContent;

            trigger OnValidate()
            begin
                Rec."Table Number" := GetTableNo(Rec."Record ID");
            end;
        }
        field(3; "Field Number"; Integer)
        {
            Caption = 'Field Number';

            trigger OnValidate()
            begin
                if Rec."Table Number" = 0 then
                    Rec."Field Number" := 0;
            end;
        }
        field(4; "Message Type"; Option)
        {
            Caption = 'Message Type';
            Editable = false;
            OptionCaption = 'Error,Warning,Information';
            OptionMembers = Error,Warning,Information;
        }
#if not CLEANSCHEMA25
        field(5; "Description"; Text[250])
        {
            Caption = 'Description';
            Editable = false;
            ObsoleteState = Removed;
            ObsoleteTag = '25.0';
            ObsoleteReason = 'Replaced by "Message" which has an increase in field length.';
        }
#endif
        field(6; "Additional Information"; Text[250])
        {
            Caption = 'Additional Information';
            Editable = false;
        }
        field(7; "Support Url"; Text[250])
        {
            Caption = 'Support Url';
            Editable = false;
        }
        field(8; "Table Number"; Integer)
        {
            Caption = 'Table Number';
        }
        field(10; "Context Record ID"; RecordID)
        {
            Caption = 'Context Record ID';
            DataClassification = CustomerContent;

            trigger OnValidate()
            begin
                Rec."Context Table Number" := GetTableNo(Rec."Context Record ID");
            end;
        }
        field(11; "Field Name"; Text[80])
        {
            CalcFormula = lookup(Field."Field Caption" where(TableNo = field("Table Number"),
                                                              "No." = field("Field Number")));
            Caption = 'Field Name';
            Editable = false;
            FieldClass = FlowField;
        }
        field(12; "Table Name"; Text[80])
        {
            CalcFormula = lookup("Table Metadata".Caption where(ID = field("Table Number")));
            Caption = 'Table Name';
            Editable = false;
            FieldClass = FlowField;
        }
        field(13; "Context Field Number"; Integer)
        {
            Caption = 'Context Field Number';
            DataClassification = SystemMetadata;

            trigger OnValidate()
            begin
                if Rec."Context Table Number" = 0 then
                    Rec."Context Field Number" := 0;
            end;
        }
        field(14; "Context Table Number"; Integer)
        {
            Caption = 'Context Table Number';
            DataClassification = SystemMetadata;
        }
        field(15; "Context Field Name"; Text[80])
        {
            CalcFormula = lookup(Field."Field Caption" where(TableNo = field("Context Table Number"),
                                                              "No." = field("Context Field Number")));
            Caption = 'Context Field Name';
            Editable = false;
            FieldClass = FlowField;
        }
        field(16; "Register ID"; Guid)
        {
            Caption = 'Register ID';
            DataClassification = SystemMetadata;
            TableRelation = "Error Message Register".ID;
        }
        field(17; "Created On"; DateTime)
        {
            Caption = 'Created On';
            DataClassification = SystemMetadata;
        }
        field(18; Context; Boolean)
        {
            Caption = 'Context';
            DataClassification = SystemMetadata;
        }
        field(20; Duplicate; Boolean)
        {
            Caption = 'Duplicate';
            DataClassification = SystemMetadata;
        }
        field(21; "Error Call Stack"; BLOB)
        {
            Caption = 'Error Call Stack';
            DataClassification = SystemMetadata;
        }
        field(22; "Message"; Text[2048])
        {
            Caption = 'Description';
            Editable = false;
        }
        field(23; "Reg. Err. Msg. System ID"; Guid) // Link temporary error message with registered (committed) error message.
        {
            DataClassification = SystemMetadata;
        }
    }

    keys
    {
        key(Key1; ID)
        {
            Clustered = true;
        }
        key(Key2; "Context Record ID", "Record ID")
        {
        }
        key(Key3; "Message Type", ID)
        {
        }
        key(Key4; "Created On")
        {
        }
        key(Key5; "Register ID", ID, Context)
        {
        }
        key(Key6; Context, "Context Record ID")
        {
        }
    }

    fieldgroups
    {
    }

    var
        ContextErrorMessage: Record "Error Message";
        DataTypeManagement: Codeunit "Data Type Management";
        ErrorMessageMgt: Codeunit "Error Message Management";
        CachedLastID: Integer;
        CachedRegisterID: Guid;

        DevMsgNotTemporaryErr: Label 'This function can only be used when the record is temporary.';
        IfEmptyErr: Label '''%1'' in ''%2'' must not be blank.', Comment = '%1=caption of a field, %2=key of record';
        IfNotEmptyErr: Label '''%1'' in ''%2'' must be blank.', Comment = '%1=caption of a field, %2=key of record';
        IfLengthExceededErr: Label 'The maximum length of ''%1'' in ''%2'' is %3 characters. The actual length is %4.', Comment = '%1=caption of a field, %2=key of record, %3=integer, %4=integer';
        IfInvalidCharactersErr: Label '''%1'' in ''%2'' contains characters that are not valid.', Comment = '%1=caption of a field, %2=key of record';
        IfOutsideRangeErr: Label '''%1'' in ''%2'' is outside of the permitted range from %3 to %4.', Comment = '%1=caption of a field, %2=key of record, %3=integer, %4=integer';
        IfGreaterThanErr: Label '''%1'' in ''%2'' must be less than or equal to %3.', Comment = '%1=caption of a field, %2=key of record, %3=integer';
        IfLessThanErr: Label '''%1'' in ''%2'' must be greater than or equal to %3.', Comment = '%1=caption of a field, %2=key of record, %3=integer';
        IfEqualToErr: Label '''%1'' in ''%2'' must not be equal to %3.', Comment = '%1=caption of a field, %2=key of record, %3=integer';
        IfNotEqualToErr: Label '''%1'' in ''%2'' must be equal to %3.', Comment = '%1=caption of a field, %2=key of record, %3=integer';
        HasErrorsMsg: Label 'One or more errors were found. You must resolve all the errors before you can proceed.';

    trigger OnInsert()
    begin
        Rec.Validate("Created On", CurrentDateTime());
        if not Rec.Context then begin
            CachedLastID := Rec.ID;
            CachedRegisterID := Rec."Register ID";
        end;
    end;

    procedure LogIfEmpty(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option): Integer
    var
        RecordRef: RecordRef;
        TempRecordRef: RecordRef;
        FieldRef: FieldRef;
        EmptyFieldRef: FieldRef;
        NewDescription: Text;
        IsHandled: Boolean;
    begin
        if not DataTypeManagement.GetRecordRefAndFieldRef(RecRelatedVariant, FieldNumber, RecordRef, FieldRef) then
            exit(0);

        TempRecordRef.Open(RecordRef.Number(), true);
        EmptyFieldRef := TempRecordRef.Field(FieldNumber);

        if FieldRef.Value() <> EmptyFieldRef.Value() then
            exit(0);

        IsHandled := false;
        OnLogIfEmptyOnAfterCheckEmptyValue(FieldRef, EmptyFieldRef, IsHandled);
        if IsHandled then
            exit(0);

        NewDescription := StrSubstNo(IfEmptyErr, FieldRef.Caption(), Format(RecordRef.RecordId()));

        exit(LogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription));
    end;

    procedure LogIfNotEmpty(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option): Integer
    var
        RecordRef: RecordRef;
        TempRecordRef: RecordRef;
        FieldRef: FieldRef;
        EmptyFieldRef: FieldRef;
        NewDescription: Text;
        IsHandled: Boolean;
    begin
        if not DataTypeManagement.GetRecordRefAndFieldRef(RecRelatedVariant, FieldNumber, RecordRef, FieldRef) then
            exit(0);

        TempRecordRef.Open(RecordRef.Number(), true);
        EmptyFieldRef := TempRecordRef.Field(FieldNumber);

        if FieldRef.Value() = EmptyFieldRef.Value() then
            exit(0);

        IsHandled := false;
        OnLogIfNotEmptyOnAfterCheckEmptyValue(FieldRef, EmptyFieldRef, IsHandled);
        if IsHandled then
            exit(0);

        NewDescription := StrSubstNo(IfNotEmptyErr, FieldRef.Caption(), Format(RecordRef.RecordId()));

        exit(LogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription));
    end;

    procedure LogIfLengthExceeded(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option; MaxLength: Integer): Integer
    var
        RecordRef: RecordRef;
        FieldRef: FieldRef;
        NewDescription: Text;
        StringLength: Integer;
    begin
        if not DataTypeManagement.GetRecordRefAndFieldRef(RecRelatedVariant, FieldNumber, RecordRef, FieldRef) then
            exit(0);

        StringLength := StrLen(Format(FieldRef.Value()));
        if StringLength <= MaxLength then
            exit(0);

        NewDescription := StrSubstNo(IfLengthExceededErr, FieldRef.Caption(), Format(RecordRef.RecordId()), MaxLength, StringLength);

        exit(LogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription));
    end;

    procedure LogIfInvalidCharacters(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option; ValidCharacters: Text): Integer
    var
        RecordRef: RecordRef;
        FieldRef: FieldRef;
        NewDescription: Text;
    begin
        if not DataTypeManagement.GetRecordRefAndFieldRef(RecRelatedVariant, FieldNumber, RecordRef, FieldRef) then
            exit(0);

        if DelChr(Format(FieldRef.Value()), '=', ValidCharacters) = '' then
            exit(0);

        NewDescription := StrSubstNo(IfInvalidCharactersErr, FieldRef.Caption(), Format(RecordRef.RecordId()));

        exit(LogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription));
    end;

    procedure LogIfOutsideRange(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option; LowerBound: Variant; UpperBound: Variant): Integer
    var
        RecordRef: RecordRef;
        FieldRef: FieldRef;
        NewDescription: Text;
    begin
        if FieldValueIsWithinFilter(RecRelatedVariant, FieldNumber, RecordRef, FieldRef, '%1..%2', LowerBound, UpperBound) then
            exit(0);

        NewDescription := StrSubstNo(IfOutsideRangeErr, FieldRef.Caption(), Format(RecordRef.RecordId()), LowerBound, UpperBound);

        exit(LogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription));
    end;

    procedure LogIfGreaterThan(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option; LowerBound: Variant): Integer
    var
        RecordRef: RecordRef;
        FieldRef: FieldRef;
        NewDescription: Text;
    begin
        if FieldValueIsWithinFilter(RecRelatedVariant, FieldNumber, RecordRef, FieldRef, '<=%1', LowerBound, '') then
            exit(0);

        NewDescription := StrSubstNo(IfGreaterThanErr, FieldRef.Caption(), Format(RecordRef.RecordId()), LowerBound);

        exit(LogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription));
    end;

    procedure LogIfLessThan(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option; UpperBound: Variant): Integer
    var
        RecordRef: RecordRef;
        FieldRef: FieldRef;
        NewDescription: Text;
    begin
        if FieldValueIsWithinFilter(RecRelatedVariant, FieldNumber, RecordRef, FieldRef, '>=%1', UpperBound, '') then
            exit(0);

        NewDescription := StrSubstNo(IfLessThanErr, FieldRef.Caption(), Format(RecordRef.RecordId()), UpperBound);

        exit(LogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription));
    end;

    procedure LogIfEqualTo(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option; ValueVariant: Variant): Integer
    var
        RecordRef: RecordRef;
        FieldRef: FieldRef;
        NewDescription: Text;
    begin
        if FieldValueIsWithinFilter(RecRelatedVariant, FieldNumber, RecordRef, FieldRef, '<>%1', ValueVariant, '') then
            exit(0);

        NewDescription := StrSubstNo(IfEqualToErr, FieldRef.Caption(), Format(RecordRef.RecordId()), ValueVariant);

        exit(LogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription));
    end;

    procedure LogIfNotEqualTo(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option; ValueVariant: Variant): Integer
    var
        RecordRef: RecordRef;
        FieldRef: FieldRef;
        NewDescription: Text;
    begin
        if FieldValueIsWithinFilter(RecRelatedVariant, FieldNumber, RecordRef, FieldRef, '=%1', ValueVariant, '') then
            exit(0);

        NewDescription := StrSubstNo(IfNotEqualToErr, FieldRef.Caption(), Format(RecordRef.RecordId()), ValueVariant);

        exit(LogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription));
    end;

    procedure LogContextFieldError(ContextFieldNumber: Integer; NewDescription: Text; RecRelatedVariant: Variant; SourceFieldNumber: Integer; SupportURL: Text[250]): Integer
    var
        RecordRef: RecordRef;
    begin
        Rec.LogSimpleMessage("Message Type", NewDescription);
        Rec.Validate("Support Url", SupportURL);
        Rec.Validate("Context Field Number", ContextFieldNumber);
        case true of
            RecRelatedVariant.IsInteger():
                Rec.Validate("Table Number", RecRelatedVariant);
            DataTypeManagement.GetRecordRef(RecRelatedVariant, RecordRef):
                Rec.Validate("Record ID", RecordRef.RecordId());
        end;
        Rec.Validate("Field Number", SourceFieldNumber);
        Rec.Modify(true);

        exit(Rec.ID);
    end;

    procedure LogSimpleMessage(MessageType: Option; NewDescription: Text): Integer
    begin
        exit(LogSimpleMessage(MessageType, NewDescription, ''));
    end;

    procedure LogSimpleMessage(MessageType: Option; NewDescription: Text; ErrorCallStack: Text): Integer
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeLogSimpleMessage(MessageType, NewDescription, IsHandled);
        if IsHandled then
            exit;

        AssertRecordTemporaryOrInContext();

        Rec.Init();
        Rec.Validate("Message Type", MessageType);
        Rec.Validate("Message", CopyStr(NewDescription, 1, MaxStrLen(Rec."Message")));
        Rec.Validate("Context Record ID", ContextErrorMessage."Context Record ID");
        Rec.Validate("Context Field Number", ContextErrorMessage."Context Field Number");
        Rec.Validate("Additional Information", ContextErrorMessage."Additional Information");
        if ErrorCallStack <> '' then
            Rec.SetErrorCallStack(ErrorCallStack)
        else
            if Rec."Message Type" = Rec."Message Type"::Error then
                Rec.SetErrorCallStack(ErrorMessageMgt.GetCurrCallStack());

        Rec.ID := Rec.IsTemporary() ? FindLastMessageID() + 1 : 0;
        Rec.Insert(true);

        exit(Rec.ID);
    end;

    procedure LogMessage(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option; NewDescription: Text): Integer
    var
        RecordRef: RecordRef;
        ErrorMessageID: Integer;
        TableNumber: Integer;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeLogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription, IsHandled);
        if IsHandled then
            exit;

        if RecRelatedVariant.IsInteger() then
            TableNumber := RecRelatedVariant
        else begin
            if not DataTypeManagement.GetRecordRef(RecRelatedVariant, RecordRef) then
                exit(0);

            ErrorMessageID := Rec.FindRecord(RecordRef.RecordId(), FieldNumber, MessageType, NewDescription);
            if ErrorMessageID <> 0 then
                exit(ErrorMessageID);
        end;

        Rec.LogSimpleMessage(MessageType, NewDescription);
        if TableNumber = 0 then
            Rec.Validate("Record ID", RecordRef.RecordId())
        else
            Rec.Validate("Table Number", TableNumber);
        Rec.Validate("Field Number", FieldNumber);
        Rec.Modify(true);

        exit(Rec.ID);
    end;

    procedure LogDetailedMessage(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option; NewDescription: Text; AdditionalInformation: Text[250]; SupportUrl: Text[250]): Integer
    begin
        Rec.LogMessage(RecRelatedVariant, FieldNumber, MessageType, NewDescription);
        Rec.Validate("Additional Information", AdditionalInformation);
        Rec.Validate("Support Url", SupportUrl);
        Rec.Modify(true);

        exit(Rec.ID);
    end;

    procedure LogLastError()
    begin
        Rec.LogLastError(true);
    end;

    internal procedure LogLastError(ClearError: Boolean)
    begin
        if (GetLastErrorCode() <> '') and (GetLastErrorText() <> '') then begin
            if ErrorMessageMgt.GetLastContext(Rec) then begin
                Rec.Validate("Message Type", Rec."Message Type"::Error);
                Rec.Validate("Message", CopyStr(GetLastErrorText(), 1, MaxStrLen(Rec."Message")));
                Rec.SetErrorCallStack(GetLastErrorCallStack());
                Rec.ID := Rec.IsTemporary() ? FindLastMessageID() + 1 : 0;
                Rec.Insert(true);
            end else
                Rec.LogSimpleMessage("Message Type"::Error, GetLastErrorText(), GetLastErrorCallStack());

            if ClearError then
                ClearLastError();
        end;
    end;

    procedure AddMessageDetails(MessageID: Integer; AdditionalInformation: Text[250]; SupportUrl: Text[250])
    begin
        if MessageID = 0 then
            exit;

        Rec.Get(MessageID);
        Rec.Validate("Additional Information", AdditionalInformation);
        Rec.Validate("Support Url", SupportUrl);
        Rec.Modify(true);
    end;

    local procedure FindLastMessageID(): Integer
    var
        TempErrorMessage: Record "Error Message" temporary;
    begin
        TempErrorMessage.Copy(Rec, true);
        TempErrorMessage.Reset();
        if TempErrorMessage.FindLast() then
            exit(TempErrorMessage.ID);
    end;

    procedure FindLastID(): Integer
    begin
        Rec.Reset();
        if Rec.FindLast() then
            exit(Rec.ID);
    end;

    procedure GetContext(var ErrorMessage: Record "Error Message") Result: Boolean
    begin
        Result := ErrorMessageMgt.GetTopContext(ErrorMessage);
        ContextErrorMessage := ErrorMessage;
    end;

    procedure GetLastID(): Integer
    begin
        ClearFilters();
        Rec.SetRange(Context, false);
        Rec.SetRange("Register ID", Rec."Register ID");

        if not Rec.FindLast() then
            exit(0);

        CachedLastID := Rec.ID;
        CachedRegisterID := Rec."Register ID";
        exit(Rec.ID);
    end;

    procedure GetCachedLastID(): Integer
    begin
        if CachedRegisterID <> Rec."Register ID" then
            exit(Rec.GetLastID());

        exit(CachedLastID);
    end;

    local procedure GetTableNo(RecordID: RecordID): Integer
    begin
        exit(RecordID.TableNo());
    end;

    procedure SetContext(ContextRecordVariant: Variant)
    var
        RecordRef: RecordRef;
    begin
        Clear(ContextErrorMessage);
        case true of
            ContextRecordVariant.IsRecordId():
                ContextErrorMessage.Validate("Context Record ID", ContextRecordVariant);
            ContextRecordVariant.IsInteger():
                ContextErrorMessage."Context Table Number" := ContextRecordVariant;
            DataTypeManagement.GetRecordRef(ContextRecordVariant, RecordRef):
                ContextErrorMessage.Validate("Context Record ID", RecordRef.RecordId())
        end;
    end;

    procedure ClearLog()
    begin
        AssertRecordTemporaryOrInContext();

        ClearFilters();
        SetContextFilter();
        Rec.DeleteAll(true);
    end;

    procedure ClearLogRec(RecordVariant: Variant)
    begin
        AssertRecordTemporaryOrInContext();

        ClearFilters();
        SetContextFilter();
        SetRecordFilter(RecordVariant);
        Rec.DeleteAll(true);
    end;

    procedure HasErrorMessagesRelatedTo(RecRelatedVariant: Variant): Boolean
    var
        RecordRef: RecordRef;
    begin
        AssertRecordTemporaryOrInContext();

        if not DataTypeManagement.GetRecordRef(RecRelatedVariant, RecordRef) then
            exit(false);

        ClearFilters();
        if not Rec.IsTemporary() then
            Rec.ReadIsolation := ReadIsolation::ReadCommitted;
        SetContextFilter();
        Rec.SetRange("Record ID", RecordRef.RecordId());
        exit(not Rec.IsEmpty());
    end;

    procedure ErrorMessageCount(LowestSeverityMessageType: Option): Integer
    begin
        AssertRecordTemporaryOrInContext();

        ClearFilters();
        if not Rec.IsTemporary() then
            Rec.ReadIsolation := ReadIsolation::ReadCommitted;
        SetContextFilter();
        Rec.SetRange(Context, false);
        Rec.SetRange("Message Type", Rec."Message Type"::Error, LowestSeverityMessageType);
        exit(Rec.Count());
    end;

    procedure HasErrors(ShowMessage: Boolean): Boolean
    begin
        if ErrorMessageCount(Rec."Message Type"::Error) = 0 then
            exit(false);

        if ShowMessage and GuiAllowed() then
            Message(HasErrorsMsg);

        exit(true);
    end;

    procedure ShowErrors() IsPageOpen: Boolean
    var
        ErrorMessages: Page "Error Messages";
        IsHandled: Boolean;
    begin
        AssertRecordTemporaryOrInContext();

        ClearFilters();
        Rec.SetRange(Context, false);
        if Rec.IsEmpty() then
            Error(GetLastErrorText());

        IsHandled := false;
        OnShowErrorsOnBeforeErrorMessagesRun(Rec, IsPageOpen, IsHandled);
        if IsHandled then
            exit(IsPageOpen);

        if GuiAllowed() then begin
            ErrorMessages.SetRecords(Rec);
            ErrorMessages.Run();
            IsPageOpen := true;
        end else begin
            Rec.SetRange("Message Type", Rec."Message Type"::Error);
            if Rec.FindFirst() then
                Error(Rec."Message");
            IsPageOpen := false;
        end;
    end;

    procedure ShowErrorMessages(RollBackOnError: Boolean) ErrorString: Text
    var
        ErrorMessages: Page "Error Messages";
        IsHandled: Boolean;
    begin
        AssertRecordTemporaryOrInContext();

        ClearFilters();
        if not Rec.IsTemporary() then
            Rec.ReadIsolation := ReadIsolation::ReadCommitted;
        SetContextFilter();
        Rec.SetRange(Context, false);
        if Rec.IsEmpty() then
            exit;

        IsHandled := false;
        OnShowErrorMessagesOnBeforeErrorMessagesRun(Rec, ErrorString, IsHandled);
        if IsHandled then
            exit(ErrorString);

        if GuiAllowed() then begin
            ErrorMessages.SetRecords(Rec);
            ErrorMessages.Run();
        end;

        ErrorString := Rec.ToString();

        if RollBackOnError then
            if Rec.HasErrors(false) then
                Error('');

        exit;
    end;

    procedure ToString(): Text
    var
        ErrorString: Text;
    begin
        AssertRecordTemporaryOrInContext();

        ClearFilters();
        if not Rec.IsTemporary() then
            Rec.ReadIsolation := ReadIsolation::ReadCommitted;
        SetContextFilter();
        Rec.SetCurrentKey("Message Type", Rec.ID);
        Rec.SetRange(Context, false);
        if Rec.FindSet() then
            repeat
                if ErrorString <> '' then
                    ErrorString += '\';
                ErrorString += Format(Rec."Message Type") + ': ' + Rec."Message";
            until Rec.Next() = 0;
        ClearFilters();
        exit(ErrorString);
    end;

    procedure ThrowError()
    begin
        AssertRecordTemporaryOrInContext();

        if Rec.HasErrors(false) then
            Error(Rec.ToString());
    end;

    local procedure FieldValueIsWithinFilter(RecRelatedVariant: Variant; FieldNumber: Integer; var RecordRef: RecordRef; var FieldRef: FieldRef; FilterString: Text; FilterValue1: Variant; FilterValue2: Variant): Boolean
    var
        TempRecordRef: RecordRef;
        TempFieldRef: FieldRef;
    begin
        if not DataTypeManagement.GetRecordRefAndFieldRef(RecRelatedVariant, FieldNumber, RecordRef, FieldRef) then
            exit(false);

        TempRecordRef.Open(RecordRef.Number(), true);
        TempRecordRef.Init();
        TempFieldRef := TempRecordRef.Field(FieldNumber);
        TempFieldRef.Value(FieldRef.Value());
        TempRecordRef.Insert();

        TempFieldRef.SetFilter(FilterString, FilterValue1, FilterValue2);

        exit(not TempRecordRef.IsEmpty());
    end;

    procedure FindRecord(RecordID: RecordID; FieldNumber: Integer; MessageType: Option; NewDescription: Text) FoundID: Integer
    begin
        ClearFilters();
        if not Rec.IsTemporary() then
            Rec.ReadIsolation := ReadIsolation::ReadCommitted;
        SetContextFilter();
        Rec.SetRange(Context, false);
        Rec.SetRange("Record ID", RecordID);
        Rec.SetRange("Field Number", FieldNumber);
        Rec.SetRange("Message Type", MessageType);
        Rec.SetRange("Message", CopyStr(NewDescription, 1, MaxStrLen(Rec."Message")));
        FoundID := 0;
        if Rec.FindFirst() then
            FoundID := Rec.ID;
        ClearFilters();
    end;

    local procedure AssertRecordTemporary()
    begin
        if not Rec.IsTemporary() then
            Error(DevMsgNotTemporaryErr);
    end;

    local procedure AssertRecordTemporaryOrInContext()
    begin
        if (ContextErrorMessage.ID <> 0) or (ContextErrorMessage.ID = 0) and (ContextErrorMessage."Context Table Number" = 0) then begin
            Clear(ContextErrorMessage);
            if not ErrorMessageMgt.GetTopContext(ContextErrorMessage) then
                AssertRecordTemporary();
        end else
            if ContextErrorMessage."Context Table Number" = 0 then
                AssertRecordTemporary();
    end;

    procedure CopyToTemp(var TempErrorMessage: Record "Error Message" temporary)
    var
        TempID: Integer;
    begin
        if not Rec.IsTemporary() then
            Rec.ReadIsolation := ReadIsolation::ReadCommitted;
        if not Rec.FindSet() then
            exit;

        TempID := TempErrorMessage.FindLastID();
        repeat
            if TempErrorMessage.FindRecord(Rec."Record ID", Rec."Field Number", Rec."Message Type", Rec."Message") = 0 then begin
                TempID += 1;
                TempErrorMessage := Rec;
                TempErrorMessage.ID := TempID;
                TempErrorMessage."Reg. Err. Msg. System ID" := Rec.SystemId;
                TempErrorMessage.SetErrorCallStack(Rec.GetErrorCallStack());
                TempErrorMessage.Insert();
            end;
        until Rec.Next() = 0;
        TempErrorMessage.Reset();
    end;

    procedure CopyFromTemp(var TempErrorMessage: Record "Error Message" temporary)
    var
        ErrorMessage: Record "Error Message";
    begin
        if not TempErrorMessage.FindSet() then
            exit;

        repeat
            ErrorMessage := TempErrorMessage;
            ErrorMessage.ID := 0;
            ErrorMessage.SetErrorCallStack(TempErrorMessage.GetErrorCallStack());
            ErrorMessage.Insert(true);
        until TempErrorMessage.Next() = 0;
    end;

    procedure CopyFromContext(ContextRecordVariant: Variant)
    var
        ErrorMessage: Record "Error Message";
        RecordRef: RecordRef;
    begin
        AssertRecordTemporary();

        if not DataTypeManagement.GetRecordRef(ContextRecordVariant, RecordRef) then
            exit;

        ErrorMessage.SetRange("Context Record ID", RecordRef.RecordId());
        ErrorMessage.CopyToTemp(Rec);
    end;

    local procedure ClearFilters()
    var
        LocalContextErrorMessage: Record "Error Message";
    begin
        LocalContextErrorMessage := ContextErrorMessage;
        Rec.Reset();
        ContextErrorMessage := LocalContextErrorMessage;
    end;

    local procedure SetContextFilter()
    begin
        if ContextErrorMessage."Context Table Number" = 0 then
            Rec.SetRange("Context Record ID")
        else
            Rec.SetRange("Context Record ID", ContextErrorMessage."Context Record ID");
    end;

    local procedure SetRecordFilter(RecordVariant: Variant)
    var
        RecordRef: RecordRef;
    begin
        DataTypeManagement.GetRecordRef(RecordVariant, RecordRef);
        Rec.SetRange("Record ID", RecordRef.RecordId());
    end;

    procedure GetErrorCallStack(): Text
    var
        InStream: InStream;
        LF: Text[1];
    begin
        if not Rec."Error Call Stack".HasValue() then
            exit('');
        Rec.CalcFields("Error Call Stack");
        Rec."Error Call Stack".CreateInStream(InStream, TextEncoding::Windows);

        LF[1] := 10; // Line feed, '\n'
        exit(ReadAsTextWithSeparator(InStream, LF));
    end;

    procedure SetErrorCallStack(NewCallStack: Text)
    var
        OutStream: OutStream;
    begin
        Rec."Error Call Stack".CreateOutStream(OutStream, TextEncoding::Windows);
        OutStream.Write(NewCallStack);
    end;

    procedure ShowErrorCallStack()
    var
        CallStack: Text;
    begin
        CallStack := Rec.GetErrorCallStack();
        if CallStack <> '' then
            Message(CallStack);
    end;

    local procedure ReadAsTextWithSeparator(InStream: InStream; LineSeparator: Text): Text
    var
        Tb: TextBuilder;
        ContentLine: Text;
    begin
        InStream.ReadText(ContentLine);
        Tb.Append(ContentLine);
        while not InStream.EOS do begin
            InStream.ReadText(ContentLine);
            Tb.Append(LineSeparator);
            Tb.Append(ContentLine);
        end;

        exit(Tb.ToText());
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeLogMessage(RecRelatedVariant: Variant; FieldNumber: Integer; MessageType: Option; NewDescription: Text; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeLogSimpleMessage(MessageType: Option; NewDescription: Text; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnLogIfEmptyOnAfterCheckEmptyValue(FieldRef: FieldRef; EmptyFieldRef: FieldRef; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnLogIfNotEmptyOnAfterCheckEmptyValue(FieldRef: FieldRef; EmptyFieldRef: FieldRef; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnShowErrorMessagesOnBeforeErrorMessagesRun(var ErrorMessage: Record "Error Message"; var ErrorString: Text; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnShowErrorsOnBeforeErrorMessagesRun(var ErrorMessage: Record "Error Message"; var IsPageOpen: Boolean; var IsHandled: Boolean)
    begin
    end;
}