top of page
Search

Enhancing Transparency in Microsoft Dynamics 365 Business Central: creators and modifiers of records.

  • Russell Kallman
  • Apr 21
  • 4 min read

In today's fast-paced business world, transparency and accountability are vital for smooth operations. Back in 2020 Wave 2, Microsoft helpfully added four news fields to track who created and modified a record in the database. As always Zhu did a good job of running through the change.


Unfortunately, as is too often the case, Microsoft left the job undone in many respects. The feature is not integrated into the existing change logs and isn't shown to users by default on any pages.


The default answer on the forums is to use the Page Inspector as seen in this video by All My Systems.


At least in our business that is both too technically challenging and confusing. Good maybe for administrators, but few others.


Then there are the appsource apps. There is Created-by on Documents which adds specific fields, but more has the purpose of tracking on posted documents who created the original. Also Change Log Quick Access and a few others in that vein.


Key requirements for our solution


  1. We wanted the information to appear consistently always in the same place

  2. We wanted the information to take up as little room as possible

  3. We wanted the information to be digestible by a regular non-power user

  4. We wanted the core code to be re-usable and to drop-in and be self-aware of its own context.


Leveraging Built-in Features in Business Central


Microsoft Dynamics 365 Business Central comes equipped with several features to effectively help us achieve this even without object orientation, multiple inherentence and interfaces. In no particular order we used:


  • Subpage with subpage links similar to the Doc. Attach List Factbox

  • Temporary table given us fields to set filters on but without loading any data

  • Similar to API pattern we analyse filters and use them to load specific data

  • Recordref and Fieldref to get consistent data regardless of the record



The new CardPart page User and Change Info


This is the key page which does all the work, with one support codeunit that performs a few helper functions used elsewhere in our code.


The supporting codeunit:


  • Displays a change log specific to the record being viewed

  • Translated text to proper case for readibility of user names (less shouting)


A few key innovations:


  • Rather than displaying four fields in a grid or group, often that contain duplicate information or provide confusing information, we interpret the data for the user figuring our how many people are involved and compensate for the fact that Business Central as some atypical meanings of modified/created.

  • We take the filters and use them to interpret context, so this page itself does not need to be aware of its context at all.


using System.Diagnostics;
using System.Security.User;
using System.Security.AccessControl;

