Enhancing the Recurring General Journal with Automated Approval Workflows in Dynamics 365 Business Central - CloudFronts

Enhancing the Recurring General Journal with Automated Approval Workflows in Dynamics 365 Business Central

In any accounting or financial system, ensuring that the proper approvals are in place is critical for maintaining audit trails and accountability. Microsoft Dynamics 365 Business Central (BC) offers powerful approval workflows that can automate this process, ensuring compliance and accuracy in your financial entries. This blog will guide you through creating an extension for the Recurring General Journal page, enabling automated approval requests for journal batches and journal lines.

Understanding the Recurring General Journal

The Recurring General Journal in Business Central allows users to post transactions that occur periodically, such as monthly expenses. However, posting journal entries often requires an approval process to guarantee compliance and accuracy. Our extension will empower users to request approvals, cancel requests, and manage the approval status directly from the Recurring General Journal.

Solution Outline

This extension will:

  • Add fields to display the approval status for both journal batches and individual journal lines.
  • Provide actions to send approval requests for both the entire journal batch and individual journal lines.
  • Allow users to cancel approval requests for the journal batch.
  • Include actions to approve, reject, and delegate approval requests.

Code

Here’s the complete PageExtension code that extends the Recurring General Journal page:

pageextension 50103 RecurringGenJnl extends “Recurring General Journal”

