diff --git a/src/Layers/BE/Tests/Report/ERMFinancialReports.Codeunit.al b/src/Layers/BE/Tests/Report/ERMFinancialReports.Codeunit.al index 6b9006d9895..974a50858cb 100644 --- a/src/Layers/BE/Tests/Report/ERMFinancialReports.Codeunit.al +++ b/src/Layers/BE/Tests/Report/ERMFinancialReports.Codeunit.al @@ -58,6 +58,8 @@ codeunit 134982 "ERM Financial Reports" PostingGroupDetailsLedEntryErr: Label 'Posting group should be populated from customer ledger entry'; MissingGLEntryErr: Label 'No %1 exists for account %2 and source currency %3.', Comment = '%1 = Table Caption, %2 = G/L Account No., %3 = Source Currency Code'; CloseIncomeAmountMismatchErr: Label 'Close Income Statement %1 mismatch for source currency %2. Expected %3, actual %4.', Comment = '%1 = Field Caption, %2 = Source Currency Code, %3 = Expected Amount, %4 = Actual Amount'; + CloseIncomeLineCountErr: Label 'Close Income Statement must create one consolidated line for account %1 when no dimensions are selected. Expected %2, actual %3.', Comment = '%1 = G/L Account No., %2 = Expected line count, %3 = Actual line count'; + CloseIncomeLineAmountErr: Label 'The consolidated Close Income Statement line amount must equal the negated sum of the posted G/L entry amounts.'; [Test] [HandlerFunctions('RHDetailTrialBalance')] @@ -1542,6 +1544,168 @@ codeunit 134982 "ERM Financial Reports" UpdateCurOnGeneralLedgerSetup(AdditionalReportingCurrency); end; + [Test] + [HandlerFunctions('MessageHandler,ConfirmHandler,CloseIncomeStatementRequestPageHandler')] + procedure CloseIncomeStatementConsolidatesEntriesPerGLAccountWithoutARC() + var + GLAccount: Record "G/L Account"; + BalGLAccount: Record "G/L Account"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalBatch: Record "Gen. Journal Batch"; + GenJournalTemplate: Record "Gen. Journal Template"; + ClosingGenJournalLine: Record "Gen. Journal Line"; + Date: Record Date; + GeneralLedgerSetup: Record "General Ledger Setup"; + DocNo: Code[20]; + PostingDate: Date; + Amount1: Decimal; + Amount2: Decimal; + Amount3: Decimal; + OldAdditionalReportingCurrency: Code[10]; + begin + // [SCENARIO 646076] Close Income Statement consolidates multiple G/L entries into one journal line per account when no dimensions are selected and ARC is not enabled. + Initialize(); + + // [GIVEN] No dimensions are selected for the Close Income Statement report + ClearSelectedDimensionsForCloseIncomeStatement(); + + // [GIVEN] Additional Reporting Currency is blank + GeneralLedgerSetup.Get(); + OldAdditionalReportingCurrency := GeneralLedgerSetup."Additional Reporting Currency"; + if OldAdditionalReportingCurrency <> '' then + UpdateCurOnGeneralLedgerSetup(''); + + // [GIVEN] Previous Fiscal Year Closed, New Fiscal Year Created and Closed + LibraryFiscalYear.CloseFiscalYear(); + ExecuteUIHandler(); + LibraryFiscalYear.CreateFiscalYear(); + LibraryFiscalYear.CloseFiscalYear(); + + // [GIVEN] Income Statement G/L Account "A" and a Balance Sheet G/L Account for balancing + LibraryCostAccounting.CreateIncomeStmtGLAccount(GLAccount); + LibraryERM.CreateGLAccount(BalGLAccount); + + // [GIVEN] Three posted G/L entries to account "A" with blank dimensions in the closed fiscal year + PostingDate := LibraryFiscalYear.GetLastPostingDate(true); + Amount1 := LibraryRandom.RandIntInRange(100, 500); + Amount2 := LibraryRandom.RandIntInRange(100, 500); + Amount3 := LibraryRandom.RandIntInRange(100, 500); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount1); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount2); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount3); + + // [GIVEN] Gen. Journal Template and Batch for close income statement output + LibraryERM.CreateGenJournalTemplate(GenJournalTemplate); + LibraryERM.CreateGenJournalBatch(GenJournalBatch, GenJournalTemplate.Name); + LibraryERM.ClearGenJournalLines(GenJournalBatch); + GenJournalLine.Init(); + GenJournalLine."Journal Template Name" := GenJournalBatch."Journal Template Name"; + GenJournalLine."Journal Batch Name" := GenJournalBatch.Name; + + // [WHEN] Run Close Income Statement with no dimensions selected + Date.SetRange("Period Type", Date."Period Type"::Month); + Date.SetRange("Period Start", PostingDate); + Date.FindFirst(); + DocNo := LibraryUtility.GenerateGUID(); + RunCloseIncomeStatement(GenJournalLine, NormalDate(Date."Period End"), DocNo); + + // [THEN] Exactly one closing Gen. Journal Line exists for account "A" + ClosingGenJournalLine.SetRange("Journal Template Name", GenJournalBatch."Journal Template Name"); + ClosingGenJournalLine.SetRange("Journal Batch Name", GenJournalBatch.Name); + ClosingGenJournalLine.SetRange("Document No.", DocNo); + ClosingGenJournalLine.SetRange("Account No.", GLAccount."No."); + Assert.AreEqual( + 1, ClosingGenJournalLine.Count, + StrSubstNo(CloseIncomeLineCountErr, GLAccount."No.", 1, ClosingGenJournalLine.Count)); + + // [THEN] The closing line amount equals the negated sum of posted amounts + ClosingGenJournalLine.FindFirst(); + Assert.AreEqual( + -(Amount1 + Amount2 + Amount3), ClosingGenJournalLine.Amount, CloseIncomeLineAmountErr); + + // Cleanup: restore Additional Reporting Currency + if OldAdditionalReportingCurrency <> '' then + UpdateCurOnGeneralLedgerSetup(OldAdditionalReportingCurrency); + end; + + [Test] + [HandlerFunctions('MessageHandler,ConfirmHandler,CloseIncomeStatementWithRetainedEarningsRequestPageHandler')] + procedure CloseIncomeStatementConsolidatesEntriesPerGLAccountWithARC() + var + GLAccount: Record "G/L Account"; + BalGLAccount: Record "G/L Account"; + RetainedEarningsGLAccount: Record "G/L Account"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalBatch: Record "Gen. Journal Batch"; + ClosingGLEntry: Record "G/L Entry"; + Date: Record Date; + DocNo: Code[20]; + PostingDate: Date; + Amount1: Decimal; + Amount2: Decimal; + Amount3: Decimal; + OldAdditionalReportingCurrency: Code[10]; + begin + // [SCENARIO 646077] Close Income Statement consolidates multiple G/L entries into one journal line per account when no dimensions are selected and ARC is enabled. + Initialize(); + + // [GIVEN] No dimensions are selected for the Close Income Statement report + ClearSelectedDimensionsForCloseIncomeStatement(); + + // [GIVEN] Additional Reporting Currency is enabled + OldAdditionalReportingCurrency := UpdateCurOnGeneralLedgerSetup(LibraryERM.CreateCurrencyWithRandomExchRates()); + + // [GIVEN] Previous Fiscal Year Closed, New Fiscal Year Created and Closed + LibraryFiscalYear.CloseFiscalYear(); + ExecuteUIHandler(); + LibraryFiscalYear.CreateFiscalYear(); + LibraryFiscalYear.CloseFiscalYear(); + + // [GIVEN] Income Statement G/L Account "A", a balancing account and a Retained Earnings account + LibraryCostAccounting.CreateIncomeStmtGLAccount(GLAccount); + LibraryERM.CreateGLAccount(BalGLAccount); + LibraryERM.CreateGLAccount(RetainedEarningsGLAccount); + + // [GIVEN] Three posted G/L entries to account "A" with blank dimensions and blank Business Unit Code + PostingDate := LibraryFiscalYear.GetLastPostingDate(true); + Amount1 := LibraryRandom.RandIntInRange(100, 500); + Amount2 := LibraryRandom.RandIntInRange(100, 500); + Amount3 := LibraryRandom.RandIntInRange(100, 500); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount1); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount2); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount3); + + // [GIVEN] Gen. Journal Batch with a No. Series, required because with an Additional Reporting Currency + // report 94 posts the closing entries directly instead of inserting journal lines. + LibraryERM.SelectGenJnlBatch(GenJournalBatch); + LibraryERM.ClearGenJournalLines(GenJournalBatch); + GenJournalLine.Init(); + GenJournalLine."Journal Template Name" := GenJournalBatch."Journal Template Name"; + GenJournalLine."Journal Batch Name" := GenJournalBatch.Name; + + // [WHEN] Run Close Income Statement with no dimensions selected + Date.SetRange("Period Type", Date."Period Type"::Month); + Date.SetRange("Period Start", PostingDate); + Date.FindFirst(); + DocNo := LibraryUtility.GenerateGUID(); + RunCloseIncomeStatementWithRetainedEarnings(GenJournalLine, NormalDate(Date."Period End"), DocNo, RetainedEarningsGLAccount."No."); + + // [THEN] Exactly one closing G/L Entry exists for account "A" + ClosingGLEntry.SetRange("G/L Account No.", GLAccount."No."); + ClosingGLEntry.SetRange("Document No.", DocNo); + Assert.AreEqual( + 1, ClosingGLEntry.Count, + StrSubstNo(CloseIncomeLineCountErr, GLAccount."No.", 1, ClosingGLEntry.Count)); + + // [THEN] The consolidated closing entry equals the negated sum of posted amounts + ClosingGLEntry.CalcSums(Amount); + Assert.AreEqual( + -(Amount1 + Amount2 + Amount3), ClosingGLEntry.Amount, CloseIncomeLineAmountErr); + + // Cleanup: restore General Ledger Setup + UpdateCurOnGeneralLedgerSetup(OldAdditionalReportingCurrency); + end; + [Test] [HandlerFunctions('ConfirmHandler,RHReconcileCustandVendAccs')] procedure ReconcileCustVendAccounts_AfterExchRateAdjustment() @@ -2471,6 +2635,19 @@ codeunit 134982 "ERM Financial Reports" GenJournalLine.FindSet(); end; + local procedure ClearSelectedDimensionsForCloseIncomeStatement() + var + SelectedDimension: Record "Selected Dimension"; + AllObj: Record AllObj; + begin + // Other tests in this codeunit leave Selected Dimension records behind for report 94. They would make + // the report close per dimension, which hides defects that only occur when no dimensions are selected. + SelectedDimension.SetRange("User ID", UserId()); + SelectedDimension.SetRange("Object Type", AllObj."Object Type"::Report); + SelectedDimension.SetRange("Object ID", Report::"Close Income Statement"); + SelectedDimension.DeleteAll(); + end; + local procedure RunCloseIncomeStatement(GenJournalLine: Record "Gen. Journal Line"; PostingDate: Date; DocumentNo: Code[20]) begin // Enqueue values for CloseIncomeStatementRequestPageHandler. @@ -2483,6 +2660,19 @@ codeunit 134982 "ERM Financial Reports" Report.Run(Report::"Close Income Statement"); end; + local procedure RunCloseIncomeStatementWithRetainedEarnings(GenJournalLine: Record "Gen. Journal Line"; PostingDate: Date; DocumentNo: Code[20]; RetainedEarningsAccNo: Code[20]) + begin + // Enqueue values for CloseIncomeStatementWithRetainedEarningsRequestPageHandler. + LibraryVariableStorage.Enqueue(PostingDate); + LibraryVariableStorage.Enqueue(GenJournalLine."Journal Template Name"); + LibraryVariableStorage.Enqueue(GenJournalLine."Journal Batch Name"); + LibraryVariableStorage.Enqueue(DocumentNo); + LibraryVariableStorage.Enqueue(RetainedEarningsAccNo); + + Commit(); // commit requires to run report. + Report.Run(Report::"Close Income Statement"); + end; + local procedure ExecuteUIHandler() begin // Generate Dummy message. Required for executing the test case successfully in ES. @@ -2823,6 +3013,19 @@ codeunit 134982 "ERM Financial Reports" CloseIncomeStatement.OK().Invoke(); end; + [RequestPageHandler] + procedure CloseIncomeStatementWithRetainedEarningsRequestPageHandler(var CloseIncomeStatement: TestRequestPage "Close Income Statement") + begin + // Same as CloseIncomeStatementRequestPageHandler, but supplies a Retained Earnings Account, which + // report 94 requires when an Additional Reporting Currency is set. Dimensions are left blank. + CloseIncomeStatement.FiscalYearEndingDate.SetValue(LibraryVariableStorage.DequeueDate()); // Fiscal Year Ending Date + CloseIncomeStatement.GenJournalTemplate.SetValue(LibraryVariableStorage.DequeueText()); // Gen. Journal Template + CloseIncomeStatement.GenJournalBatch.SetValue(LibraryVariableStorage.DequeueText()); // Gen. Journal Batch + CloseIncomeStatement.DocumentNo.SetValue(LibraryVariableStorage.DequeueText()); // Document No. + CloseIncomeStatement.RetainedEarningsAcc.SetValue(LibraryVariableStorage.DequeueText()); // Retained Earnings Acc. + CloseIncomeStatement.OK().Invoke(); + end; + [RequestPageHandler] procedure AuditTrailReportRequestPageHandler(var AuditTrail: TestRequestPage "Audit Trail") begin diff --git a/src/Layers/CZ/Tests/Report/ERMFinancialReports.Codeunit.al b/src/Layers/CZ/Tests/Report/ERMFinancialReports.Codeunit.al index 16f467f74f6..57e24b9c0f3 100644 --- a/src/Layers/CZ/Tests/Report/ERMFinancialReports.Codeunit.al +++ b/src/Layers/CZ/Tests/Report/ERMFinancialReports.Codeunit.al @@ -58,6 +58,8 @@ codeunit 134982 "ERM Financial Reports" PostingGroupDetailsLedEntryErr: Label 'Posting group should be populated from customer ledger entry'; MissingGLEntryErr: Label 'No %1 exists for account %2 and source currency %3.', Comment = '%1 = Table Caption, %2 = G/L Account No., %3 = Source Currency Code'; CloseIncomeAmountMismatchErr: Label 'Close Income Statement %1 mismatch for source currency %2. Expected %3, actual %4.', Comment = '%1 = Field Caption, %2 = Source Currency Code, %3 = Expected Amount, %4 = Actual Amount'; + CloseIncomeLineCountErr: Label 'Close Income Statement must create one consolidated line for account %1 when no dimensions are selected. Expected %2, actual %3.', Comment = '%1 = G/L Account No., %2 = Expected line count, %3 = Actual line count'; + CloseIncomeLineAmountErr: Label 'The consolidated Close Income Statement line amount must equal the negated sum of the posted G/L entry amounts.'; [Test] [HandlerFunctions('RHDetailTrialBalance')] @@ -1506,6 +1508,168 @@ codeunit 134982 "ERM Financial Reports" UpdateCurOnGeneralLedgerSetup(AdditionalReportingCurrency); end; + [Test] + [HandlerFunctions('MessageHandler,ConfirmHandler,CloseIncomeStatementRequestPageHandler')] + procedure CloseIncomeStatementConsolidatesEntriesPerGLAccountWithoutARC() + var + GLAccount: Record "G/L Account"; + BalGLAccount: Record "G/L Account"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalBatch: Record "Gen. Journal Batch"; + GenJournalTemplate: Record "Gen. Journal Template"; + ClosingGenJournalLine: Record "Gen. Journal Line"; + Date: Record Date; + GeneralLedgerSetup: Record "General Ledger Setup"; + DocNo: Code[20]; + PostingDate: Date; + Amount1: Decimal; + Amount2: Decimal; + Amount3: Decimal; + OldAdditionalReportingCurrency: Code[10]; + begin + // [SCENARIO 646076] Close Income Statement consolidates multiple G/L entries into one journal line per account when no dimensions are selected and ARC is not enabled. + Initialize(); + + // [GIVEN] No dimensions are selected for the Close Income Statement report + ClearSelectedDimensionsForCloseIncomeStatement(); + + // [GIVEN] Additional Reporting Currency is blank + GeneralLedgerSetup.Get(); + OldAdditionalReportingCurrency := GeneralLedgerSetup."Additional Reporting Currency"; + if OldAdditionalReportingCurrency <> '' then + UpdateCurOnGeneralLedgerSetup(''); + + // [GIVEN] Previous Fiscal Year Closed, New Fiscal Year Created and Closed + LibraryFiscalYear.CloseFiscalYear(); + ExecuteUIHandler(); + LibraryFiscalYear.CreateFiscalYear(); + LibraryFiscalYear.CloseFiscalYear(); + + // [GIVEN] Income Statement G/L Account "A" and a Balance Sheet G/L Account for balancing + LibraryCostAccounting.CreateIncomeStmtGLAccount(GLAccount); + LibraryERM.CreateGLAccount(BalGLAccount); + + // [GIVEN] Three posted G/L entries to account "A" with blank dimensions in the closed fiscal year + PostingDate := LibraryFiscalYear.GetLastPostingDate(true); + Amount1 := LibraryRandom.RandIntInRange(100, 500); + Amount2 := LibraryRandom.RandIntInRange(100, 500); + Amount3 := LibraryRandom.RandIntInRange(100, 500); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount1); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount2); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount3); + + // [GIVEN] Gen. Journal Template and Batch for close income statement output + LibraryERM.CreateGenJournalTemplate(GenJournalTemplate); + LibraryERM.CreateGenJournalBatch(GenJournalBatch, GenJournalTemplate.Name); + LibraryERM.ClearGenJournalLines(GenJournalBatch); + GenJournalLine.Init(); + GenJournalLine."Journal Template Name" := GenJournalBatch."Journal Template Name"; + GenJournalLine."Journal Batch Name" := GenJournalBatch.Name; + + // [WHEN] Run Close Income Statement with no dimensions selected + Date.SetRange("Period Type", Date."Period Type"::Month); + Date.SetRange("Period Start", PostingDate); + Date.FindFirst(); + DocNo := LibraryUtility.GenerateGUID(); + RunCloseIncomeStatement(GenJournalLine, NormalDate(Date."Period End"), DocNo); + + // [THEN] Exactly one closing Gen. Journal Line exists for account "A" + ClosingGenJournalLine.SetRange("Journal Template Name", GenJournalBatch."Journal Template Name"); + ClosingGenJournalLine.SetRange("Journal Batch Name", GenJournalBatch.Name); + ClosingGenJournalLine.SetRange("Document No.", DocNo); + ClosingGenJournalLine.SetRange("Account No.", GLAccount."No."); + Assert.AreEqual( + 1, ClosingGenJournalLine.Count, + StrSubstNo(CloseIncomeLineCountErr, GLAccount."No.", 1, ClosingGenJournalLine.Count)); + + // [THEN] The closing line amount equals the negated sum of posted amounts + ClosingGenJournalLine.FindFirst(); + Assert.AreEqual( + -(Amount1 + Amount2 + Amount3), ClosingGenJournalLine.Amount, CloseIncomeLineAmountErr); + + // Cleanup: restore Additional Reporting Currency + if OldAdditionalReportingCurrency <> '' then + UpdateCurOnGeneralLedgerSetup(OldAdditionalReportingCurrency); + end; + + [Test] + [HandlerFunctions('MessageHandler,ConfirmHandler,CloseIncomeStatementWithRetainedEarningsRequestPageHandler')] + procedure CloseIncomeStatementConsolidatesEntriesPerGLAccountWithARC() + var + GLAccount: Record "G/L Account"; + BalGLAccount: Record "G/L Account"; + RetainedEarningsGLAccount: Record "G/L Account"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalBatch: Record "Gen. Journal Batch"; + ClosingGLEntry: Record "G/L Entry"; + Date: Record Date; + DocNo: Code[20]; + PostingDate: Date; + Amount1: Decimal; + Amount2: Decimal; + Amount3: Decimal; + OldAdditionalReportingCurrency: Code[10]; + begin + // [SCENARIO 646077] Close Income Statement consolidates multiple G/L entries into one journal line per account when no dimensions are selected and ARC is enabled. + Initialize(); + + // [GIVEN] No dimensions are selected for the Close Income Statement report + ClearSelectedDimensionsForCloseIncomeStatement(); + + // [GIVEN] Additional Reporting Currency is enabled + OldAdditionalReportingCurrency := UpdateCurOnGeneralLedgerSetup(LibraryERM.CreateCurrencyWithRandomExchRates()); + + // [GIVEN] Previous Fiscal Year Closed, New Fiscal Year Created and Closed + LibraryFiscalYear.CloseFiscalYear(); + ExecuteUIHandler(); + LibraryFiscalYear.CreateFiscalYear(); + LibraryFiscalYear.CloseFiscalYear(); + + // [GIVEN] Income Statement G/L Account "A", a balancing account and a Retained Earnings account + LibraryCostAccounting.CreateIncomeStmtGLAccount(GLAccount); + LibraryERM.CreateGLAccount(BalGLAccount); + LibraryERM.CreateGLAccount(RetainedEarningsGLAccount); + + // [GIVEN] Three posted G/L entries to account "A" with blank dimensions and blank Business Unit Code + PostingDate := LibraryFiscalYear.GetLastPostingDate(true); + Amount1 := LibraryRandom.RandIntInRange(100, 500); + Amount2 := LibraryRandom.RandIntInRange(100, 500); + Amount3 := LibraryRandom.RandIntInRange(100, 500); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount1); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount2); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount3); + + // [GIVEN] Gen. Journal Batch with a No. Series, required because with an Additional Reporting Currency + // report 94 posts the closing entries directly instead of inserting journal lines. + LibraryERM.SelectGenJnlBatch(GenJournalBatch); + LibraryERM.ClearGenJournalLines(GenJournalBatch); + GenJournalLine.Init(); + GenJournalLine."Journal Template Name" := GenJournalBatch."Journal Template Name"; + GenJournalLine."Journal Batch Name" := GenJournalBatch.Name; + + // [WHEN] Run Close Income Statement with no dimensions selected + Date.SetRange("Period Type", Date."Period Type"::Month); + Date.SetRange("Period Start", PostingDate); + Date.FindFirst(); + DocNo := LibraryUtility.GenerateGUID(); + RunCloseIncomeStatementWithRetainedEarnings(GenJournalLine, NormalDate(Date."Period End"), DocNo, RetainedEarningsGLAccount."No."); + + // [THEN] Exactly one closing G/L Entry exists for account "A" + ClosingGLEntry.SetRange("G/L Account No.", GLAccount."No."); + ClosingGLEntry.SetRange("Document No.", DocNo); + Assert.AreEqual( + 1, ClosingGLEntry.Count, + StrSubstNo(CloseIncomeLineCountErr, GLAccount."No.", 1, ClosingGLEntry.Count)); + + // [THEN] The consolidated closing entry equals the negated sum of posted amounts + ClosingGLEntry.CalcSums(Amount); + Assert.AreEqual( + -(Amount1 + Amount2 + Amount3), ClosingGLEntry.Amount, CloseIncomeLineAmountErr); + + // Cleanup: restore General Ledger Setup + UpdateCurOnGeneralLedgerSetup(OldAdditionalReportingCurrency); + end; + [Test] [HandlerFunctions('ConfirmHandler,RHReconcileCustandVendAccs')] procedure ReconcileCustVendAccounts_AfterExchRateAdjustment() @@ -2426,6 +2590,19 @@ codeunit 134982 "ERM Financial Reports" GenJournalLine.FindSet(); end; + local procedure ClearSelectedDimensionsForCloseIncomeStatement() + var + SelectedDimension: Record "Selected Dimension"; + AllObj: Record AllObj; + begin + // Other tests in this codeunit leave Selected Dimension records behind for report 94. They would make + // the report close per dimension, which hides defects that only occur when no dimensions are selected. + SelectedDimension.SetRange("User ID", UserId()); + SelectedDimension.SetRange("Object Type", AllObj."Object Type"::Report); + SelectedDimension.SetRange("Object ID", Report::"Close Income Statement"); + SelectedDimension.DeleteAll(); + end; + local procedure RunCloseIncomeStatement(GenJournalLine: Record "Gen. Journal Line"; PostingDate: Date; DocumentNo: Code[20]) begin // Enqueue values for CloseIncomeStatementRequestPageHandler. @@ -2438,6 +2615,19 @@ codeunit 134982 "ERM Financial Reports" Report.Run(Report::"Close Income Statement"); end; + local procedure RunCloseIncomeStatementWithRetainedEarnings(GenJournalLine: Record "Gen. Journal Line"; PostingDate: Date; DocumentNo: Code[20]; RetainedEarningsAccNo: Code[20]) + begin + // Enqueue values for CloseIncomeStatementWithRetainedEarningsRequestPageHandler. + LibraryVariableStorage.Enqueue(PostingDate); + LibraryVariableStorage.Enqueue(GenJournalLine."Journal Template Name"); + LibraryVariableStorage.Enqueue(GenJournalLine."Journal Batch Name"); + LibraryVariableStorage.Enqueue(DocumentNo); + LibraryVariableStorage.Enqueue(RetainedEarningsAccNo); + + Commit(); // commit requires to run report. + Report.Run(Report::"Close Income Statement"); + end; + local procedure ExecuteUIHandler() begin // Generate Dummy message. Required for executing the test case successfully in ES. @@ -2778,6 +2968,19 @@ codeunit 134982 "ERM Financial Reports" CloseIncomeStatement.OK().Invoke(); end; + [RequestPageHandler] + procedure CloseIncomeStatementWithRetainedEarningsRequestPageHandler(var CloseIncomeStatement: TestRequestPage "Close Income Statement") + begin + // Same as CloseIncomeStatementRequestPageHandler, but supplies a Retained Earnings Account, which + // report 94 requires when an Additional Reporting Currency is set. Dimensions are left blank. + CloseIncomeStatement.FiscalYearEndingDate.SetValue(LibraryVariableStorage.DequeueDate()); // Fiscal Year Ending Date + CloseIncomeStatement.GenJournalTemplate.SetValue(LibraryVariableStorage.DequeueText()); // Gen. Journal Template + CloseIncomeStatement.GenJournalBatch.SetValue(LibraryVariableStorage.DequeueText()); // Gen. Journal Batch + CloseIncomeStatement.DocumentNo.SetValue(LibraryVariableStorage.DequeueText()); // Document No. + CloseIncomeStatement.RetainedEarningsAcc.SetValue(LibraryVariableStorage.DequeueText()); // Retained Earnings Acc. + CloseIncomeStatement.OK().Invoke(); + end; + [RequestPageHandler] procedure AuditTrailReportRequestPageHandler(var AuditTrail: TestRequestPage "Audit Trail") begin diff --git a/src/Layers/DE/Tests/Report/ERMFinancialReports.Codeunit.al b/src/Layers/DE/Tests/Report/ERMFinancialReports.Codeunit.al index 6c4a8501230..dc00891258b 100644 --- a/src/Layers/DE/Tests/Report/ERMFinancialReports.Codeunit.al +++ b/src/Layers/DE/Tests/Report/ERMFinancialReports.Codeunit.al @@ -58,6 +58,8 @@ codeunit 134982 "ERM Financial Reports" PostingGroupDetailsLedEntryErr: Label 'Posting group should be populated from customer ledger entry'; MissingGLEntryErr: Label 'No %1 exists for account %2 and source currency %3.', Comment = '%1 = Table Caption, %2 = G/L Account No., %3 = Source Currency Code'; CloseIncomeAmountMismatchErr: Label 'Close Income Statement %1 mismatch for source currency %2. Expected %3, actual %4.', Comment = '%1 = Field Caption, %2 = Source Currency Code, %3 = Expected Amount, %4 = Actual Amount'; + CloseIncomeLineCountErr: Label 'Close Income Statement must create one consolidated line for account %1 when no dimensions are selected. Expected %2, actual %3.', Comment = '%1 = G/L Account No., %2 = Expected line count, %3 = Actual line count'; + CloseIncomeLineAmountErr: Label 'The consolidated Close Income Statement line amount must equal the negated sum of the posted G/L entry amounts.'; [Test] [HandlerFunctions('RHDetailTrialBalance')] @@ -1506,6 +1508,168 @@ codeunit 134982 "ERM Financial Reports" UpdateCurOnGeneralLedgerSetup(AdditionalReportingCurrency); end; + [Test] + [HandlerFunctions('MessageHandler,ConfirmHandler,CloseIncomeStatementRequestPageHandler')] + procedure CloseIncomeStatementConsolidatesEntriesPerGLAccountWithoutARC() + var + GLAccount: Record "G/L Account"; + BalGLAccount: Record "G/L Account"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalBatch: Record "Gen. Journal Batch"; + GenJournalTemplate: Record "Gen. Journal Template"; + ClosingGenJournalLine: Record "Gen. Journal Line"; + Date: Record Date; + GeneralLedgerSetup: Record "General Ledger Setup"; + DocNo: Code[20]; + PostingDate: Date; + Amount1: Decimal; + Amount2: Decimal; + Amount3: Decimal; + OldAdditionalReportingCurrency: Code[10]; + begin + // [SCENARIO 646076] Close Income Statement consolidates multiple G/L entries into one journal line per account when no dimensions are selected and ARC is not enabled. + Initialize(); + + // [GIVEN] No dimensions are selected for the Close Income Statement report + ClearSelectedDimensionsForCloseIncomeStatement(); + + // [GIVEN] Additional Reporting Currency is blank + GeneralLedgerSetup.Get(); + OldAdditionalReportingCurrency := GeneralLedgerSetup."Additional Reporting Currency"; + if OldAdditionalReportingCurrency <> '' then + UpdateCurOnGeneralLedgerSetup(''); + + // [GIVEN] Previous Fiscal Year Closed, New Fiscal Year Created and Closed + LibraryFiscalYear.CloseFiscalYear(); + ExecuteUIHandler(); + LibraryFiscalYear.CreateFiscalYear(); + LibraryFiscalYear.CloseFiscalYear(); + + // [GIVEN] Income Statement G/L Account "A" and a Balance Sheet G/L Account for balancing + LibraryCostAccounting.CreateIncomeStmtGLAccount(GLAccount); + LibraryERM.CreateGLAccount(BalGLAccount); + + // [GIVEN] Three posted G/L entries to account "A" with blank dimensions in the closed fiscal year + PostingDate := LibraryFiscalYear.GetLastPostingDate(true); + Amount1 := LibraryRandom.RandIntInRange(100, 500); + Amount2 := LibraryRandom.RandIntInRange(100, 500); + Amount3 := LibraryRandom.RandIntInRange(100, 500); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount1); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount2); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount3); + + // [GIVEN] Gen. Journal Template and Batch for close income statement output + LibraryERM.CreateGenJournalTemplate(GenJournalTemplate); + LibraryERM.CreateGenJournalBatch(GenJournalBatch, GenJournalTemplate.Name); + LibraryERM.ClearGenJournalLines(GenJournalBatch); + GenJournalLine.Init(); + GenJournalLine."Journal Template Name" := GenJournalBatch."Journal Template Name"; + GenJournalLine."Journal Batch Name" := GenJournalBatch.Name; + + // [WHEN] Run Close Income Statement with no dimensions selected + Date.SetRange("Period Type", Date."Period Type"::Month); + Date.SetRange("Period Start", PostingDate); + Date.FindFirst(); + DocNo := LibraryUtility.GenerateGUID(); + RunCloseIncomeStatement(GenJournalLine, NormalDate(Date."Period End"), DocNo); + + // [THEN] Exactly one closing Gen. Journal Line exists for account "A" + ClosingGenJournalLine.SetRange("Journal Template Name", GenJournalBatch."Journal Template Name"); + ClosingGenJournalLine.SetRange("Journal Batch Name", GenJournalBatch.Name); + ClosingGenJournalLine.SetRange("Document No.", DocNo); + ClosingGenJournalLine.SetRange("Account No.", GLAccount."No."); + Assert.AreEqual( + 1, ClosingGenJournalLine.Count, + StrSubstNo(CloseIncomeLineCountErr, GLAccount."No.", 1, ClosingGenJournalLine.Count)); + + // [THEN] The closing line amount equals the negated sum of posted amounts + ClosingGenJournalLine.FindFirst(); + Assert.AreEqual( + -(Amount1 + Amount2 + Amount3), ClosingGenJournalLine.Amount, CloseIncomeLineAmountErr); + + // Cleanup: restore Additional Reporting Currency + if OldAdditionalReportingCurrency <> '' then + UpdateCurOnGeneralLedgerSetup(OldAdditionalReportingCurrency); + end; + + [Test] + [HandlerFunctions('MessageHandler,ConfirmHandler,CloseIncomeStatementWithRetainedEarningsRequestPageHandler')] + procedure CloseIncomeStatementConsolidatesEntriesPerGLAccountWithARC() + var + GLAccount: Record "G/L Account"; + BalGLAccount: Record "G/L Account"; + RetainedEarningsGLAccount: Record "G/L Account"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalBatch: Record "Gen. Journal Batch"; + ClosingGLEntry: Record "G/L Entry"; + Date: Record Date; + DocNo: Code[20]; + PostingDate: Date; + Amount1: Decimal; + Amount2: Decimal; + Amount3: Decimal; + OldAdditionalReportingCurrency: Code[10]; + begin + // [SCENARIO 646077] Close Income Statement consolidates multiple G/L entries into one journal line per account when no dimensions are selected and ARC is enabled. + Initialize(); + + // [GIVEN] No dimensions are selected for the Close Income Statement report + ClearSelectedDimensionsForCloseIncomeStatement(); + + // [GIVEN] Additional Reporting Currency is enabled + OldAdditionalReportingCurrency := UpdateCurOnGeneralLedgerSetup(LibraryERM.CreateCurrencyWithRandomExchRates()); + + // [GIVEN] Previous Fiscal Year Closed, New Fiscal Year Created and Closed + LibraryFiscalYear.CloseFiscalYear(); + ExecuteUIHandler(); + LibraryFiscalYear.CreateFiscalYear(); + LibraryFiscalYear.CloseFiscalYear(); + + // [GIVEN] Income Statement G/L Account "A", a balancing account and a Retained Earnings account + LibraryCostAccounting.CreateIncomeStmtGLAccount(GLAccount); + LibraryERM.CreateGLAccount(BalGLAccount); + LibraryERM.CreateGLAccount(RetainedEarningsGLAccount); + + // [GIVEN] Three posted G/L entries to account "A" with blank dimensions and blank Business Unit Code + PostingDate := LibraryFiscalYear.GetLastPostingDate(true); + Amount1 := LibraryRandom.RandIntInRange(100, 500); + Amount2 := LibraryRandom.RandIntInRange(100, 500); + Amount3 := LibraryRandom.RandIntInRange(100, 500); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount1); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount2); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount3); + + // [GIVEN] Gen. Journal Batch with a No. Series, required because with an Additional Reporting Currency + // report 94 posts the closing entries directly instead of inserting journal lines. + LibraryERM.SelectGenJnlBatch(GenJournalBatch); + LibraryERM.ClearGenJournalLines(GenJournalBatch); + GenJournalLine.Init(); + GenJournalLine."Journal Template Name" := GenJournalBatch."Journal Template Name"; + GenJournalLine."Journal Batch Name" := GenJournalBatch.Name; + + // [WHEN] Run Close Income Statement with no dimensions selected + Date.SetRange("Period Type", Date."Period Type"::Month); + Date.SetRange("Period Start", PostingDate); + Date.FindFirst(); + DocNo := LibraryUtility.GenerateGUID(); + RunCloseIncomeStatementWithRetainedEarnings(GenJournalLine, NormalDate(Date."Period End"), DocNo, RetainedEarningsGLAccount."No."); + + // [THEN] Exactly one closing G/L Entry exists for account "A" + ClosingGLEntry.SetRange("G/L Account No.", GLAccount."No."); + ClosingGLEntry.SetRange("Document No.", DocNo); + Assert.AreEqual( + 1, ClosingGLEntry.Count, + StrSubstNo(CloseIncomeLineCountErr, GLAccount."No.", 1, ClosingGLEntry.Count)); + + // [THEN] The consolidated closing entry equals the negated sum of posted amounts + ClosingGLEntry.CalcSums(Amount); + Assert.AreEqual( + -(Amount1 + Amount2 + Amount3), ClosingGLEntry.Amount, CloseIncomeLineAmountErr); + + // Cleanup: restore General Ledger Setup + UpdateCurOnGeneralLedgerSetup(OldAdditionalReportingCurrency); + end; + [Test] [HandlerFunctions('ConfirmHandler,RHReconcileCustandVendAccs')] procedure ReconcileCustVendAccounts_AfterExchRateAdjustment() @@ -2420,6 +2584,19 @@ codeunit 134982 "ERM Financial Reports" GenJournalLine.FindSet(); end; + local procedure ClearSelectedDimensionsForCloseIncomeStatement() + var + SelectedDimension: Record "Selected Dimension"; + AllObj: Record AllObj; + begin + // Other tests in this codeunit leave Selected Dimension records behind for report 94. They would make + // the report close per dimension, which hides defects that only occur when no dimensions are selected. + SelectedDimension.SetRange("User ID", UserId()); + SelectedDimension.SetRange("Object Type", AllObj."Object Type"::Report); + SelectedDimension.SetRange("Object ID", Report::"Close Income Statement"); + SelectedDimension.DeleteAll(); + end; + local procedure RunCloseIncomeStatement(GenJournalLine: Record "Gen. Journal Line"; PostingDate: Date; DocumentNo: Code[20]) begin // Enqueue values for CloseIncomeStatementRequestPageHandler. @@ -2432,6 +2609,19 @@ codeunit 134982 "ERM Financial Reports" Report.Run(Report::"Close Income Statement"); end; + local procedure RunCloseIncomeStatementWithRetainedEarnings(GenJournalLine: Record "Gen. Journal Line"; PostingDate: Date; DocumentNo: Code[20]; RetainedEarningsAccNo: Code[20]) + begin + // Enqueue values for CloseIncomeStatementWithRetainedEarningsRequestPageHandler. + LibraryVariableStorage.Enqueue(PostingDate); + LibraryVariableStorage.Enqueue(GenJournalLine."Journal Template Name"); + LibraryVariableStorage.Enqueue(GenJournalLine."Journal Batch Name"); + LibraryVariableStorage.Enqueue(DocumentNo); + LibraryVariableStorage.Enqueue(RetainedEarningsAccNo); + + Commit(); // commit requires to run report. + Report.Run(Report::"Close Income Statement"); + end; + local procedure ExecuteUIHandler() begin // Generate Dummy message. Required for executing the test case successfully in ES. @@ -2776,6 +2966,19 @@ codeunit 134982 "ERM Financial Reports" CloseIncomeStatement.OK().Invoke(); end; + [RequestPageHandler] + procedure CloseIncomeStatementWithRetainedEarningsRequestPageHandler(var CloseIncomeStatement: TestRequestPage "Close Income Statement") + begin + // Same as CloseIncomeStatementRequestPageHandler, but supplies a Retained Earnings Account, which + // report 94 requires when an Additional Reporting Currency is set. Dimensions are left blank. + CloseIncomeStatement.FiscalYearEndingDate.SetValue(LibraryVariableStorage.DequeueDate()); // Fiscal Year Ending Date + CloseIncomeStatement.GenJournalTemplate.SetValue(LibraryVariableStorage.DequeueText()); // Gen. Journal Template + CloseIncomeStatement.GenJournalBatch.SetValue(LibraryVariableStorage.DequeueText()); // Gen. Journal Batch + CloseIncomeStatement.DocumentNo.SetValue(LibraryVariableStorage.DequeueText()); // Document No. + CloseIncomeStatement.RetainedEarningsAcc.SetValue(LibraryVariableStorage.DequeueText()); // Retained Earnings Acc. + CloseIncomeStatement.OK().Invoke(); + end; + [RequestPageHandler] procedure AuditTrailReportRequestPageHandler(var AuditTrail: TestRequestPage "Audit Trail") begin diff --git a/src/Layers/ES/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al b/src/Layers/ES/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al index bd890ff73fd..5ce292a3f11 100644 --- a/src/Layers/ES/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al +++ b/src/Layers/ES/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al @@ -93,7 +93,7 @@ report 94 "Close Income Statement" else TempEntryNoAmountBuffer."Business Unit Code" := ''; TempEntryNoAmountBuffer."Source Currency Code" := "Source Currency Code"; - TempEntryNoAmountBuffer."Entry No." := GetEntryNo(DimensionBufferID); + 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"; @@ -132,7 +132,6 @@ report 94 "Close Income Statement" TempEntryNoAmountBuffer.Reset(); MaxEntry := TempEntryNoAmountBuffer.Count(); EntryCount := 0; - EntryNo := 0; Window.Update(2, Text012); Window.Update(3, 0); @@ -158,7 +157,8 @@ report 94 "Close Income Statement" GenJnlLine."Business Unit Code" := TempEntryNoAmountBuffer."Business Unit Code"; TempDimBuf2.DeleteAll(); - DimBufMgt.RetrieveDimensions(TempEntryNoAmountBuffer."Entry No.", TempDimBuf2); + 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); @@ -202,6 +202,7 @@ report 94 "Close Income Statement" TempEntryNoAmountBuffer.DeleteAll(); EntryCount := 0; + ResetEntryNoGrouping(); LastWindowUpdateDateTime := CurrentDateTime; end; @@ -509,6 +510,8 @@ report 94 "Close Income Statement" 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.'; @@ -849,22 +852,62 @@ report 94 "Close Income Statement" 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 Code" := TempEntryNoAmountBuffer."Source Currency Code"; 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): Integer + 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 DimensionBufferID <> 0 then + if EntryNoDimensionIds.Get(MakeGroupKey(BusinessUnitCode, Format(BufferEntryNo), ''), DimensionBufferID) then exit(DimensionBufferID); - EntryNo := EntryNo - 1; - exit(EntryNo); + exit(BufferEntryNo); + end; + + local procedure MakeGroupKey(BusinessUnitCode: Code[20]; DimensionPart: Text; SourceCurrencyCode: Code[10]): Text + begin + exit(BusinessUnitCode + '|' + DimensionPart + '|' + SourceCurrencyCode); + end; + + local procedure ResetEntryNoGrouping() + begin + Clear(GroupEntryNos); + Clear(EntryNoDimensionIds); + EntryNo := 0; end; /// diff --git a/src/Layers/IT/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al b/src/Layers/IT/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al index bd931e56cd9..9a72ce5c019 100644 --- a/src/Layers/IT/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al +++ b/src/Layers/IT/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al @@ -89,7 +89,7 @@ report 94 "Close Income Statement" else TempEntryNoAmountBuffer."Business Unit Code" := ''; TempEntryNoAmountBuffer."Source Currency Code" := "Source Currency Code"; - TempEntryNoAmountBuffer."Entry No." := GetEntryNo(DimensionBufferID); + 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"; @@ -123,7 +123,6 @@ report 94 "Close Income Statement" TempEntryNoAmountBuffer.Reset(); MaxEntry := TempEntryNoAmountBuffer.Count(); EntryCount := 0; - EntryNo := 0; Window.Update(2, Text012); Window.Update(3, 0); @@ -148,7 +147,8 @@ report 94 "Close Income Statement" GenJnlLine."Business Unit Code" := TempEntryNoAmountBuffer."Business Unit Code"; TempDimBuf2.DeleteAll(); - DimBufMgt.RetrieveDimensions(TempEntryNoAmountBuffer."Entry No.", TempDimBuf2); + 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); @@ -199,6 +199,7 @@ report 94 "Close Income Statement" TempEntryNoAmountBuffer.DeleteAll(); EntryCount := 0; + ResetEntryNoGrouping(); LastWindowUpdateDateTime := CurrentDateTime; end; @@ -546,6 +547,8 @@ report 94 "Close Income Statement" 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.'; @@ -810,22 +813,62 @@ report 94 "Close Income Statement" 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 Code" := TempEntryNoAmountBuffer."Source Currency Code"; 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): Integer + 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 DimensionBufferID <> 0 then + if EntryNoDimensionIds.Get(MakeGroupKey(BusinessUnitCode, Format(BufferEntryNo), ''), DimensionBufferID) then exit(DimensionBufferID); - EntryNo := EntryNo - 1; - exit(EntryNo); + exit(BufferEntryNo); + end; + + local procedure MakeGroupKey(BusinessUnitCode: Code[20]; DimensionPart: Text; SourceCurrencyCode: Code[10]): Text + begin + exit(BusinessUnitCode + '|' + DimensionPart + '|' + SourceCurrencyCode); + end; + + local procedure ResetEntryNoGrouping() + begin + Clear(GroupEntryNos); + Clear(EntryNoDimensionIds); + EntryNo := 0; end; /// diff --git a/src/Layers/IT/Tests/Report/ERMFinancialReports.Codeunit.al b/src/Layers/IT/Tests/Report/ERMFinancialReports.Codeunit.al index ce4550bba7e..dd0f99c0e40 100644 --- a/src/Layers/IT/Tests/Report/ERMFinancialReports.Codeunit.al +++ b/src/Layers/IT/Tests/Report/ERMFinancialReports.Codeunit.al @@ -58,6 +58,8 @@ codeunit 134982 "ERM Financial Reports" PostingGroupDetailsLedEntryErr: Label 'Posting group should be populated from customer ledger entry'; MissingGLEntryErr: Label 'No %1 exists for account %2 and source currency %3.', Comment = '%1 = Table Caption, %2 = G/L Account No., %3 = Source Currency Code'; CloseIncomeAmountMismatchErr: Label 'Close Income Statement %1 mismatch for source currency %2. Expected %3, actual %4.', Comment = '%1 = Field Caption, %2 = Source Currency Code, %3 = Expected Amount, %4 = Actual Amount'; + CloseIncomeLineCountErr: Label 'Close Income Statement must create one consolidated line for account %1 when no dimensions are selected. Expected %2, actual %3.', Comment = '%1 = G/L Account No., %2 = Expected line count, %3 = Actual line count'; + CloseIncomeLineAmountErr: Label 'The consolidated Close Income Statement line amount must equal the negated sum of the posted G/L entry amounts.'; [Test] [HandlerFunctions('RHDetailTrialBalance')] @@ -1508,6 +1510,168 @@ codeunit 134982 "ERM Financial Reports" UpdateCurOnGeneralLedgerSetup(AdditionalReportingCurrency); end; + [Test] + [HandlerFunctions('MessageHandler,ConfirmHandler,CloseIncomeStatementRequestPageHandler')] + procedure CloseIncomeStatementConsolidatesEntriesPerGLAccountWithoutARC() + var + GLAccount: Record "G/L Account"; + BalGLAccount: Record "G/L Account"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalBatch: Record "Gen. Journal Batch"; + GenJournalTemplate: Record "Gen. Journal Template"; + ClosingGenJournalLine: Record "Gen. Journal Line"; + Date: Record Date; + GeneralLedgerSetup: Record "General Ledger Setup"; + DocNo: Code[20]; + PostingDate: Date; + Amount1: Decimal; + Amount2: Decimal; + Amount3: Decimal; + OldAdditionalReportingCurrency: Code[10]; + begin + // [SCENARIO 646076] Close Income Statement consolidates multiple G/L entries into one journal line per account when no dimensions are selected and ARC is not enabled. + Initialize(); + + // [GIVEN] No dimensions are selected for the Close Income Statement report + ClearSelectedDimensionsForCloseIncomeStatement(); + + // [GIVEN] Additional Reporting Currency is blank + GeneralLedgerSetup.Get(); + OldAdditionalReportingCurrency := GeneralLedgerSetup."Additional Reporting Currency"; + if OldAdditionalReportingCurrency <> '' then + UpdateCurOnGeneralLedgerSetup(''); + + // [GIVEN] Previous Fiscal Year Closed, New Fiscal Year Created and Closed + LibraryFiscalYear.CloseFiscalYear(); + ExecuteUIHandler(); + LibraryFiscalYear.CreateFiscalYear(); + LibraryFiscalYear.CloseFiscalYear(); + + // [GIVEN] Income Statement G/L Account "A" and a Balance Sheet G/L Account for balancing + LibraryCostAccounting.CreateIncomeStmtGLAccount(GLAccount); + LibraryERM.CreateGLAccount(BalGLAccount); + + // [GIVEN] Three posted G/L entries to account "A" with blank dimensions in the closed fiscal year + PostingDate := LibraryFiscalYear.GetLastPostingDate(true); + Amount1 := LibraryRandom.RandIntInRange(100, 500); + Amount2 := LibraryRandom.RandIntInRange(100, 500); + Amount3 := LibraryRandom.RandIntInRange(100, 500); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount1); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount2); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount3); + + // [GIVEN] Gen. Journal Template and Batch for close income statement output + LibraryERM.CreateGenJournalTemplate(GenJournalTemplate); + LibraryERM.CreateGenJournalBatch(GenJournalBatch, GenJournalTemplate.Name); + LibraryERM.ClearGenJournalLines(GenJournalBatch); + GenJournalLine.Init(); + GenJournalLine."Journal Template Name" := GenJournalBatch."Journal Template Name"; + GenJournalLine."Journal Batch Name" := GenJournalBatch.Name; + + // [WHEN] Run Close Income Statement with no dimensions selected + Date.SetRange("Period Type", Date."Period Type"::Month); + Date.SetRange("Period Start", PostingDate); + Date.FindFirst(); + DocNo := LibraryUtility.GenerateGUID(); + RunCloseIncomeStatement(GenJournalLine, NormalDate(Date."Period End"), DocNo); + + // [THEN] Exactly one closing Gen. Journal Line exists for account "A" + ClosingGenJournalLine.SetRange("Journal Template Name", GenJournalBatch."Journal Template Name"); + ClosingGenJournalLine.SetRange("Journal Batch Name", GenJournalBatch.Name); + ClosingGenJournalLine.SetRange("Document No.", DocNo); + ClosingGenJournalLine.SetRange("Account No.", GLAccount."No."); + Assert.AreEqual( + 1, ClosingGenJournalLine.Count, + StrSubstNo(CloseIncomeLineCountErr, GLAccount."No.", 1, ClosingGenJournalLine.Count)); + + // [THEN] The closing line amount equals the negated sum of posted amounts + ClosingGenJournalLine.FindFirst(); + Assert.AreEqual( + -(Amount1 + Amount2 + Amount3), ClosingGenJournalLine.Amount, CloseIncomeLineAmountErr); + + // Cleanup: restore Additional Reporting Currency + if OldAdditionalReportingCurrency <> '' then + UpdateCurOnGeneralLedgerSetup(OldAdditionalReportingCurrency); + end; + + [Test] + [HandlerFunctions('MessageHandler,ConfirmHandler,CloseIncomeStatementWithRetainedEarningsRequestPageHandler')] + procedure CloseIncomeStatementConsolidatesEntriesPerGLAccountWithARC() + var + GLAccount: Record "G/L Account"; + BalGLAccount: Record "G/L Account"; + RetainedEarningsGLAccount: Record "G/L Account"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalBatch: Record "Gen. Journal Batch"; + ClosingGLEntry: Record "G/L Entry"; + Date: Record Date; + DocNo: Code[20]; + PostingDate: Date; + Amount1: Decimal; + Amount2: Decimal; + Amount3: Decimal; + OldAdditionalReportingCurrency: Code[10]; + begin + // [SCENARIO 646077] Close Income Statement consolidates multiple G/L entries into one journal line per account when no dimensions are selected and ARC is enabled. + Initialize(); + + // [GIVEN] No dimensions are selected for the Close Income Statement report + ClearSelectedDimensionsForCloseIncomeStatement(); + + // [GIVEN] Additional Reporting Currency is enabled + OldAdditionalReportingCurrency := UpdateCurOnGeneralLedgerSetup(LibraryERM.CreateCurrencyWithRandomExchRates()); + + // [GIVEN] Previous Fiscal Year Closed, New Fiscal Year Created and Closed + LibraryFiscalYear.CloseFiscalYear(); + ExecuteUIHandler(); + LibraryFiscalYear.CreateFiscalYear(); + LibraryFiscalYear.CloseFiscalYear(); + + // [GIVEN] Income Statement G/L Account "A", a balancing account and a Retained Earnings account + LibraryCostAccounting.CreateIncomeStmtGLAccount(GLAccount); + LibraryERM.CreateGLAccount(BalGLAccount); + LibraryERM.CreateGLAccount(RetainedEarningsGLAccount); + + // [GIVEN] Three posted G/L entries to account "A" with blank dimensions and blank Business Unit Code + PostingDate := LibraryFiscalYear.GetLastPostingDate(true); + Amount1 := LibraryRandom.RandIntInRange(100, 500); + Amount2 := LibraryRandom.RandIntInRange(100, 500); + Amount3 := LibraryRandom.RandIntInRange(100, 500); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount1); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount2); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount3); + + // [GIVEN] Gen. Journal Batch with a No. Series, required because with an Additional Reporting Currency + // report 94 posts the closing entries directly instead of inserting journal lines. + LibraryERM.SelectGenJnlBatch(GenJournalBatch); + LibraryERM.ClearGenJournalLines(GenJournalBatch); + GenJournalLine.Init(); + GenJournalLine."Journal Template Name" := GenJournalBatch."Journal Template Name"; + GenJournalLine."Journal Batch Name" := GenJournalBatch.Name; + + // [WHEN] Run Close Income Statement with no dimensions selected + Date.SetRange("Period Type", Date."Period Type"::Month); + Date.SetRange("Period Start", PostingDate); + Date.FindFirst(); + DocNo := LibraryUtility.GenerateGUID(); + RunCloseIncomeStatementWithRetainedEarnings(GenJournalLine, NormalDate(Date."Period End"), DocNo, RetainedEarningsGLAccount."No."); + + // [THEN] Exactly one closing G/L Entry exists for account "A" + ClosingGLEntry.SetRange("G/L Account No.", GLAccount."No."); + ClosingGLEntry.SetRange("Document No.", DocNo); + Assert.AreEqual( + 1, ClosingGLEntry.Count, + StrSubstNo(CloseIncomeLineCountErr, GLAccount."No.", 1, ClosingGLEntry.Count)); + + // [THEN] The consolidated closing entry equals the negated sum of posted amounts + ClosingGLEntry.CalcSums(Amount); + Assert.AreEqual( + -(Amount1 + Amount2 + Amount3), ClosingGLEntry.Amount, CloseIncomeLineAmountErr); + + // Cleanup: restore General Ledger Setup + UpdateCurOnGeneralLedgerSetup(OldAdditionalReportingCurrency); + end; + [Test] [HandlerFunctions('ConfirmHandler,RHReconcileCustandVendAccs')] procedure ReconcileCustVendAccounts_AfterExchRateAdjustment() @@ -2431,6 +2595,19 @@ codeunit 134982 "ERM Financial Reports" GenJournalLine.FindSet(); end; + local procedure ClearSelectedDimensionsForCloseIncomeStatement() + var + SelectedDimension: Record "Selected Dimension"; + AllObj: Record AllObj; + begin + // Other tests in this codeunit leave Selected Dimension records behind for report 94. They would make + // the report close per dimension, which hides defects that only occur when no dimensions are selected. + SelectedDimension.SetRange("User ID", UserId()); + SelectedDimension.SetRange("Object Type", AllObj."Object Type"::Report); + SelectedDimension.SetRange("Object ID", Report::"Close Income Statement"); + SelectedDimension.DeleteAll(); + end; + local procedure RunCloseIncomeStatement(GenJournalLine: Record "Gen. Journal Line"; PostingDate: Date; DocumentNo: Code[20]) begin // Enqueue values for CloseIncomeStatementRequestPageHandler. @@ -2443,6 +2620,19 @@ codeunit 134982 "ERM Financial Reports" Report.Run(Report::"Close Income Statement"); end; + local procedure RunCloseIncomeStatementWithRetainedEarnings(GenJournalLine: Record "Gen. Journal Line"; PostingDate: Date; DocumentNo: Code[20]; RetainedEarningsAccNo: Code[20]) + begin + // Enqueue values for CloseIncomeStatementWithRetainedEarningsRequestPageHandler. + LibraryVariableStorage.Enqueue(PostingDate); + LibraryVariableStorage.Enqueue(GenJournalLine."Journal Template Name"); + LibraryVariableStorage.Enqueue(GenJournalLine."Journal Batch Name"); + LibraryVariableStorage.Enqueue(DocumentNo); + LibraryVariableStorage.Enqueue(RetainedEarningsAccNo); + + Commit(); // commit requires to run report. + Report.Run(Report::"Close Income Statement"); + end; + local procedure ExecuteUIHandler() begin // Generate Dummy message. Required for executing the test case successfully in ES. @@ -2790,6 +2980,19 @@ codeunit 134982 "ERM Financial Reports" CloseIncomeStatement.OK().Invoke(); end; + [RequestPageHandler] + procedure CloseIncomeStatementWithRetainedEarningsRequestPageHandler(var CloseIncomeStatement: TestRequestPage "Close Income Statement") + begin + // Same as CloseIncomeStatementRequestPageHandler, but supplies a Retained Earnings Account, which + // report 94 requires when an Additional Reporting Currency is set. Dimensions are left blank. + CloseIncomeStatement.FiscalYearEndingDate.SetValue(LibraryVariableStorage.DequeueDate()); // Fiscal Year Ending Date + CloseIncomeStatement.GenJournalTemplate.SetValue(LibraryVariableStorage.DequeueText()); // Gen. Journal Template + CloseIncomeStatement.GenJournalBatch.SetValue(LibraryVariableStorage.DequeueText()); // Gen. Journal Batch + CloseIncomeStatement.DocumentNo.SetValue(LibraryVariableStorage.DequeueText()); // Document No. + CloseIncomeStatement.RetainedEarningsAcc.SetValue(LibraryVariableStorage.DequeueText()); // Retained Earnings Acc. + CloseIncomeStatement.OK().Invoke(); + end; + [RequestPageHandler] procedure AuditTrailReportRequestPageHandler(var AuditTrail: TestRequestPage "Audit Trail") begin diff --git a/src/Layers/W1/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al b/src/Layers/W1/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al index 095e0f3ea90..f97b84323da 100644 --- a/src/Layers/W1/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al +++ b/src/Layers/W1/BaseApp/Finance/GeneralLedger/Setup/CloseIncomeStatement.Report.al @@ -89,7 +89,7 @@ report 94 "Close Income Statement" else TempEntryNoAmountBuffer."Business Unit Code" := ''; TempEntryNoAmountBuffer."Source Currency Code" := "Source Currency Code"; - TempEntryNoAmountBuffer."Entry No." := GetEntryNo(DimensionBufferID); + 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"; @@ -123,7 +123,6 @@ report 94 "Close Income Statement" TempEntryNoAmountBuffer.Reset(); MaxEntry := TempEntryNoAmountBuffer.Count(); EntryCount := 0; - EntryNo := 0; Window.Update(2, Text012); Window.Update(3, 0); @@ -148,7 +147,8 @@ report 94 "Close Income Statement" GenJnlLine."Business Unit Code" := TempEntryNoAmountBuffer."Business Unit Code"; TempDimBuf2.DeleteAll(); - DimBufMgt.RetrieveDimensions(TempEntryNoAmountBuffer."Entry No.", TempDimBuf2); + 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); @@ -199,6 +199,7 @@ report 94 "Close Income Statement" TempEntryNoAmountBuffer.DeleteAll(); EntryCount := 0; + ResetEntryNoGrouping(); LastWindowUpdateDateTime := CurrentDateTime; end; @@ -530,6 +531,8 @@ report 94 "Close Income Statement" 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.'; @@ -793,22 +796,62 @@ report 94 "Close Income Statement" 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 Code" := TempEntryNoAmountBuffer."Source Currency Code"; 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): Integer + 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 DimensionBufferID <> 0 then + if EntryNoDimensionIds.Get(MakeGroupKey(BusinessUnitCode, Format(BufferEntryNo), ''), DimensionBufferID) then exit(DimensionBufferID); - EntryNo := EntryNo - 1; - exit(EntryNo); + exit(BufferEntryNo); + end; + + local procedure MakeGroupKey(BusinessUnitCode: Code[20]; DimensionPart: Text; SourceCurrencyCode: Code[10]): Text + begin + exit(BusinessUnitCode + '|' + DimensionPart + '|' + SourceCurrencyCode); + end; + + local procedure ResetEntryNoGrouping() + begin + Clear(GroupEntryNos); + Clear(EntryNoDimensionIds); + EntryNo := 0; end; /// diff --git a/src/Layers/W1/Tests/Report/ERMFinancialReports.Codeunit.al b/src/Layers/W1/Tests/Report/ERMFinancialReports.Codeunit.al index ffc4fce6cfe..c41d0db3f36 100644 --- a/src/Layers/W1/Tests/Report/ERMFinancialReports.Codeunit.al +++ b/src/Layers/W1/Tests/Report/ERMFinancialReports.Codeunit.al @@ -58,6 +58,8 @@ codeunit 134982 "ERM Financial Reports" PostingGroupDetailsLedEntryErr: Label 'Posting group should be populated from customer ledger entry'; MissingGLEntryErr: Label 'No %1 exists for account %2 and source currency %3.', Comment = '%1 = Table Caption, %2 = G/L Account No., %3 = Source Currency Code'; CloseIncomeAmountMismatchErr: Label 'Close Income Statement %1 mismatch for source currency %2. Expected %3, actual %4.', Comment = '%1 = Field Caption, %2 = Source Currency Code, %3 = Expected Amount, %4 = Actual Amount'; + CloseIncomeLineCountErr: Label 'Close Income Statement must create one consolidated line for account %1 when no dimensions are selected. Expected %2, actual %3.', Comment = '%1 = G/L Account No., %2 = Expected line count, %3 = Actual line count'; + CloseIncomeLineAmountErr: Label 'The consolidated Close Income Statement line amount must equal the negated sum of the posted G/L entry amounts.'; [Test] [HandlerFunctions('RHDetailTrialBalance')] @@ -1506,6 +1508,168 @@ codeunit 134982 "ERM Financial Reports" UpdateCurOnGeneralLedgerSetup(AdditionalReportingCurrency); end; + [Test] + [HandlerFunctions('MessageHandler,ConfirmHandler,CloseIncomeStatementRequestPageHandler')] + procedure CloseIncomeStatementConsolidatesEntriesPerGLAccountWithoutARC() + var + GLAccount: Record "G/L Account"; + BalGLAccount: Record "G/L Account"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalBatch: Record "Gen. Journal Batch"; + GenJournalTemplate: Record "Gen. Journal Template"; + ClosingGenJournalLine: Record "Gen. Journal Line"; + Date: Record Date; + GeneralLedgerSetup: Record "General Ledger Setup"; + DocNo: Code[20]; + PostingDate: Date; + Amount1: Decimal; + Amount2: Decimal; + Amount3: Decimal; + OldAdditionalReportingCurrency: Code[10]; + begin + // [SCENARIO 646076] Close Income Statement consolidates multiple G/L entries into one journal line per account when no dimensions are selected and ARC is not enabled. + Initialize(); + + // [GIVEN] No dimensions are selected for the Close Income Statement report + ClearSelectedDimensionsForCloseIncomeStatement(); + + // [GIVEN] Additional Reporting Currency is blank + GeneralLedgerSetup.Get(); + OldAdditionalReportingCurrency := GeneralLedgerSetup."Additional Reporting Currency"; + if OldAdditionalReportingCurrency <> '' then + UpdateCurOnGeneralLedgerSetup(''); + + // [GIVEN] Previous Fiscal Year Closed, New Fiscal Year Created and Closed + LibraryFiscalYear.CloseFiscalYear(); + ExecuteUIHandler(); + LibraryFiscalYear.CreateFiscalYear(); + LibraryFiscalYear.CloseFiscalYear(); + + // [GIVEN] Income Statement G/L Account "A" and a Balance Sheet G/L Account for balancing + LibraryCostAccounting.CreateIncomeStmtGLAccount(GLAccount); + LibraryERM.CreateGLAccount(BalGLAccount); + + // [GIVEN] Three posted G/L entries to account "A" with blank dimensions in the closed fiscal year + PostingDate := LibraryFiscalYear.GetLastPostingDate(true); + Amount1 := LibraryRandom.RandIntInRange(100, 500); + Amount2 := LibraryRandom.RandIntInRange(100, 500); + Amount3 := LibraryRandom.RandIntInRange(100, 500); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount1); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount2); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount3); + + // [GIVEN] Gen. Journal Template and Batch for close income statement output + LibraryERM.CreateGenJournalTemplate(GenJournalTemplate); + LibraryERM.CreateGenJournalBatch(GenJournalBatch, GenJournalTemplate.Name); + LibraryERM.ClearGenJournalLines(GenJournalBatch); + GenJournalLine.Init(); + GenJournalLine."Journal Template Name" := GenJournalBatch."Journal Template Name"; + GenJournalLine."Journal Batch Name" := GenJournalBatch.Name; + + // [WHEN] Run Close Income Statement with no dimensions selected + Date.SetRange("Period Type", Date."Period Type"::Month); + Date.SetRange("Period Start", PostingDate); + Date.FindFirst(); + DocNo := LibraryUtility.GenerateGUID(); + RunCloseIncomeStatement(GenJournalLine, NormalDate(Date."Period End"), DocNo); + + // [THEN] Exactly one closing Gen. Journal Line exists for account "A" + ClosingGenJournalLine.SetRange("Journal Template Name", GenJournalBatch."Journal Template Name"); + ClosingGenJournalLine.SetRange("Journal Batch Name", GenJournalBatch.Name); + ClosingGenJournalLine.SetRange("Document No.", DocNo); + ClosingGenJournalLine.SetRange("Account No.", GLAccount."No."); + Assert.AreEqual( + 1, ClosingGenJournalLine.Count, + StrSubstNo(CloseIncomeLineCountErr, GLAccount."No.", 1, ClosingGenJournalLine.Count)); + + // [THEN] The closing line amount equals the negated sum of posted amounts + ClosingGenJournalLine.FindFirst(); + Assert.AreEqual( + -(Amount1 + Amount2 + Amount3), ClosingGenJournalLine.Amount, CloseIncomeLineAmountErr); + + // Cleanup: restore Additional Reporting Currency + if OldAdditionalReportingCurrency <> '' then + UpdateCurOnGeneralLedgerSetup(OldAdditionalReportingCurrency); + end; + + [Test] + [HandlerFunctions('MessageHandler,ConfirmHandler,CloseIncomeStatementWithRetainedEarningsRequestPageHandler')] + procedure CloseIncomeStatementConsolidatesEntriesPerGLAccountWithARC() + var + GLAccount: Record "G/L Account"; + BalGLAccount: Record "G/L Account"; + RetainedEarningsGLAccount: Record "G/L Account"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalBatch: Record "Gen. Journal Batch"; + ClosingGLEntry: Record "G/L Entry"; + Date: Record Date; + DocNo: Code[20]; + PostingDate: Date; + Amount1: Decimal; + Amount2: Decimal; + Amount3: Decimal; + OldAdditionalReportingCurrency: Code[10]; + begin + // [SCENARIO 646077] Close Income Statement consolidates multiple G/L entries into one journal line per account when no dimensions are selected and ARC is enabled. + Initialize(); + + // [GIVEN] No dimensions are selected for the Close Income Statement report + ClearSelectedDimensionsForCloseIncomeStatement(); + + // [GIVEN] Additional Reporting Currency is enabled + OldAdditionalReportingCurrency := UpdateCurOnGeneralLedgerSetup(LibraryERM.CreateCurrencyWithRandomExchRates()); + + // [GIVEN] Previous Fiscal Year Closed, New Fiscal Year Created and Closed + LibraryFiscalYear.CloseFiscalYear(); + ExecuteUIHandler(); + LibraryFiscalYear.CreateFiscalYear(); + LibraryFiscalYear.CloseFiscalYear(); + + // [GIVEN] Income Statement G/L Account "A", a balancing account and a Retained Earnings account + LibraryCostAccounting.CreateIncomeStmtGLAccount(GLAccount); + LibraryERM.CreateGLAccount(BalGLAccount); + LibraryERM.CreateGLAccount(RetainedEarningsGLAccount); + + // [GIVEN] Three posted G/L entries to account "A" with blank dimensions and blank Business Unit Code + PostingDate := LibraryFiscalYear.GetLastPostingDate(true); + Amount1 := LibraryRandom.RandIntInRange(100, 500); + Amount2 := LibraryRandom.RandIntInRange(100, 500); + Amount3 := LibraryRandom.RandIntInRange(100, 500); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount1); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount2); + CreateAndPostGenJnlLineOnPostingDate(GLAccount."No.", BalGLAccount."No.", PostingDate, Amount3); + + // [GIVEN] Gen. Journal Batch with a No. Series, required because with an Additional Reporting Currency + // report 94 posts the closing entries directly instead of inserting journal lines. + LibraryERM.SelectGenJnlBatch(GenJournalBatch); + LibraryERM.ClearGenJournalLines(GenJournalBatch); + GenJournalLine.Init(); + GenJournalLine."Journal Template Name" := GenJournalBatch."Journal Template Name"; + GenJournalLine."Journal Batch Name" := GenJournalBatch.Name; + + // [WHEN] Run Close Income Statement with no dimensions selected + Date.SetRange("Period Type", Date."Period Type"::Month); + Date.SetRange("Period Start", PostingDate); + Date.FindFirst(); + DocNo := LibraryUtility.GenerateGUID(); + RunCloseIncomeStatementWithRetainedEarnings(GenJournalLine, NormalDate(Date."Period End"), DocNo, RetainedEarningsGLAccount."No."); + + // [THEN] Exactly one closing G/L Entry exists for account "A" + ClosingGLEntry.SetRange("G/L Account No.", GLAccount."No."); + ClosingGLEntry.SetRange("Document No.", DocNo); + Assert.AreEqual( + 1, ClosingGLEntry.Count, + StrSubstNo(CloseIncomeLineCountErr, GLAccount."No.", 1, ClosingGLEntry.Count)); + + // [THEN] The consolidated closing entry equals the negated sum of posted amounts + ClosingGLEntry.CalcSums(Amount); + Assert.AreEqual( + -(Amount1 + Amount2 + Amount3), ClosingGLEntry.Amount, CloseIncomeLineAmountErr); + + // Cleanup: restore General Ledger Setup + UpdateCurOnGeneralLedgerSetup(OldAdditionalReportingCurrency); + end; + [Test] [HandlerFunctions('ConfirmHandler,RHReconcileCustandVendAccs')] procedure ReconcileCustVendAccounts_AfterExchRateAdjustment() @@ -2426,6 +2590,19 @@ codeunit 134982 "ERM Financial Reports" GenJournalLine.FindSet(); end; + local procedure ClearSelectedDimensionsForCloseIncomeStatement() + var + SelectedDimension: Record "Selected Dimension"; + AllObj: Record AllObj; + begin + // Other tests in this codeunit leave Selected Dimension records behind for report 94. They would make + // the report close per dimension, which hides defects that only occur when no dimensions are selected. + SelectedDimension.SetRange("User ID", UserId()); + SelectedDimension.SetRange("Object Type", AllObj."Object Type"::Report); + SelectedDimension.SetRange("Object ID", Report::"Close Income Statement"); + SelectedDimension.DeleteAll(); + end; + local procedure RunCloseIncomeStatement(GenJournalLine: Record "Gen. Journal Line"; PostingDate: Date; DocumentNo: Code[20]) begin // Enqueue values for CloseIncomeStatementRequestPageHandler. @@ -2438,6 +2615,19 @@ codeunit 134982 "ERM Financial Reports" Report.Run(Report::"Close Income Statement"); end; + local procedure RunCloseIncomeStatementWithRetainedEarnings(GenJournalLine: Record "Gen. Journal Line"; PostingDate: Date; DocumentNo: Code[20]; RetainedEarningsAccNo: Code[20]) + begin + // Enqueue values for CloseIncomeStatementWithRetainedEarningsRequestPageHandler. + LibraryVariableStorage.Enqueue(PostingDate); + LibraryVariableStorage.Enqueue(GenJournalLine."Journal Template Name"); + LibraryVariableStorage.Enqueue(GenJournalLine."Journal Batch Name"); + LibraryVariableStorage.Enqueue(DocumentNo); + LibraryVariableStorage.Enqueue(RetainedEarningsAccNo); + + Commit(); // commit requires to run report. + Report.Run(Report::"Close Income Statement"); + end; + local procedure ExecuteUIHandler() begin // Generate Dummy message. Required for executing the test case successfully in ES. @@ -2779,6 +2969,19 @@ codeunit 134982 "ERM Financial Reports" CloseIncomeStatement.OK().Invoke(); end; + [RequestPageHandler] + procedure CloseIncomeStatementWithRetainedEarningsRequestPageHandler(var CloseIncomeStatement: TestRequestPage "Close Income Statement") + begin + // Same as CloseIncomeStatementRequestPageHandler, but supplies a Retained Earnings Account, which + // report 94 requires when an Additional Reporting Currency is set. Dimensions are left blank. + CloseIncomeStatement.FiscalYearEndingDate.SetValue(LibraryVariableStorage.DequeueDate()); // Fiscal Year Ending Date + CloseIncomeStatement.GenJournalTemplate.SetValue(LibraryVariableStorage.DequeueText()); // Gen. Journal Template + CloseIncomeStatement.GenJournalBatch.SetValue(LibraryVariableStorage.DequeueText()); // Gen. Journal Batch + CloseIncomeStatement.DocumentNo.SetValue(LibraryVariableStorage.DequeueText()); // Document No. + CloseIncomeStatement.RetainedEarningsAcc.SetValue(LibraryVariableStorage.DequeueText()); // Retained Earnings Acc. + CloseIncomeStatement.OK().Invoke(); + end; + [RequestPageHandler] procedure AuditTrailReportRequestPageHandler(var AuditTrail: TestRequestPage "Audit Trail") begin