page 50006 "User and Change Info"
{
    PageType = CardPart;
    ApplicationArea = All;
    UsageCategory = Administration;
    SourceTable = "Change Log Entry";
    SourceTableTemporary = true;
    layout
    {
        area(Content)
        {
            group(CreatedByGroup)
            {
                ShowCaption = false;
                field(CreatedBy; GetShortenedUserChangeHistory())
                {
                    Caption = 'User Details';
                    ToolTip = 'Shows key information on what was changed and when';
                    ShowCaption = false;
                }
            }
        }
    }
    actions
    {
        area(Processing)
        {
            action(History)
            {
                ApplicationArea = All;
                Image = History;
                Caption = 'Change Log Entries';
                ToolTip = 'Shows entries in the change log if they exist. Specific logging needs to be turned on for fields';
                Enabled = ChangeLogExists;

                trigger OnAction()
                var

                begin
                    CommonCU.ShowValueHistory(ReferenceRecRef.RecordId);
                end;
            }
        }
    }

    trigger OnFindRecord(Which: Text): Boolean

    var
        ReferenceRecordId: RecordId;
        TableNoFilter: Text;
        ReferenceTableNo: Integer;
        ReferenceSystemId: Guid;
        RecordIDFilter: Text;
        SystemIdFilter: Text;
    begin
        Rec.FilterGroup(4);

        RecordIDFilter := Rec.GetFilter("Record ID");
        SystemIdFilter := Rec.GetFilter("Changed Record SystemId");
        TableNoFilter := Rec.GetFilter("Table No.");

        if (SystemIdFilter = '') or (TableNoFilter = '') then exit;  //no valid filters
        Evaluate(ReferenceTableNo, TableNoFilter);
        Evaluate(ReferenceRecordId, RecordIDFilter);
        Evaluate(ReferenceSystemId, SystemIdFilter);

        //ReferenceRecRef := ReferenceRecordId.GetRecord();
        if ReferenceRecRef.Number = 0 then
            ReferenceRecRef.Open(ReferenceTableNo);
        ReferenceRecRef.GetBySystemId(ReferenceSystemId);
        ChangeLogExists := CommonCU.ValueHistoryExists(ReferenceRecRef.RecordId);
    end;

    var
        ReferenceRecRef: RecordRef;

    local procedure GetUserNameFromSecurityId(UserSecurityID: Guid): Code[80]

    var
        User: Record User;

    begin
        User.SetLoadFields("Full Name");
        User.Get(UserSecurityID);
        exit(user."Full Name");
    end;

    local procedure GetCreatedUserNameFromRecRef(): Text[80]
    var
        FieldRef: FieldRef;
    begin
        FieldRef := ReferenceRecRef.Field(ReferenceRecRef.SystemCreatedByNo);
        exit(CommonCU.ConvertToProperCase(GetUserNameFromSecurityId(FieldRef.Value)));
    end;

    local procedure GetModifiedUserNameFromRecRef(): Text[80]
    var
        FieldRef: FieldRef;
    begin
        FieldRef := ReferenceRecRef.Field(ReferenceRecRef.SystemModifiedByNo);
        exit(CommonCU.ConvertToProperCase(GetUserNameFromSecurityId(FieldRef.Value)));
    end;

    local procedure GetCreatedOnDateTime(): DateTime
    var
        FieldRef: FieldRef;
    begin
        FieldRef := ReferenceRecRef.Field(ReferenceRecRef.SystemCreatedAtNo);
        exit(FieldRef.Value);
    end;

    local procedure GetModifiedOnDateTime(): DateTime
    var
        FieldRef: FieldRef;
    begin
        FieldRef := ReferenceRecRef.Field(ReferenceRecRef.SystemModifiedAtNo);
        exit(FieldRef.Value);
    end;

    local procedure GetShortenedUserChangeHistory(): Text[250]
    var
        CreatedUser: Text[80];
        ModifiedUser: Text[80];
        Duration: Duration;
        GapInMinutes: Decimal;
        SameUser: Boolean;
        SameTime: Boolean;

    begin
        CreatedUser := GetCreatedUserNameFromRecRef();
        ModifiedUser := GetModifiedUserNameFromRecRef();

        if CreatedUser = ModifiedUser then SameUser := true;
        Duration := GetModifiedOnDateTime() - GetCreatedOnDateTime();
        GapInMinutes := Duration / 60000;
        if GapInMinutes < 10 then SameTime := true;

        if SameUser then
            if SameTime then
                exit(StrSubstNo('Created by %1 on %2 and not yet modified', CreatedUser, GetCreatedOnDateTime()))
            else // implied different time
                exit(StrSubstNo('Created by %1 on %2 and last modified on %3', CreatedUser, GetCreatedOnDateTime(), GetModifiedOnDateTime()))
        else // implied different user
            if SameTime then
                exit(StrSubstNo('Created by %1 on %2 and quickly modified by %3', CreatedUser, GetCreatedOnDateTime(), ModifiedUser))
            else //timplied different time and user
                exit(StrSubstNo('Created by %1 on %2 and last modified by %3 on %4', CreatedUser, GetCreatedOnDateTime(), ModifiedUser, GetModifiedOnDateTime()));

    end;

    var
        CommonCU: CodeUnit "TFB Common Library";
        ChangeLogExists: Boolean;

}

All that is needed to add this information is on one of your own pages to add the part and the end of the content section, or add it in an AddLast section to a page extension. The only change required on these pages is to change the Constant for the Table No. field being passed. We haven't found a way to pass that dynamically yet (open to ideas).


  part(UserAndChange; "User and Change Info")
            {
                ApplicationArea = All;
                SubPageLink = "Table No." = Const(Database::"Non-Conformance Report"), "Changed Record SystemId" = field(SystemId);
            }


Boosting Trust Through Transparency


We often have a question around who was the last user to change a record, or a question of when a record was last updated. The lack of inbuilt functionality within business central to facilitate that answer quickly is a disappointment.

 
 
 

Comentarios


  • Facebook
  • Twitter
  • LinkedIn

©2021 by Unasked. Created using the free tools of wix.com

bottom of page