{

    layout

    {

        addafter(CurrentJnlBatchName)

        {

            field(StatusLine; Rec.StatusBatch)

            {

                ApplicationArea = All;

                Caption = ‘Approval Status’;

                Editable = false;

                Visible = true;

            }

        }

    }

    actions

    {

        addafter(“F&unctions”)

        {

            group(“Request Approval”)

            {

                Caption = ‘Request Approval’;

                group(SendApprovalRequest)

                {

                    Caption = ‘Send Approval Request’;

                    Image = SendApprovalRequest;

                    action(SendApprovalRequestJournalLine)

                    {

                        ApplicationArea = Basic, Suite;

                        Caption = ‘Journal Batch’;

                        Image = SendApprovalRequest;

                        Enabled = true;

                        ToolTip = ‘Send all journal lines for approval, also those that you may not see because of filters.’;

                        trigger OnAction()

                        var

                            GenJournalLine: Record “Gen. Journal Line”;

                            GenJournalBatch: Record “Gen. Journal Batch”;

                            ApprovalsMgmt: Codeunit “Approvals Mgmt.”;

                            IsLineValid: Boolean;

                        begin

                            GenJournalBatch.Get(Rec.”Journal Template Name”, Rec.”Journal Batch Name”);

                            if GenJournalLine.SetCurrentKey(“Journal Template Name”, “Journal Batch Name”) then begin

                                GenJournalLine.SetRange(“Journal Template Name”, GenJournalBatch.”Journal Template Name”);

                                GenJournalLine.SetRange(“Journal Batch Name”, GenJournalBatch.”Name”);

                                if GenJournalLine.FindSet() then

                                    ApprovalsMgmt.TrySendJournalBatchApprovalRequest(GenJournalLine);

                                SetControlAppearanceFromBatch();

                                SetControlAppearance();

                            end;

                        end;

                    }

                }

                group(CancelApprovalRequest)

                {

                    Caption = ‘Cancel Approval Request’;

                    Image = Cancel;

                    Enabled = true;

                    action(CancelApprovalRequestJournalBatch)

                    {

                        ApplicationArea = Basic, Suite;

                        Caption = ‘Journal Batch’;

                        Image = CancelApprovalRequest;

                        ToolTip = ‘Cancel sending all journal lines for approval, also those that you may not see because of filters.’;

                        trigger OnAction()

                        var

                            ApprovalsMgmt: Codeunit “Approvals Mgmt.”;

                            GenJournalBatch: Record “Gen. Journal Batch”;

                            GenJournalLine: Record “Gen. Journal Line”;

                            IsLineValid: Boolean;

                        begin

                            GenJournalBatch.Get(Rec.”Journal Template Name”, Rec.”Journal Batch Name”);

                            //ApprovalsMgmt.TryCancelJournalBatchApprovalRequest(GenJournalLine);

                            if GenJournalLine.SetCurrentKey(“Journal Template Name”, “Journal Batch Name”) then begin

                                GenJournalLine.SetRange(“Journal Template Name”, GenJournalBatch.”Journal Template Name”);

                                GenJournalLine.SetRange(“Journal Batch Name”, GenJournalBatch.”Name”);

                                if GenJournalLine.FindSet() then

                                    IsLineValid := true;

                                if (GenJournalLine.Status.ToUpper() = ‘OPEN’) or

                                   (GenJournalLine.Status.ToUpper() = ‘APPROVED’) then begin

                                    IsLineValid := false;

                                end;

                                if IsLineValid then

                                    ApprovalsMgmt.TryCancelJournalBatchApprovalRequest(GenJournalLine);

                                //ApprovalsMgmt.TryCancelJournalLineApprovalRequests(GenJournalLine);

                            end else begin

                                Message(‘Gen. Journal Batch not found for the provided template and batch names.’);

                            end;

                            SetControlAppearanceFromBatch();

                            SetControlAppearance();

                        end;

                    }

                }

                group(Approval)

                {

                    Caption = ‘Approval’;

                    action(Approve)

                    {

                        ApplicationArea = All;

                        Caption = ‘Approve’;

                        Image = Approve;

                        ToolTip = ‘Approve the requested changes.’;

                        Visible = true;

                        trigger OnAction()

                        var

                            ApprovalsMgmt: Codeunit “Approvals Mgmt.”;

                        begin

                            ApprovalsMgmt.ApproveGenJournalLineRequest(Rec);

                        end;

                    }

                    action(Reject)

                    {

                        ApplicationArea = All;

                        Caption = ‘Reject’;

                        Image = Reject;

                        ToolTip = ‘Reject the approval request.’;

                        Visible = true;

                        trigger OnAction()

                        var

                            ApprovalsMgmt: Codeunit “Approvals Mgmt.”;

                        begin

                            ApprovalsMgmt.RejectGenJournalLineRequest(Rec);

                        end;

                    }

                    action(Delegate)

                    {

                        ApplicationArea = All;

                        Caption = ‘Delegate’;

                        Image = Delegate;

                        ToolTip = ‘Delegate the approval to a substitute approver.’;

                        Visible = OpenApprovalEntriesExistForCurrUser;

                        trigger OnAction()

                        var

                            ApprovalsMgmt: Codeunit “Approvals Mgmt.”;

                        begin

                            ApprovalsMgmt.DelegateGenJournalLineRequest(Rec);

                        end;

                    }

                }

            }

        }

    }

    trigger OnOpenPage()

    begin

        SetControlAppearanceFromBatch();

    end;

    trigger OnAfterGetCurrRecord()

    var

        GenJournalBatch: Record “Gen. Journal Batch”;

    begin

        EnableApplyEntriesAction();

        SetControlAppearance();

        if GenJournalBatch.Get(GetJournalTemplateNameFromFilter(), CurrentJnlBatchName) then

            SetApprovalStateForBatch(GenJournalBatch, Rec, OpenApprovalEntriesExistForCurrUser, OpenApprovalEntriesOnJnlBatchExist, OpenApprovalEntriesOnBatchOrAnyJnlLineExist, CanCancelApprovalForJnlBatch, CanRequestFlowApprovalForBatch, CanCancelFlowApprovalForBatch, CanRequestFlowApprovalForBatchAndAllLines, ApprovalEntriesExistSentByCurrentUser, EnabledGenJnlBatchWorkflowsExist, EnabledGenJnlLineWorkflowsExist);

        ApprovalMgmt.GetGenJnlBatchApprovalStatus(Rec, Rec.StatusBatch, EnabledGenJnlBatchWorkflowsExist);

    end;

    trigger OnAfterGetRecord()

    var

        GenJournalLine: Record “Gen. Journal Line”;

        GenJournalBatch: Record “Gen. Journal Batch”;

        ApprovalsMgmt: Codeunit “Approvals Mgmt.”;

    begin

        GenJnlManagement.GetAccounts(Rec, AccName, BalAccName);

        Rec.ShowShortcutDimCode(ShortcutDimCode);

        SetUserInteractions();

        GenJournalBatch.Get(Rec.”Journal Template Name”, Rec.”Journal Batch Name”);

        if GenJournalLine.SetCurrentKey(“Journal Template Name”, “Journal Batch Name”) then begin

            GenJournalLine.SetRange(“Journal Template Name”, GenJournalBatch.”Journal Template Name”);

            GenJournalLine.SetRange(“Journal Batch Name”, GenJournalBatch.”Name”);

            if GenJournalLine.FindSet() then

                repeat

                    ApprovalMgmt.GetGenJnlLineApprovalStatus(Rec, Rec.StatusBatch, EnabledGenJnlLineWorkflowsExist);

                until GenJournalLine.Next() = 0;

        end;

    end;

    trigger OnModifyRecord(): Boolean

    var

        GenJournalBatch: Record “Gen. Journal Batch”;

        GenJournalLine: Record “Gen. Journal Line”;

        ApprovalsMgmt: Codeunit “Approvals Mgmt.”;

        IsLineValid: Boolean;

    begin

        SetUserInteractions();

        ApprovalMgmt.CleanGenJournalApprovalStatus(Rec, Rec.StatusBatch, Rec.Status);

        if Rec.StatusBatch = ‘Pending Approval’ then

            Error(‘Modification not allowed. The journal batch “%1” has entries with a status of “Pending Approval”. Please approve, reject, or cancel these entries before making changes.’, GenJournalBatch.”Name”);

    end;

    var

        GenJnlManagement: Codeunit GenJnlManagement;

        JournalErrorsMgt: Codeunit “Journal Errors Mgt.”;

        BackgroundErrorHandlingMgt: Codeunit “Background Error Handling Mgt.”;

        ApprovalMgmt: Codeunit “Approvals Mgmt.”;

        ChangeExchangeRate: Page “Change Exchange Rate”;

        GLReconcile: Page Reconciliation;

        GenJnlBatchApprovalStatus: Text[20];

        GenJnlLineApprovalStatus: Text[20];

        Balance: Decimal;

        TotalBalance: Decimal;

        NumberOfRecords: Integer;

        ShowBalance: Boolean;

        ShowTotalBalance: Boolean;

        HasIncomingDocument: Boolean;

        BalanceVisible: Boolean;

        TotalBalanceVisible: Boolean;

        StyleTxt: Text;

        ApprovalEntriesExistSentByCurrentUser: Boolean;

        OpenApprovalEntriesExistForCurrUser: Boolean;

        OpenApprovalEntriesOnJnlBatchExist: Boolean;

        OpenApprovalEntriesOnJnlLineExist: Boolean;

        OpenApprovalEntriesOnBatchOrCurrJnlLineExist: Boolean;

        OpenApprovalEntriesOnBatchOrAnyJnlLineExist: Boolean;

        EnabledGenJnlLineWorkflowsExist: Boolean;

        EnabledGenJnlBatchWorkflowsExist: Boolean;

        ShowWorkflowStatusOnBatch: Boolean;

        ShowWorkflowStatusOnLine: Boolean;

        CanCancelApprovalForJnlBatch: Boolean;

        CanCancelApprovalForJnlLine: Boolean;

        ImportPayrollTransactionsAvailable: Boolean;

        CanRequestFlowApprovalForBatch: Boolean;

        CanRequestFlowApprovalForBatchAndAllLines: Boolean;

        CanRequestFlowApprovalForBatchAndCurrentLine: Boolean;

        CanCancelFlowApprovalForBatch: Boolean;

        CanCancelFlowApprovalForLine: Boolean;

        BackgroundErrorCheck: Boolean;

        ShowAllLinesEnabled: Boolean;

        CurrentPostingDate: Date;

    protected var

        ApplyEntriesActionEnabled: Boolean;

        IsSimplePage: Boolean;

    local procedure EnableApplyEntriesAction()

    begin

        ApplyEntriesActionEnabled :=

          (Rec.”Account Type” in [Rec.”Account Type”::Customer, Rec.”Account Type”::Vendor, Rec.”Account Type”::Employee]) or

          (Rec.”Bal. Account Type” in [Rec.”Bal. Account Type”::Customer, Rec.”Bal. Account Type”::Vendor, Rec.”Bal. Account Type”::Employee]);

        OnAfterEnableApplyEntriesAction(Rec, ApplyEntriesActionEnabled);

    end;

    local procedure CurrentJnlBatchNameOnAfterVali()

    begin

        CurrPage.SaveRecord();

        GenJnlManagement.SetName(CurrentJnlBatchName, Rec);

        SetControlAppearanceFromBatch();

        CurrPage.Update(false);

    end;

    procedure SetUserInteractions()

    begin

        StyleTxt := Rec.GetStyle();

    end;

    local procedure GetCurrentlySelectedLines(var GenJournalLine: Record “Gen. Journal Line”): Boolean

    begin

        CurrPage.SetSelectionFilter(GenJournalLine);

        exit(GenJournalLine.FindSet());

    end;

    local procedure GetPostingDate(): Date

    begin

        if IsSimplePage then

            exit(CurrentPostingDate);

        exit(Workdate());

    end;

    internal procedure SetApprovalState(RecordId: RecordId; OpenApprovalEntriesOnJournalBatchExist: Boolean; LocalCanRequestFlowApprovalForBatch: Boolean; var LocalCanCancelFlowApprovalForLine: Boolean; var OpenApprovalEntriesExistForCurrentUser: Boolean; var OpenApprovalEntriesOnJournalLineExist: Boolean; var OpenApprovalEntriesOnBatchOrCurrentJournalLineExist: Boolean; var CanCancelApprovalForJournalLine: Boolean; var LocalCanRequestFlowApprovalForBatchAndCurrentLine: Boolean)

    var

        ApprovalsMgmt: Codeunit “Approvals Mgmt.”;

        WorkflowWebhookManagement: Codeunit “Workflow Webhook Management”;

        CanRequestFlowApprovalForLine: Boolean;

    begin

        OpenApprovalEntriesExistForCurrentUser := OpenApprovalEntriesExistForCurrentUser or ApprovalsMgmt.HasOpenApprovalEntriesForCurrentUser(RecordId);

        OpenApprovalEntriesOnJournalLineExist := ApprovalsMgmt.HasOpenApprovalEntries(RecordId);

        OpenApprovalEntriesOnBatchOrCurrentJournalLineExist := OpenApprovalEntriesOnJournalBatchExist or OpenApprovalEntriesOnJournalLineExist;

        CanCancelApprovalForJournalLine := ApprovalsMgmt.CanCancelApprovalForRecord(RecordId);

        WorkflowWebhookManagement.GetCanRequestAndCanCancel(RecordId, CanRequestFlowApprovalForLine, LocalCanCancelFlowApprovalForLine);

        LocalCanRequestFlowApprovalForBatchAndCurrentLine := LocalCanRequestFlowApprovalForBatch and CanRequestFlowApprovalForLine;

    end;

    internal procedure SetApprovalStateForBatch(GenJournalBatch: Record “Gen. Journal Batch”; GenJournalLine: Record “Gen. Journal Line”; var OpenApprovalEntriesExistForCurrentUser: Boolean; var OpenApprovalEntriesOnJournalBatchExist: Boolean; var OpenApprovalEntriesOnBatchOrAnyJournalLineExist: Boolean; var CanCancelApprovalForJournalBatch: Boolean; var LocalCanRequestFlowApprovalForBatch: Boolean; var LocalCanCancelFlowApprovalForBatch: Boolean; var LocalCanRequestFlowApprovalForBatchAndAllLines: Boolean; var LocalApprovalEntriesExistSentByCurrentUser: Boolean; var EnabledGeneralJournalBatchWorkflowsExist: Boolean; var EnabledGeneralJournalLineWorkflowsExist: Boolean)

    var

        ApprovalsMgmt: Codeunit “Approvals Mgmt.”;

        WorkflowWebhookManagement: Codeunit “Workflow Webhook Management”;

        WorkflowEventHandling: Codeunit “Workflow Event Handling”;

        WorkflowManagement: Codeunit “Workflow Management”;

        CanRequestFlowApprovalForAllLines: Boolean;

    begin

        OpenApprovalEntriesExistForCurrentUser := OpenApprovalEntriesExistForCurrentUser or ApprovalsMgmt.HasOpenApprovalEntriesForCurrentUser(GenJournalBatch.RecordId);

        OpenApprovalEntriesOnJournalBatchExist := ApprovalsMgmt.HasOpenApprovalEntries(GenJournalBatch.RecordId);

        OpenApprovalEntriesOnBatchOrAnyJournalLineExist := OpenApprovalEntriesOnJournalBatchExist or ApprovalsMgmt.HasAnyOpenJournalLineApprovalEntries(GenJournalLine.”Journal Template Name”, GenJournalLine.”Journal Batch Name”);

        CanCancelApprovalForJournalBatch := ApprovalsMgmt.CanCancelApprovalForRecord(GenJournalBatch.RecordId);

        WorkflowWebhookManagement.GetCanRequestAndCanCancelJournalBatch(GenJournalBatch, LocalCanRequestFlowApprovalForBatch, LocalCanCancelFlowApprovalForBatch, CanRequestFlowApprovalForAllLines);

        LocalCanRequestFlowApprovalForBatchAndAllLines := LocalCanRequestFlowApprovalForBatch and CanRequestFlowApprovalForAllLines;

        LocalApprovalEntriesExistSentByCurrentUser := ApprovalsMgmt.HasApprovalEntriesSentByCurrentUser(GenJournalBatch.RecordId) or ApprovalsMgmt.HasApprovalEntriesSentByCurrentUser(GenJournalLine.RecordId);

        EnabledGeneralJournalLineWorkflowsExist := WorkflowManagement.EnabledWorkflowExist(DATABASE::”Gen. Journal Line”, WorkflowEventHandling.RunWorkflowOnSendGeneralJournalLineForApprovalCode());

        EnabledGeneralJournalBatchWorkflowsExist := WorkflowManagement.EnabledWorkflowExist(DATABASE::”Gen. Journal Batch”, WorkflowEventHandling.RunWorkflowOnSendGeneralJournalBatchForApprovalCode());

    end;

    local procedure GetJournalTemplateNameFromFilter(): Text[10]

    begin

        if Rec.GetFilter(“Journal Template Name”) = ” then

            exit;

        exit(Rec.GetRangeMax(“Journal Template Name”));

    end;

    local procedure SetControlAppearance()

    begin

        SetApprovalState(Rec.RecordId, OpenApprovalEntriesOnJnlBatchExist, CanRequestFlowApprovalForBatch, CanCancelFlowApprovalForLine, OpenApprovalEntriesExistForCurrUser, OpenApprovalEntriesOnJnlLineExist, OpenApprovalEntriesOnBatchOrCurrJnlLineExist, CanCancelApprovalForJnlLine, CanRequestFlowApprovalForBatchAndCurrentLine);

    end;

    local procedure OpenJournalFromBatch() Result: Boolean

    var

        IsHandled: Boolean;

    begin

        IsHandled := false;

        OnBeforeOpenJournalFromBatch(Rec, Result, IsHandled);

        if IsHandled then

            exit(Result);

        if Rec.IsOpenedFromBatch() then begin

            CurrentJnlBatchName := Rec.”Journal Batch Name”;

            GenJnlManagement.OpenJnl(CurrentJnlBatchName, Rec);

            SetControlAppearanceFromBatch();

            exit(true);

        end;

    end;

    local procedure SetControlAppearanceFromBatch()

    var

        GenJournalBatch: Record “Gen. Journal Batch”;

    begin

        if not GenJournalBatch.Get(Rec.GetRangeMax(“Journal Template Name”), CurrentJnlBatchName) then

            exit;

        //ShowWorkflowStatusOnBatch := CurrPage.WorkflowStatusBatch.PAGE.SetFilterOnWorkflowRecord(GenJournalBatch.RecordId);

        SetApprovalStateForBatch(GenJournalBatch, Rec, OpenApprovalEntriesExistForCurrUser, OpenApprovalEntriesOnJnlBatchExist, OpenApprovalEntriesOnBatchOrAnyJnlLineExist, CanCancelApprovalForJnlBatch, CanRequestFlowApprovalForBatch, CanCancelFlowApprovalForBatch, CanRequestFlowApprovalForBatchAndAllLines, ApprovalEntriesExistSentByCurrentUser, EnabledGenJnlBatchWorkflowsExist, EnabledGenJnlLineWorkflowsExist);

        BackgroundErrorCheck := BackgroundErrorHandlingMgt.BackgroundValidationFeatureEnabled();

        ShowAllLinesEnabled := true;

        Rec.SwitchLinesWithErrorsFilter(ShowAllLinesEnabled);

        JournalErrorsMgt.SetFullBatchCheck(true);

    end;

    [IntegrationEvent(false, false)]

    local procedure OnAfterValidateCurrentJnlBatchName(CurrentJnlBatchName: Code[10])

    begin

    end;

    [IntegrationEvent(true, false)]

    local procedure OnBeforeOpenJournalFromBatch(var GenJournalLine: Record “Gen. Journal Line”; var Result: Boolean; var IsHandled: Boolean)

    begin

    end;

    [IntegrationEvent(true, false)]

    local procedure OnBeforeSelectTemplate(var GenJournalLine: Record “Gen. Journal Line”; var GenJnlManagement: Codeunit GenJnlManagement; var IsHandled: Boolean)

    begin

    end;

    [IntegrationEvent(false, false)]

    local procedure OnBeforeSetDataForSimpleModeOnPost(var GenJournalLine: Record “Gen. Journal Line”; IsSimplePage: Boolean; var IsHandled: Boolean)

    begin

    end;

    [IntegrationEvent(false, false)]

    local procedure OnLookupCurrentJnlBatchNameOnAfterSetDataForSimpleModeOnBatchChange(CurrentJnlBatchName: Code[10])

    begin

    end;

    [IntegrationEvent(false, false)]

    local procedure OnOpenPageOnAfterAssignCurrentJnlBatchName(var CurrentJnlBatchName: Code[10])

    begin

    end;

    [IntegrationEvent(true, false)]

    local procedure OnOpenPageOnBeforeGetLastViewedJournalBatchName(var CurrentJnlBatchName: Code[10]; var GenJnlManagement: Codeunit GenJnlManagement)

    begin

    end;

    [IntegrationEvent(false, false)]

    local procedure OnAfterEnableApplyEntriesAction(GenJournalLine: Record “Gen. Journal Line”; var ApplyEntriesActionEnabled: Boolean)

    begin

    end;

    [IntegrationEvent(false, false)]

    local procedure OnAccountNoValidateOnAfterSetUserInteractions(var Balance: Decimal; var TotalBalance: Decimal; var ShowBalance: Boolean; var ShowTotalBalance: Boolean; var BalanceVisible: Boolean; var TotalBalanceVisible: Boolean; var NumberOfRecords: Integer)

    begin

    end;

}

