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
- 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: Recurring, Template 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:
- 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.