diff --git a/src/Apps/W1/ExcelReports/App/src/Financials/EXRFixedAssetProjected.Report.al b/src/Apps/W1/ExcelReports/App/src/Financials/EXRFixedAssetProjected.Report.al index c92a0db538..345ff0f504 100644 --- a/src/Apps/W1/ExcelReports/App/src/Financials/EXRFixedAssetProjected.Report.al +++ b/src/Apps/W1/ExcelReports/App/src/Financials/EXRFixedAssetProjected.Report.al @@ -336,17 +336,17 @@ report 4413 "EXR Fixed Asset Projected" DaysInPeriod := 0; end; - if ProjectionDate = EndCurrentFiscalYear then begin - EntryAmounts[3] := 0; - UpdateToNextFiscalYearEndDate(DepreciationBook, EndCurrentFiscalYear); - end; - if AssetWasDepreciated then begin AccumulateProjectionEntryAmounts(DepreciationAmount, Custom1Amount, EntryAmounts); if ProjectDisposal then CalculateDisposal.CalcGainLoss(FixedAssetNo, DepreciationBookCode, EntryAmounts); end; + if ProjectionDate = EndCurrentFiscalYear then begin + EntryAmounts[3] := 0; + UpdateToNextFiscalYearEndDate(DepreciationBook, EndCurrentFiscalYear); + end; + InsertProjectedFixedAssetLedgerEntry(ProjectionDate, FixedAssetNo, DepreciationAmount, NumberOfDays, TempFixedAssetLedgerEntry); LastProjectionInserted := ProjectionDate >= ProjectionsEnd; diff --git a/src/Apps/W1/ExcelReports/Test/src/FixedAssetExcelReports.Codeunit.al b/src/Apps/W1/ExcelReports/Test/src/FixedAssetExcelReports.Codeunit.al index 024e6d6836..3a2ef0a1a3 100644 --- a/src/Apps/W1/ExcelReports/Test/src/FixedAssetExcelReports.Codeunit.al +++ b/src/Apps/W1/ExcelReports/Test/src/FixedAssetExcelReports.Codeunit.al @@ -6,9 +6,13 @@ namespace Microsoft.Finance.ExcelReports.Test; using Microsoft.Finance.ExcelReports; +using Microsoft.Finance.GeneralLedger.Setup; using Microsoft.FixedAssets.Depreciation; using Microsoft.FixedAssets.FixedAsset; +using Microsoft.FixedAssets.Journal; using Microsoft.FixedAssets.Posting; +using Microsoft.FixedAssets.Setup; +using Microsoft.Foundation.Period; using System.TestLibraries.Utilities; codeunit 139545 "Fixed Asset Excel Reports" @@ -17,11 +21,22 @@ codeunit 139545 "Fixed Asset Excel Reports" RequiredTestIsolation = Disabled; TestPermissions = Disabled; + trigger OnRun() + begin + isInitialized := false; + end; + var + LibraryERM: Codeunit "Library - ERM"; LibraryFixedAsset: Codeunit "Library - Fixed Asset"; + LibraryRandom: Codeunit "Library - Random"; LibraryReportDataset: Codeunit "Library - Report Dataset"; + LibrarySetupStorage: Codeunit "Library - Setup Storage"; + LibraryTestInitialize: Codeunit "Library - Test Initialize"; LibraryVariableStorage: Codeunit "Library - Variable Storage"; Assert: Codeunit Assert; + isInitialized: Boolean; + ProjectedDeprMismatchLbl: Label 'Projected depreciation should be calculated from the latest closing book value.'; [Test] [HandlerFunctions('EXRFixedAssetAnalysisExcelHandler')] @@ -80,6 +95,124 @@ codeunit 139545 "Fixed Asset Excel Reports" Assert.AreEqual(FADepreciationBook."Acquisition Date", ReportAcquisitionDate, 'Acquisition date of first fixed asset should match the one in the depreciation book'); end; + [Test] + [HandlerFunctions('EXRFixedAssetProjectedHandler')] + procedure ProjectedValueDeclBalShouldUseCorrectBookValueAcrossFiscalYear() + var + AccountingPeriod: Record "Accounting Period"; + DepreciationBook: Record "Depreciation Book"; + FADepreciationBook: Record "FA Depreciation Book"; + FAJournalSetup: Record "FA Journal Setup"; + FAJournalTemplate: Record "FA Journal Template"; + FASetup: Record "FA Setup"; + FixedAsset: Record "Fixed Asset"; + AcquisitionAmount: Decimal; + BookValueAfterDepr: Decimal; + DecliningBalancePct: Decimal; + ExpectedProjectedDepr: Decimal; + MonthlyDeprAmount: Decimal; + ReportAmount: Decimal; + I: Integer; + AcquisitionDate: Date; + BasePostingDate: Date; + DeprStartDate: Date; + FirstReportDeprDate: Date; + FiscalYearStartDate: Date; + LastReportDeprDate: Date; + PostingDate: Date; + Variant: Variant; + begin + // [SCENARIO 631253] Report 4413 "Fixed Asset Projected Value (Excel)" should calculate depreciation + // using the last posted month's book value when crossing a fiscal year boundary with Declining-Balance 1. + // Previously it incorrectly used the penultimate month's book value. + Initialize(); + + // [GIVEN] Create Accounting periods with fiscal year. + CleanupFixedAssetData(); + AccountingPeriod.DeleteAll(); + FiscalYearStartDate := DMY2Date(1, 9, 2025); + CreateMonthlyAccountingPeriods(FiscalYearStartDate, 24); + + // [GIVEN] Create a depreciation book. + LibraryFixedAsset.CreateDepreciationBook(DepreciationBook); + ModifyDepreciationBook(DepreciationBook); + + FASetup.Get(); + FASetup.Validate("Default Depr. Book", DepreciationBook.Code); + FASetup.Modify(true); + + LibraryFixedAsset.CreateFAJournalSetup(FAJournalSetup, DepreciationBook.Code, ''); + SetupFAJournalSetup(FAJournalSetup); + + // [GIVEN] Create a fixed asset with Declining-Balance 1 and randomized amount/% values. + AcquisitionDate := DMY2Date(1, 1, 2025); + DeprStartDate := AcquisitionDate; + AcquisitionAmount := 10000 + Round(LibraryRandom.RandDec(90000, 0), 1); + DecliningBalancePct := 10 + Round(LibraryRandom.RandDec(20, 0), 1); + + LibraryFixedAsset.CreateFAWithPostingGroup(FixedAsset); + LibraryFixedAsset.CreateFADepreciationBook(FADepreciationBook, FixedAsset."No.", DepreciationBook.Code); + FADepreciationBook.Validate("FA Posting Group", FixedAsset."FA Posting Group"); + FADepreciationBook.Validate("Depreciation Starting Date", DeprStartDate); + FADepreciationBook.Validate("Depreciation Method", FADepreciationBook."Depreciation Method"::"Declining-Balance 1"); + FADepreciationBook.Validate("Declining-Balance %", DecliningBalancePct); + FADepreciationBook.Modify(true); + + // [GIVEN] Post acquisition on the randomized acquisition date. + LibraryFixedAsset.CreateJournalTemplate(FAJournalTemplate); + CreateAndPostFAJournalLine( + FAJournalTemplate.Name, FixedAsset."No.", AcquisitionDate, + "FA Journal Line FA Posting Type"::"Acquisition Cost", + DepreciationBook.Code, AcquisitionAmount, FixedAsset."No."); + + // [GIVEN] Post depreciation starting from the acquisition month. + BasePostingDate := CalcDate('', AcquisitionDate); + BookValueAfterDepr := AcquisitionAmount; + for I := 1 to 8 do begin + MonthlyDeprAmount := Round(BookValueAfterDepr * DecliningBalancePct / 100 / 12, 1); + PostingDate := CalcDate('<' + Format(I - 1) + 'M>', BasePostingDate); + + CreateAndPostFAJournalLine( + FAJournalTemplate.Name, FixedAsset."No.", PostingDate, + "FA Journal Line FA Posting Type"::Depreciation, + DepreciationBook.Code, -MonthlyDeprAmount, CopyStr(FixedAsset."No." + '-' + Format(I), 1, MaxStrLen(FixedAsset."No."))); + + BookValueAfterDepr -= MonthlyDeprAmount; + end; + + // The next projected month should be based on this closing book value. + ExpectedProjectedDepr := -Round(BookValueAfterDepr * DecliningBalancePct / 100 / 12, 1); + Commit(); + + // [WHEN] Running the Fixed Asset Projected Value (Excel) report. + FirstReportDeprDate := CalcDate('', CalcDate('<8M>', BasePostingDate)); + LastReportDeprDate := CalcDate('<11M>', FirstReportDeprDate); + RunFixedAssetProjectedReport(DepreciationBook.Code, FirstReportDeprDate, LastReportDeprDate, false); + + // [THEN] Verify the projected depreciation in the first projected month uses the latest closing book value. + LibraryReportDataset.SetXmlNodeList('DataItem[@name="FixedAssetLedgerEntries"]'); + LibraryReportDataset.GetNextRow(); + LibraryReportDataset.FindCurrentRowValue('Amount', Variant); + ReportAmount := Variant; + ReportAmount := Round(ReportAmount, 1); + Assert.AreEqual(ExpectedProjectedDepr, ReportAmount, ProjectedDeprMismatchLbl); + end; + + local procedure Initialize() + begin + LibraryTestInitialize.OnTestInitialize(Codeunit::"Fixed Asset Excel Reports"); + LibrarySetupStorage.Restore(); + if isInitialized then + exit; + LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Fixed Asset Excel Reports"); + + EnsureGeneralPostingSetup(); + LibrarySetupStorage.Save(Database::"FA Setup"); + isInitialized := true; + Commit(); + LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Fixed Asset Excel Reports"); + end; + local procedure CleanupFixedAssetData() var FAPostingType: Record "FA Posting Type"; @@ -89,6 +222,91 @@ codeunit 139545 "Fixed Asset Excel Reports" FixedAsset.DeleteAll(); end; + local procedure CreateMonthlyAccountingPeriods(FiscalYearStart: Date; NumberOfMonths: Integer) + var + AccountingPeriod: Record "Accounting Period"; + PeriodStart: Date; + I: Integer; + begin + // Create a fiscal year starting at FiscalYearStart with monthly periods + // Also create the prior fiscal year. + PeriodStart := CalcDate('<-1Y>', FiscalYearStart); + AccountingPeriod.Init(); + AccountingPeriod."Starting Date" := PeriodStart; + AccountingPeriod."New Fiscal Year" := true; + AccountingPeriod.Insert(); + for I := 1 to 11 do begin + PeriodStart := CalcDate('<1M>', PeriodStart); + AccountingPeriod.Init(); + AccountingPeriod."Starting Date" := PeriodStart; + AccountingPeriod."New Fiscal Year" := false; + AccountingPeriod.Insert(); + end; + + // Create the target fiscal year and its periods. + for I := 0 to NumberOfMonths - 1 do begin + PeriodStart := CalcDate('<' + Format(I) + 'M>', FiscalYearStart); + AccountingPeriod.Init(); + AccountingPeriod."Starting Date" := PeriodStart; + AccountingPeriod."New Fiscal Year" := (I = 0); + AccountingPeriod.Insert(); + end; + end; + + local procedure SetupFAJournalSetup(var FAJournalSetup: Record "FA Journal Setup") + var + DefaultFAJournalSetup: Record "FA Journal Setup"; + begin + DefaultFAJournalSetup.SetRange("Depreciation Book Code", LibraryFixedAsset.GetDefaultDeprBook()); + DefaultFAJournalSetup.FindFirst(); + FAJournalSetup.TransferFields(DefaultFAJournalSetup, false); + FAJournalSetup.Modify(true); + end; + + local procedure CreateAndPostFAJournalLine(FAJournalTemplateName: Code[10]; FANo: Code[20]; PostingDate: Date; FAPostingType: Enum "FA Journal Line FA Posting Type"; DepreciationBookCode: Code[10]; Amount: Decimal; DocumentNo: Code[20]) + var + FAJournalBatch: Record "FA Journal Batch"; + FAJournalLine: Record "FA Journal Line"; + begin + LibraryFixedAsset.CreateFAJournalBatch(FAJournalBatch, FAJournalTemplateName); + LibraryFixedAsset.CreateFAJournalLine(FAJournalLine, FAJournalTemplateName, FAJournalBatch.Name); + FAJournalLine.Validate("FA No.", FANo); + FAJournalLine.Validate("FA Posting Date", PostingDate); + FAJournalLine.Validate("Posting Date", PostingDate); + FAJournalLine.Validate("FA Posting Type", FAPostingType); + FAJournalLine.Validate("Depreciation Book Code", DepreciationBookCode); + FAJournalLine.Validate(Amount, Amount); + FAJournalLine.Validate("Document No.", DocumentNo); + FAJournalLine.Modify(true); + + LibraryFixedAsset.PostFAJournalLine(FAJournalLine); + end; + + local procedure RunFixedAssetProjectedReport(DepreciationBookCode: Code[10]; FirstDeprDate: Date; LastDeprDate: Date; UseAccountingPeriod: Boolean) + var + Variant: Variant; + RequestPageXml: Text; + begin + LibraryVariableStorage.Enqueue(DepreciationBookCode); + LibraryVariableStorage.Enqueue(FirstDeprDate); + LibraryVariableStorage.Enqueue(LastDeprDate); + LibraryVariableStorage.Enqueue(UseAccountingPeriod); + RequestPageXml := Report.RunRequestPage(Report::"EXR Fixed Asset Projected", RequestPageXml); + LibraryReportDataset.RunReportAndLoad(Report::"EXR Fixed Asset Projected", Variant, RequestPageXml); + end; + + local procedure EnsureGeneralPostingSetup() + var + GeneralPostingSetup: Record "General Posting Setup"; + begin + GeneralPostingSetup.SetFilter("Gen. Bus. Posting Group", '<>%1', ''); + GeneralPostingSetup.SetFilter("Gen. Prod. Posting Group", '<>%1', ''); + if not GeneralPostingSetup.IsEmpty() then + exit; + + LibraryERM.CreateGeneralPostingSetupInvt(GeneralPostingSetup); + end; + [RequestPageHandler] procedure EXRFixedAssetAnalysisExcelHandler(var EXRFixedAssetAnalysisExcel: TestRequestPage "EXR Fixed Asset Analysis Excel") var @@ -101,4 +319,30 @@ codeunit 139545 "Fixed Asset Excel Reports" EXRFixedAssetAnalysisExcel.OK().Invoke(); end; -} + local procedure ModifyDepreciationBook(var DepreciationBook: Record "Depreciation Book") + begin + DepreciationBook.Validate("G/L Integration - Acq. Cost", false); + DepreciationBook.Validate("G/L Integration - Depreciation", false); + DepreciationBook.Modify(true); + end; + + [RequestPageHandler] + procedure EXRFixedAssetProjectedHandler(var EXRFixedAssetProjected: TestRequestPage "EXR Fixed Asset Projected") + var + DepreciationBookCode: Code[10]; + FirstDeprDate: Date; + LastDeprDate: Date; + UseAccountingPeriod: Boolean; + begin + DepreciationBookCode := CopyStr(LibraryVariableStorage.DequeueText(), 1, 10); + FirstDeprDate := LibraryVariableStorage.DequeueDate(); + LastDeprDate := LibraryVariableStorage.DequeueDate(); + UseAccountingPeriod := LibraryVariableStorage.DequeueBoolean(); + + EXRFixedAssetProjected.DepreciationBookCodeField.SetValue(DepreciationBookCode); + EXRFixedAssetProjected.FirstDepreciationDateField.SetValue(FirstDeprDate); + EXRFixedAssetProjected.SecondDepreciationDateField.SetValue(LastDeprDate); + EXRFixedAssetProjected.UseAccountingPeriodField.SetValue(UseAccountingPeriod); + EXRFixedAssetProjected.OK().Invoke(); + end; +} \ No newline at end of file