Configuring the Workflow in Business Central

  1. Go to Business Central and search for Workflows.

2. Click on New Workflow from Template

3. Select General Journal Batch Approval Workflow

4. Go to first option in workflow steps and select (View filter details).

5. Choose Journal Template Name: RecurringTemplate Type: General, and ensure Recurring is set to Yes

6. Enable the specified conditions. 

Sending Approval Requests

Now that the workflow is configured, follow these steps:

  1. Navigate to Recurring General Journals.

2. Go to Actions > Request Approval > Send Approval Request

3. The status will change to Pending Approval

Note: Ensure you have at least two users set up in Business Central for the approval process. Go to User Setup and assign the approver ID as required. 

Approval Process

Log in with a different user account in Business Central and go to Request to Approve. Here, you can view the entries awaiting approval.

To encapsulate, implementing an extension for the Recurring General Journal page in Microsoft Dynamics 365 Business Central enhances financial processes by automating approval requests for journal batches and lines. This not only improves compliance and accuracy but also streamlines workflows, allowing users to focus on strategic tasks.

By leveraging Business Central’s capabilities, businesses can foster accountability and transparency, ultimately transforming their financial operations for greater efficiency.

We hope you found this blog useful, and if you would like to discuss anything, you can reach out to us at transform@cloudfonts.com.


Share Story :

SEARCH BLOGS :

FOLLOW CLOUDFRONTS BLOG :


Secured By miniOrange