Report 94 Close Income Statement
- App
- Base Application
- Namespace
- Microsoft.Finance.GeneralLedger.Setup
- Versions
- 17-28
Versions171819202122232425262728
Source29
Source in 29
src/Layers/W1/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al955 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.GeneralLedger.Setup;
using Microsoft.Finance.Analysis;
using Microsoft.Finance.Dimension;
using Microsoft.Finance.GeneralLedger.Account;
using Microsoft.Finance.GeneralLedger.Journal;
using Microsoft.Finance.GeneralLedger.Ledger;
using Microsoft.Finance.GeneralLedger.Posting;
using Microsoft.Foundation.AuditCodes;
using Microsoft.Foundation.NoSeries;
using Microsoft.Foundation.Period;
using Microsoft.Inventory.Setup;
using Microsoft.Utilities;
using System.Globalization;
/// <summary>
/// Year-end report that closes income statement accounts by transferring balances to retained earnings.
/// Processes G/L entries, creates closing entries with configurable dimensions, and maintains audit trail.
/// </summary>
/// <remarks>
/// Key features: Closes by business unit and dimensions, validates posting rules, supports test mode.
/// Integration events available for customizing closing entry creation and dimension handling.
/// Critical year-end process affecting all financial reporting and compliance requirements.
/// </remarks>
report 94 "Close Income Statement"
{
AdditionalSearchTerms = 'year closing statement,close accounting period statement,close fiscal year statement';
ApplicationArea = Basic, Suite;
Caption = 'Close Income Statement';
ProcessingOnly = true;
UsageCategory = Tasks;
dataset
{
dataitem("G/L Account"; "G/L Account")
{
DataItemTableView = sorting("No.") where("Account Type" = const(Posting), "Income/Balance" = const("Income Statement"));
dataitem("G/L Entry"; "G/L Entry")
{
DataItemLink = "G/L Account No." = field("No.");
DataItemTableView = sorting("G/L Account No.", "Posting Date");
trigger OnAfterGetRecord()
var
TempDimBuf: Record "Dimension Buffer" temporary;
TempDimBuf2: Record "Dimension Buffer" temporary;
DimensionBufferID: Integer;
RowOffset: Integer;
begin
EntryCount := EntryCount + 1;
if CurrentDateTime - LastWindowUpdateDateTime > 1000 then begin
LastWindowUpdateDateTime := CurrentDateTime;
Window.Update(3, Round(EntryCount / MaxEntry * 10000, 1));
end;
if GroupSum() then begin
CalcSumsInFilter("G/L Entry", RowOffset);
GetGLEntryDimensions("Entry No.", TempDimBuf, "Dimension Set ID");
end;
if (Amount <> 0) or ("Additional-Currency Amount" <> 0) then begin
if not GroupSum() then begin
TotalAmount += Amount;
if (GLSetup."Additional Reporting Currency" <> '') or ("Additional-Currency Amount" <> 0) then
TotalAmountAddCurr += "Additional-Currency Amount";
GetGLEntryDimensions("Entry No.", TempDimBuf, "Dimension Set ID");
end;
if TempSelectedDim.Find('-') then
repeat
if TempDimBuf.Get(DATABASE::"G/L Entry", "Entry No.", TempSelectedDim."Dimension Code")
then begin
TempDimBuf2."Table ID" := TempDimBuf."Table ID";
TempDimBuf2."Dimension Code" := TempDimBuf."Dimension Code";
TempDimBuf2."Dimension Value Code" := TempDimBuf."Dimension Value Code";
TempDimBuf2.Insert();
end;
until TempSelectedDim.Next() = 0;
DimensionBufferID := DimBufMgt.GetDimensionId(TempDimBuf2);
TempEntryNoAmountBuffer.Reset();
if ClosePerBusUnit and FieldActive("Business Unit Code") then
TempEntryNoAmountBuffer."Business Unit Code" := "Business Unit Code"
else
TempEntryNoAmountBuffer."Business Unit Code" := '';
TempEntryNoAmountBuffer."Source Currency Code" := "Source Currency Code";
TempEntryNoAmountBuffer."Entry No." := GetEntryNo(DimensionBufferID, TempEntryNoAmountBuffer."Business Unit Code", "Source Currency Code");
if TempEntryNoAmountBuffer.Find() then begin
TempEntryNoAmountBuffer.Amount := TempEntryNoAmountBuffer.Amount + Amount;
TempEntryNoAmountBuffer.Amount2 := TempEntryNoAmountBuffer.Amount2 + "Additional-Currency Amount";
if "Source Currency Code" <> '' then begin
TempEntryNoAmountBuffer."Source Currency Amount" := TempEntryNoAmountBuffer."Source Currency Amount" + "Source Currency Amount";
TempEntryNoAmountBuffer."Source Currency VAT Amount" := TempEntryNoAmountBuffer."Source Currency VAT Amount" + "Source Currency VAT Amount";
end;
TempEntryNoAmountBuffer.Modify();
end else begin
TempEntryNoAmountBuffer.Amount := Amount;
TempEntryNoAmountBuffer.Amount2 := "Additional-Currency Amount";
TempEntryNoAmountBuffer."Source Currency Code" := "Source Currency Code";
TempEntryNoAmountBuffer."Source Currency Amount" := "Source Currency Amount";
TempEntryNoAmountBuffer."Source Currency VAT Amount" := "Source Currency VAT Amount";
TempEntryNoAmountBuffer.Insert();
end;
OnGLEntryOnAfterGetRecordOnAfterEntryNoAmountBuf(TempEntryNoAmountBuffer, "G/L Entry");
end;
if GroupSum() then
Next(RowOffset);
end;
trigger OnPostDataItem()
var
TempDimBuf2: Record "Dimension Buffer" temporary;
GlobalDimVal1: Code[20];
GlobalDimVal2: Code[20];
NewDimensionID: Integer;
begin
TempEntryNoAmountBuffer.Reset();
MaxEntry := TempEntryNoAmountBuffer.Count();
EntryCount := 0;
Window.Update(2, Text012);
Window.Update(3, 0);
if TempEntryNoAmountBuffer.Find('-') then
repeat
EntryCount := EntryCount + 1;
if CurrentDateTime - LastWindowUpdateDateTime > 1000 then begin
LastWindowUpdateDateTime := CurrentDateTime;
Window.Update(3, Round(EntryCount / MaxEntry * 10000, 1));
end;
if (TempEntryNoAmountBuffer.Amount <> 0) or (TempEntryNoAmountBuffer.Amount2 <> 0) then begin
GenJnlLine."Line No." := GenJnlLine."Line No." + 10;
GenJnlLine."Account No." := "G/L Account No.";
GenJnlLine."Source Code" := SourceCodeSetup."Close Income Statement";
GenJnlLine."Reason Code" := GenJnlBatch."Reason Code";
GenJnlLine.Validate(Amount, -TempEntryNoAmountBuffer.Amount);
if not AddSourceCurrencyFields() then
GenJnlLine."Source Currency Amount" := -TempEntryNoAmountBuffer.Amount2;
GenJnlLine."Business Unit Code" := TempEntryNoAmountBuffer."Business Unit Code";
TempDimBuf2.DeleteAll();
DimBufMgt.RetrieveDimensions(
GetDimensionBufferID(TempEntryNoAmountBuffer."Business Unit Code", TempEntryNoAmountBuffer."Entry No."), TempDimBuf2);
NewDimensionID := DimMgt.CreateDimSetIDFromDimBuf(TempDimBuf2);
GenJnlLine."Dimension Set ID" := NewDimensionID;
DimMgt.UpdateGlobalDimFromDimSetID(NewDimensionID, GlobalDimVal1, GlobalDimVal2);
GenJnlLine."Shortcut Dimension 1 Code" := '';
if ClosePerGlobalDim1 then
GenJnlLine."Shortcut Dimension 1 Code" := GlobalDimVal1;
GenJnlLine."Shortcut Dimension 2 Code" := '';
if ClosePerGlobalDim2 then
GenJnlLine."Shortcut Dimension 2 Code" := GlobalDimVal2;
OnPostDataItemOnAfterGenJnlLineDimUpdated(GenJnlLine, ClosePerGlobalDim1, ClosePerGlobalDim2, TempEntryNoAmountBuffer);
if PostToRetainedEarningsAcc = PostToRetainedEarningsAcc::Details then begin
GenJnlLine."Bal. Account Type" := GenJnlLine."Bal. Account Type"::"G/L Account";
GenJnlLine."Bal. Account No." := RetainedEarningsGLAcc."No.";
GenJnlLine.UpdateLineBalance();
end;
HandleGenJnlLine();
OnGLEntryOnPostDataItemOnAfterHandleGenJnlLine(GenJnlLine, TempEntryNoAmountBuffer);
end;
until TempEntryNoAmountBuffer.Next() = 0;
TempEntryNoAmountBuffer.DeleteAll();
end;
trigger OnPreDataItem()
begin
Window.Update(2, Text013);
Window.Update(3, 0);
if ClosePerGlobalDimOnly or ClosePerBusUnit then
case true of
ClosePerBusUnit and (ClosePerGlobalDim1 or ClosePerGlobalDim2):
SetCurrentKey(
"G/L Account No.", "Business Unit Code",
"Global Dimension 1 Code", "Global Dimension 2 Code", "Posting Date");
ClosePerBusUnit and not (ClosePerGlobalDim1 or ClosePerGlobalDim2):
SetCurrentKey(
"G/L Account No.", "Business Unit Code", "Posting Date");
not ClosePerBusUnit and (ClosePerGlobalDim1 or ClosePerGlobalDim2):
SetCurrentKey(
"G/L Account No.", "Global Dimension 1 Code", "Global Dimension 2 Code", "Posting Date");
end;
SetRange("Posting Date", FiscalYearStartDate, FiscYearClosingDate);
MaxEntry := Count;
TempEntryNoAmountBuffer.DeleteAll();
EntryCount := 0;
ResetEntryNoGrouping();
LastWindowUpdateDateTime := CurrentDateTime;
end;
}
trigger OnAfterGetRecord()
begin
ThisAccountNo := ThisAccountNo + 1;
Window.Update(1, "No.");
Window.Update(4, Round(ThisAccountNo / NoOfAccounts * 10000, 1));
Window.Update(2, '');
Window.Update(3, 0);
end;
trigger OnPostDataItem()
begin
if ((TotalAmount <> 0) or ((TotalAmountAddCurr <> 0) and (GLSetup."Additional Reporting Currency" <> ''))) and
(PostToRetainedEarningsAcc = PostToRetainedEarningsAcc::Balance)
then begin
GenJnlLine."Business Unit Code" := '';
GenJnlLine."Shortcut Dimension 1 Code" := '';
GenJnlLine."Shortcut Dimension 2 Code" := '';
GenJnlLine."Dimension Set ID" := 0;
GenJnlLine."Line No." := GenJnlLine."Line No." + 10000;
GenJnlLine."Account No." := RetainedEarningsGLAcc."No.";
GenJnlLine."Source Code" := SourceCodeSetup."Close Income Statement";
GenJnlLine."Reason Code" := GenJnlBatch."Reason Code";
GenJnlLine."Currency Code" := '';
GenJnlLine."Additional-Currency Posting" :=
GenJnlLine."Additional-Currency Posting"::None;
GenJnlLine.Validate(Amount, TotalAmount);
GenJnlLine."Source Currency Amount" := TotalAmountAddCurr;
OnGLAccountOnOnPostDataItemOnAfterGenJnlLinePopulateFields(GenJnlLine, RetainedEarningsGLAcc);
HandleGenJnlLine();
Window.Update(1, GenJnlLine."Account No.");
end;
end;
trigger OnPreDataItem()
begin
NoOfAccounts := Count;
end;
}
}
requestpage
{
SaveValues = true;
layout
{
area(content)
{
group(Options)
{
Caption = 'Options';
field(FiscalYearEndingDate; EndDateReq)
{
ApplicationArea = Basic, Suite;
Caption = 'Fiscal Year Ending Date';
ToolTip = 'Specifies the last date in the closed fiscal year. This date is used to determine the closing date.';
trigger OnValidate()
begin
ValidateEndDate(true);
end;
}
field(GenJournalTemplate; GenJnlLine."Journal Template Name")
{
ApplicationArea = Basic, Suite;
Caption = 'Gen. Journal Template';
TableRelation = "Gen. Journal Template";
ToolTip = 'Specifies the general journal template that is used by the batch job.';
trigger OnValidate()
begin
GenJnlLine."Journal Batch Name" := '';
DocNo := '';
end;
}
field(GenJournalBatch; GenJnlLine."Journal Batch Name")
{
ApplicationArea = Basic, Suite;
Caption = 'Gen. Journal Batch';
Lookup = true;
ToolTip = 'Specifies the general journal batch that is used by the batch job.';
trigger OnLookup(var Text: Text): Boolean
begin
GenJnlLine.TestField("Journal Template Name");
GenJnlTemplate.Get(GenJnlLine."Journal Template Name");
GenJnlBatch.FilterGroup(2);
GenJnlBatch.SetRange("Journal Template Name", GenJnlLine."Journal Template Name");
GenJnlBatch.FilterGroup(0);
GenJnlBatch."Journal Template Name" := GenJnlLine."Journal Template Name";
GenJnlBatch.Name := GenJnlLine."Journal Batch Name";
if PAGE.RunModal(0, GenJnlBatch) = ACTION::LookupOK then begin
Text := GenJnlBatch.Name;
exit(true);
end;
end;
trigger OnValidate()
begin
if GenJnlLine."Journal Batch Name" <> '' then begin
GenJnlLine.TestField("Journal Template Name");
GenJnlBatch.Get(GenJnlLine."Journal Template Name", GenJnlLine."Journal Batch Name");
end;
ValidateJnl();
end;
}
field(DocumentNo; DocNo)
{
ApplicationArea = Basic, Suite;
Caption = 'Document No.';
ToolTip = 'Specifies the number of the document that is processed by the report or batch job.';
}
field(RetainedEarningsAcc; RetainedEarningsGLAcc."No.")
{
ApplicationArea = Basic, Suite;
Caption = 'Retained Earnings Acc.';
TableRelation = "G/L Account" where("Account Type" = const(Posting),
"Account Category" = filter(" " | Equity),
"Income/Balance" = const("Balance Sheet"));
ToolTip = 'Specifies the retained earnings account that the batch job posts to. This account should be the same as the account that is used by the Close Income Statement batch job.';
trigger OnValidate()
begin
if RetainedEarningsGLAcc."No." <> '' then begin
RetainedEarningsGLAcc.Find();
RetainedEarningsGLAcc.CheckGLAcc();
end;
end;
}
field(PostToRetainedEarningsAccount; PostToRetainedEarningsAcc)
{
ApplicationArea = Basic, Suite;
Caption = 'Post to Retained Earnings Acc.';
OptionCaption = 'Balance,Details';
ToolTip = 'Specifies if the resulting entries are posted with the Retained Earnings account as a balancing account on each line (Details) or if retained earnings are posted as an extra line with a summarized amount (Balance).';
}
field(PostingDescription; PostingDescription)
{
ApplicationArea = Basic, Suite;
Caption = 'Posting Description';
ToolTip = 'Specifies the description that accompanies the posting.';
}
group("Close by")
{
Caption = 'Close by';
field(ClosePerBusUnit; ClosePerBusUnit)
{
ApplicationArea = Basic, Suite;
Caption = 'Business Unit Code';
ToolTip = 'Specifies the code for the business unit, in a company group structure.';
}
field(Dimensions; ColumnDim)
{
ApplicationArea = Dimensions;
Caption = 'Dimensions';
Editable = false;
ToolTip = 'Specifies dimensions, such as area, project, or department, that you can assign to sales and purchase documents to distribute costs and analyze transaction history.';
trigger OnAssistEdit()
var
TempSelectedDim2: Record "Selected Dimension" temporary;
s: Text[1024];
begin
DimSelectionBuf.SetDimSelectionMultiple(3, REPORT::"Close Income Statement", ColumnDim);
SelectedDim.GetSelectedDim(UserId, 3, REPORT::"Close Income Statement", '', TempSelectedDim2);
s := CheckDimPostingRules(TempSelectedDim2);
if s <> '' then
Message(s);
end;
}
}
field(InventoryPeriodClosed; IsInvtPeriodClosed())
{
ApplicationArea = Basic, Suite;
Caption = 'Inventory Period Closed';
ToolTip = 'Specifies that the inventory period has been closed.';
}
}
}
}
actions
{
}
trigger OnOpenPage()
var
GLAccount: Record "G/L Account";
GLAccountCategory: Record "G/L Account Category";
begin
if PostingDescription = '' then
PostingDescription :=
CopyStr(ObjTransl.TranslateObject(ObjTransl."Object Type"::Report, REPORT::"Close Income Statement"), 1, 30);
EndDateReq := 0D;
AccountingPeriod.SetRange("New Fiscal Year", true);
AccountingPeriod.SetRange("Date Locked", true);
if AccountingPeriod.FindLast() then begin
EndDateReq := AccountingPeriod."Starting Date" - 1;
if not ValidateEndDate(false) then
EndDateReq := 0D;
end else
if EndDateReq = 0D then
Error(NoFiscalYearsErr);
ValidateJnl();
ColumnDim := DimSelectionBuf.GetDimSelectionText(3, REPORT::"Close Income Statement", '');
if RetainedEarningsGLAcc."No." = '' then begin
GLAccountCategory.SetRange("Account Category", GLAccountCategory."Account Category"::Equity);
GLAccountCategory.SetRange(
"Additional Report Definition", GLAccountCategory."Additional Report Definition"::"Retained Earnings");
if GLAccountCategory.FindFirst() then begin
GLAccount.SetRange("Account Subcategory Entry No.", GLAccountCategory."Entry No.");
if GLAccount.FindFirst() then
RetainedEarningsGLAcc."No." := GLAccount."No.";
end;
end;
end;
}
labels
{
}
trigger OnPostReport()
var
UpdateAnalysisView: Codeunit "Update Analysis View";
begin
Window.Close();
Commit();
if GLSetup."Additional Reporting Currency" <> '' then begin
Message(Text016);
UpdateAnalysisView.UpdateAll(0, true);
end else
Message(Text017);
end;
trigger OnPreReport()
var
s: Text[1024];
IsHandled: Boolean;
begin
if EndDateReq = 0D then
Error(Text000);
ValidateEndDate(true);
if DocNo = '' then
Error(Text001);
SelectedDim.GetSelectedDim(UserId, 3, REPORT::"Close Income Statement", '', TempSelectedDim);
IsHandled := false;
OnPreReportOnBeforeCheckDimPostingRules(IsHandled, TempSelectedDim, GenJnlLine);
if not IsHandled then begin
s := CheckDimPostingRules(TempSelectedDim);
if s <> '' then
if not Confirm(s + Confirm04Tok, false) then
Error('');
end;
GenJnlBatch.Get(GenJnlLine."Journal Template Name", GenJnlLine."Journal Batch Name");
SourceCodeSetup.Get();
GLSetup.Get();
if GLSetup."Additional Reporting Currency" <> '' then begin
if RetainedEarningsGLAcc."No." = '' then
Error(Text002);
if not Confirm(
Confirm01Tok +
Confirm02Tok +
Confirm03Tok +
Confirm04Tok, false)
then
Error('');
PostToRetainedEarningsAcc := PostToRetainedEarningsAcc::Details;
end;
Window.Open(Text008 + Text009 + Text019 + Text010 + Text011);
ClosePerGlobalDim1 := false;
ClosePerGlobalDim2 := false;
ClosePerGlobalDimOnly := true;
if TempSelectedDim.Find('-') then
repeat
if TempSelectedDim."Dimension Code" = GLSetup."Global Dimension 1 Code" then
ClosePerGlobalDim1 := true;
if TempSelectedDim."Dimension Code" = GLSetup."Global Dimension 2 Code" then
ClosePerGlobalDim2 := true;
if (TempSelectedDim."Dimension Code" <> GLSetup."Global Dimension 1 Code") and
(TempSelectedDim."Dimension Code" <> GLSetup."Global Dimension 2 Code")
then
ClosePerGlobalDimOnly := false;
until TempSelectedDim.Next() = 0;
GenJnlLine.SetRange("Journal Template Name", GenJnlLine."Journal Template Name");
GenJnlLine.SetRange("Journal Batch Name", GenJnlLine."Journal Batch Name");
if not GenJnlLine.FindLast() then;
GenJnlLine.Init();
GenJnlLine."Posting Date" := FiscYearClosingDate;
GenJnlLine."Document No." := DocNo;
GenJnlLine.Description := PostingDescription;
GenJnlLine."Posting No. Series" := GenJnlBatch."Posting No. Series";
Clear(GenJnlPostLine);
end;
var
AccountingPeriod: Record "Accounting Period";
SourceCodeSetup: Record "Source Code Setup";
GenJnlTemplate: Record "Gen. Journal Template";
GenJnlBatch: Record "Gen. Journal Batch";
GLSetup: Record "General Ledger Setup";
DimSelectionBuf: Record "Dimension Selection Buffer";
ObjTransl: Record "Object Translation";
SelectedDim: Record "Selected Dimension";
TempSelectedDim: Record "Selected Dimension" temporary;
TempEntryNoAmountBuffer: Record "Entry No. Amount Buffer" temporary;
GenJnlPostLine: Codeunit "Gen. Jnl.-Post Line";
DimMgt: Codeunit DimensionManagement;
DimBufMgt: Codeunit "Dimension Buffer Management";
Window: Dialog;
ClosePerGlobalDim1: Boolean;
ClosePerGlobalDim2: Boolean;
ClosePerGlobalDimOnly: Boolean;
TotalAmount: Decimal;
TotalAmountAddCurr: Decimal;
ColumnDim: Text[250];
NoOfAccounts: Integer;
ThisAccountNo: Integer;
EntryNo: Integer;
GroupEntryNos: Dictionary of [Text, Integer];
EntryNoDimensionIds: Dictionary of [Text, Integer];
#pragma warning disable AA0074
Text000: Label 'Enter the ending date for the fiscal year.';
Text001: Label 'Enter a Document No.';
Text002: Label 'Enter Retained Earnings Account No.';
#pragma warning restore AA0074
Confirm01Tok: Label 'By using an additional reporting currency, this batch job will post closing entries directly to the general ledger. ';
Confirm02Tok: Label 'These closing entries will not be transferred to a general journal before the program posts them to the general ledger.\ ';
Confirm03Tok: Label 'Post to Retained Earnings Acc. will be set to Details.\\';
Confirm04Tok: Label '\Do you want to continue?';
#pragma warning disable AA0074
Text008: Label 'Creating general journal lines...\\';
#pragma warning disable AA0470
Text009: Label 'Account No. #1##################\';
Text010: Label 'Now performing #2##################\';
#pragma warning restore AA0470
Text011: Label ' @3@@@@@@@@@@@@@@@@@@\';
Text019: Label ' @4@@@@@@@@@@@@@@@@@@\';
Text012: Label 'Creating Gen. Journal lines';
Text013: Label 'Calculating Amounts';
Text014: Label 'The fiscal year must be closed before the income statement can be closed.';
Text015: Label 'The fiscal year does not exist.';
Text017: Label 'The journal lines have successfully been created.';
Text016: Label 'The closing entries have successfully been posted.';
Text020: Label 'The following G/L Accounts have mandatory dimension codes that have not been selected:';
Text021: Label '\\In order to post to these accounts you must also select these dimensions:';
#pragma warning restore AA0074
MaxEntry: Integer;
EntryCount: Integer;
LastWindowUpdateDateTime: DateTime;
NoFiscalYearsErr: Label 'No closed fiscal year exists.';
protected var
GenJnlLine: Record "Gen. Journal Line";
RetainedEarningsGLAcc: Record "G/L Account";
FiscalYearStartDate: Date;
FiscYearClosingDate: Date;
EndDateReq: Date;
DocNo: Code[20];
PostingDescription: Text[100];
ClosePerBusUnit: Boolean;
PostToRetainedEarningsAcc: Option Balance,Details;
/// <summary>
/// Validates the end date for the fiscal year closing process.
/// </summary>
/// <param name="RealMode">True for real validation, false for testing scenarios</param>
/// <returns>True if validation passes, false otherwise</returns>
local procedure ValidateEndDate(RealMode: Boolean) Result: Boolean
var
OK: Boolean;
IsHandled: Boolean;
begin
IsHandled := false;
OnBeforeValidateEndDate(EndDateReq, FiscalYearStartDate, FiscYearClosingDate, OK, Result, RealMode, IsHandled);
if IsHandled then
exit(Result);
if EndDateReq = 0D then
exit;
OK := AccountingPeriod.Get(EndDateReq + 1);
if OK then
OK := AccountingPeriod."New Fiscal Year";
if OK then begin
if not AccountingPeriod."Date Locked" then begin
if not RealMode then
exit;
Error(Text014);
end;
FiscYearClosingDate := ClosingDate(EndDateReq);
AccountingPeriod.SetRange("New Fiscal Year", true);
OK := AccountingPeriod.Find('<');
FiscalYearStartDate := AccountingPeriod."Starting Date";
end;
if not OK then begin
if not RealMode then
exit;
Error(Text015);
end;
exit(true);
end;
/// <summary>
/// Validates the journal line and generates a document number if necessary.
/// </summary>
/// <returns>True if validation passes, false otherwise</returns>
local procedure ValidateJnl()
var
NoSeries: Codeunit "No. Series";
begin
DocNo := '';
if GenJnlBatch.Get(GenJnlLine."Journal Template Name", GenJnlLine."Journal Batch Name") then
if GenJnlBatch."No. Series" <> '' then
DocNo := NoSeries.PeekNextNo(GenJnlBatch."No. Series", EndDateReq);
end;
/// <summary>
/// Handles the generation and posting of the general journal line.
/// </summary>
/// <remarks>
/// This includes setting up additional currency fields and calling the posting routine.
/// </remarks>
local procedure HandleGenJnlLine()
var
NoSeries: Codeunit "No. Series";
begin
OnBeforeHandleGenJnlLine(GenJnlLine);
GenJnlLine."Additional-Currency Posting" :=
GenJnlLine."Additional-Currency Posting"::None;
if GLSetup."Additional Reporting Currency" <> '' then begin
if ZeroGenJnlAmount() then begin
GenJnlLine."Additional-Currency Posting" :=
GenJnlLine."Additional-Currency Posting"::"Additional-Currency Amount Only";
GenJnlLine.Validate(Amount, GenJnlLine."Source Currency Amount");
GenJnlLine."Source Currency Amount" := 0;
end;
if GenJnlLine.Amount <> 0 then begin
GenJnlPostLine.Run(GenJnlLine);
if DocNo = NoSeries.PeekNextNo(GenJnlBatch."No. Series", EndDateReq) then
NoSeries.GetNextNo(GenJnlBatch."No. Series", EndDateReq);
end;
end else
if not ZeroGenJnlAmount() then
GenJnlLine.Insert();
end;
/// <summary>
/// Calculates the sum of amounts in the G/L Entry record based on the current filter.
/// </summary>
/// <param name="GLEntrySource">Source G/L Entry record</param>
/// <param name="Offset">Row offset for the calculation</param>
local procedure CalcSumsInFilter(var GLEntrySource: Record "G/L Entry"; var Offset: Integer)
var
GLEntry: Record "G/L Entry";
begin
GLEntry.CopyFilters(GLEntrySource);
GLEntry.SetRange("Source Currency Code", GLEntrySource."Source Currency Code");
if ClosePerBusUnit then begin
GLEntry.SetRange("Business Unit Code", GLEntrySource."Business Unit Code");
GenJnlLine."Business Unit Code" := GLEntrySource."Business Unit Code";
end;
if ClosePerGlobalDim1 then begin
GLEntry.SetRange("Global Dimension 1 Code", GLEntrySource."Global Dimension 1 Code");
if ClosePerGlobalDim2 then
GLEntry.SetRange("Global Dimension 2 Code", GLEntrySource."Global Dimension 2 Code");
end;
GLEntry.CalcSums(Amount);
GLEntrySource.Amount := GLEntry.Amount;
TotalAmount += GLEntrySource.Amount;
if GLSetup."Additional Reporting Currency" <> '' then begin
GLEntry.CalcSums("Additional-Currency Amount");
GLEntrySource."Additional-Currency Amount" := GLEntry."Additional-Currency Amount";
TotalAmountAddCurr += GLEntrySource."Additional-Currency Amount";
end;
Offset := GLEntry.Count - 1;
end;
/// <summary>
/// Retrieves and inserts G/L Entry dimensions into the dimension buffer record.
/// </summary>
/// <param name="EntryNo">Entry number to retrieve dimensions for</param>
/// <param name="DimBuf">Dimension buffer record to populate</param>
/// <param name="DimensionSetID">Dimension set ID to filter by</param>
local procedure GetGLEntryDimensions(EntryNo: Integer; var DimBuf: Record "Dimension Buffer"; DimensionSetID: Integer)
var
DimSetEntry: Record "Dimension Set Entry";
begin
DimSetEntry.SetRange("Dimension Set ID", DimensionSetID);
if DimSetEntry.FindSet() then
repeat
DimBuf."Table ID" := DATABASE::"G/L Entry";
DimBuf."Entry No." := EntryNo;
DimBuf."Dimension Code" := DimSetEntry."Dimension Code";
DimBuf."Dimension Value Code" := DimSetEntry."Dimension Value Code";
DimBuf.Insert();
until DimSetEntry.Next() = 0;
end;
/// <summary>
/// Validates selected dimension posting rules and returns an error message when mandatory dimensions are missing.
/// </summary>
/// <param name="SelectedDim">Selected dimensions to validate</param>
/// <returns>Error text to present to the user; empty when validation passes</returns>
procedure CheckDimPostingRules(var SelectedDim: Record "Selected Dimension"): Text[1024]
var
DefaultDim: Record "Default Dimension";
ErrorText: Text[1024];
DimText: Text[1024];
PrevAcc: Code[20];
Handled: Boolean;
begin
OnBeforeCheckDimPostingRules(SelectedDim, ErrorText, Handled, GenJnlLine);
if Handled then
exit(ErrorText);
DefaultDim.SetRange("Table ID", DATABASE::"G/L Account");
DefaultDim.SetFilter(
"Value Posting", '%1|%2',
DefaultDim."Value Posting"::"Same Code", DefaultDim."Value Posting"::"Code Mandatory");
PrevAcc := '';
if DefaultDim.Find('-') then
repeat
SelectedDim.SetRange("Dimension Code", DefaultDim."Dimension Code");
if not SelectedDim.Find('-') then begin
if StrPos(DimText, DefaultDim."Dimension Code") < 1 then
DimText := DimText + ' ' + Format(DefaultDim."Dimension Code");
if PrevAcc <> DefaultDim."No." then begin
PrevAcc := DefaultDim."No.";
if ErrorText = '' then
ErrorText := Text020;
ErrorText := ErrorText + ' ' + Format(DefaultDim."No.");
end;
end;
SelectedDim.SetRange("Dimension Code");
until (DefaultDim.Next() = 0) or (StrLen(ErrorText) > MaxStrLen(ErrorText) - MaxStrLen(DefaultDim."No.") - StrLen(Text021) - 1);
if ErrorText <> '' then
ErrorText := CopyStr(ErrorText + Text021 + DimText, 1, MaxStrLen(ErrorText));
exit(ErrorText);
end;
local procedure IsInvtPeriodClosed(): Boolean
var
AccPeriod: Record "Accounting Period";
InvtPeriod: Record "Inventory Period";
begin
if EndDateReq = 0D then
exit;
AccPeriod.Get(EndDateReq + 1);
AccPeriod.Next(-1);
exit(InvtPeriod.IsInvtPeriodClosed(AccPeriod."Starting Date"));
end;
/// <summary>
/// Initializes the report for test execution with specified parameters.
/// </summary>
/// <param name="EndDate">Fiscal year end date for closing</param>
/// <param name="GenJournalLine">General journal line template for closing entries</param>
/// <param name="GLAccount">Retained earnings G/L account for closing transfers</param>
/// <param name="CloseByBU">Whether to close by business unit</param>
procedure InitializeRequestTest(EndDate: Date; GenJournalLine: Record "Gen. Journal Line"; GLAccount: Record "G/L Account"; CloseByBU: Boolean)
begin
EndDateReq := EndDate;
GenJnlLine := GenJournalLine;
ValidateJnl();
RetainedEarningsGLAcc := GLAccount;
ClosePerBusUnit := CloseByBU;
end;
local procedure ZeroGenJnlAmount(): Boolean
begin
exit((GenJnlLine.Amount = 0) and (GenJnlLine."Source Currency Amount" <> 0))
end;
local procedure GroupSum(): Boolean
begin
exit(ClosePerGlobalDimOnly and (ClosePerBusUnit or ClosePerGlobalDim1));
end;
local procedure AddSourceCurrencyFields(): Boolean
begin
// The source currency of the group is always carried over, also when the group nets to a zero source
// currency amount. Otherwise a consolidated closing line would lose the currency it was closed for.
GenJnlLine."Source Currency Code" := TempEntryNoAmountBuffer."Source Currency Code";
if TempEntryNoAmountBuffer."Source Currency Amount" = 0 then
exit(false);
GenJnlLine."Source Currency Amount" := -(TempEntryNoAmountBuffer."Source Currency Amount");
GenJnlLine."Source Curr. VAT Amount" := -(TempEntryNoAmountBuffer."Source Currency VAT Amount");
exit(true);
end;
local procedure GetEntryNo(DimensionBufferID: Integer; BusinessUnitCode: Code[20]; SourceCurrencyCode: Code[10]): Integer
var
GroupKey: Text;
AssignedEntryNo: Integer;
begin
// Closing entries are grouped per business unit, per selected dimension combination and per source
// currency. The dimension buffer ID alone cannot carry the source currency, so when one dimension
// combination is used by more than one source currency the extra groups get a synthetic negative
// ID. GetDimensionBufferID() translates such an ID back to the real dimension buffer ID.
GroupKey := MakeGroupKey(BusinessUnitCode, Format(DimensionBufferID), SourceCurrencyCode);
if GroupEntryNos.Get(GroupKey, AssignedEntryNo) then
exit(AssignedEntryNo);
AssignedEntryNo := DimensionBufferID;
if EntryNoDimensionIds.ContainsKey(MakeGroupKey(BusinessUnitCode, Format(AssignedEntryNo), '')) then begin
EntryNo := EntryNo - 1;
AssignedEntryNo := EntryNo;
end;
GroupEntryNos.Add(GroupKey, AssignedEntryNo);
EntryNoDimensionIds.Add(MakeGroupKey(BusinessUnitCode, Format(AssignedEntryNo), ''), DimensionBufferID);
exit(AssignedEntryNo);
end;
local procedure GetDimensionBufferID(BusinessUnitCode: Code[20]; BufferEntryNo: Integer): Integer
var
DimensionBufferID: Integer;
begin
if EntryNoDimensionIds.Get(MakeGroupKey(BusinessUnitCode, Format(BufferEntryNo), ''), DimensionBufferID) then
exit(DimensionBufferID);
exit(BufferEntryNo);
end;
local procedure MakeGroupKey(BusinessUnitCode: Code[20]; DimensionPart: Text; SourceCurrencyCode: Code[10]): Text
begin
// The parts are length-prefixed so that a separator character inside one of the code fields cannot make
// two different groups collapse onto the same key.
exit(Format(StrLen(BusinessUnitCode)) + '|' + BusinessUnitCode +
'|' + Format(StrLen(DimensionPart)) + '|' + DimensionPart +
'|' + SourceCurrencyCode);
end;
local procedure ResetEntryNoGrouping()
begin
Clear(GroupEntryNos);
Clear(EntryNoDimensionIds);
EntryNo := 0;
end;
/// <summary>
/// Integration event fired before checking dimension posting rules during income statement closing.
/// </summary>
/// <param name="SelectedDimension">Selected dimensions for validation</param>
/// <param name="ErrorText">Error message text to be returned if validation fails</param>
/// <param name="Handled">Set to true if custom validation is performed</param>
/// <param name="GenJnlLine">General journal line being processed</param>
[IntegrationEvent(false, false)]
local procedure OnBeforeCheckDimPostingRules(var SelectedDimension: Record "Selected Dimension"; var ErrorText: Text[1024]; var Handled: Boolean; GenJnlLine: Record "Gen. Journal Line")
begin
end;
/// <summary>
/// Integration event fired before handling general journal line during closing process.
/// </summary>
/// <param name="GenJournalLine">General journal line being processed</param>
[IntegrationEvent(false, false)]
local procedure OnBeforeHandleGenJnlLine(var GenJournalLine: Record "Gen. Journal Line")
begin
end;
/// <summary>
/// Integration event fired after populating general journal line fields for G/L account closing entries.
/// </summary>
/// <param name="GenJournalLine">General journal line being populated</param>
/// <param name="RetainedEarningsGLAcc">Retained earnings G/L account used for closing</param>
[IntegrationEvent(false, false)]
local procedure OnGLAccountOnOnPostDataItemOnAfterGenJnlLinePopulateFields(var GenJournalLine: Record "Gen. Journal Line"; RetainedEarningsGLAcc: Record "G/L Account")
begin
end;
/// <summary>
/// Integration event fired after updating dimensions on general journal line during posting.
/// </summary>
/// <param name="GenJnlLine">General journal line with updated dimensions</param>
/// <param name="ClosePerGlobalDim1">Whether closing per global dimension 1</param>
/// <param name="ClosePerGlobalDim2">Whether closing per global dimension 2</param>
/// <param name="TempEntryNoAmountBuffer">Temporary entry number amount buffer. The "Entry No." field is a logical grouping key
/// for business unit, dimension combination and source currency, and is not always a valid dimension buffer ID that can be
/// passed to Dimension Buffer Management.RetrieveDimensions.</param>
[IntegrationEvent(false, false)]
local procedure OnPostDataItemOnAfterGenJnlLineDimUpdated(var GenJnlLine: Record "Gen. Journal Line"; ClosePerGlobalDim1: Boolean; ClosePerGlobalDim2: Boolean; var TempEntryNoAmountBuffer: Record "Entry No. Amount Buffer" temporary)
begin
end;
/// <summary>
/// Integration event fired before checking dimension posting rules in pre-report processing.
/// </summary>
/// <param name="IsHandled">Set to true if custom handling is performed</param>
/// <param name="TempSelectedDim">Temporary selected dimensions table</param>
/// <param name="GenJnlLine">General journal line template</param>
[IntegrationEvent(false, false)]
local procedure OnPreReportOnBeforeCheckDimPostingRules(var IsHandled: Boolean; var TempSelectedDim: Record "Selected Dimension" temporary; GenJnlLine: Record "Gen. Journal Line")
begin
end;
/// <summary>
/// Integration event fired after processing G/L entry into amount buffer during closing.
/// </summary>
/// <param name="TempEntryNoAmountBuffer">Temporary entry number amount buffer. The "Entry No." field is a logical grouping key
/// for business unit, dimension combination and source currency, and is not always a valid dimension buffer ID that can be
/// passed to Dimension Buffer Management.RetrieveDimensions.</param>
/// <param name="GEntry">G/L entry being processed</param>
[IntegrationEvent(false, false)]
local procedure OnGLEntryOnAfterGetRecordOnAfterEntryNoAmountBuf(var TempEntryNoAmountBuffer: Record "Entry No. Amount Buffer" temporary; GEntry: Record "G/L Entry")
begin
end;
/// <summary>
/// Integration event fired after handling general journal line during G/L entry processing.
/// </summary>
/// <param name="GenJnlLine">General journal line being handled</param>
/// <param name="TempEntryNoAmountBuf">Temporary entry number amount buffer. The "Entry No." field is a logical grouping key
/// for business unit, dimension combination and source currency, and is not always a valid dimension buffer ID that can be
/// passed to Dimension Buffer Management.RetrieveDimensions.</param>
[IntegrationEvent(false, false)]
local procedure OnGLEntryOnPostDataItemOnAfterHandleGenJnlLine(var GenJnlLine: Record "Gen. Journal Line"; var TempEntryNoAmountBuf: Record "Entry No. Amount Buffer" temporary)
begin
end;
/// <summary>
/// Integration event fired before validating the end date for income statement closing.
/// </summary>
/// <param name="EndDateReq">Requested end date for closing</param>
/// <param name="FiscalYearStartDate">Fiscal year start date</param>
/// <param name="FiscYearClosingDate">Fiscal year closing date</param>
/// <param name="OK">Validation result</param>
/// <param name="Result">Overall validation result</param>
/// <param name="RealMode">Whether running in real mode vs test mode</param>
/// <param name="IsHandled">Set to true if custom validation is performed</param>
[IntegrationEvent(false, false)]
local procedure OnBeforeValidateEndDate(EndDateReq: Date; var FiscalYearStartDate: Date; var FiscYearClosingDate: Date; var OK: Boolean; var Result: Boolean; RealMode: Boolean; var IsHandled: Boolean);
begin
end;
}