diff --git a/src/Apps/W1/Sustainability/app/src/Posting/SustPreviewPostInstance.Codeunit.al b/src/Apps/W1/Sustainability/app/src/Posting/SustPreviewPostInstance.Codeunit.al index aa8a0e5f5c2..5fb5c95c307 100644 --- a/src/Apps/W1/Sustainability/app/src/Posting/SustPreviewPostInstance.Codeunit.al +++ b/src/Apps/W1/Sustainability/app/src/Posting/SustPreviewPostInstance.Codeunit.al @@ -52,9 +52,14 @@ codeunit 6233 "Sust. Preview Post Instance" if SustLedgEntry.IsTemporary() then exit; + if NextSustLedgerPreviewEntryNo = 0 then + NextSustLedgerPreviewEntryNo := -2000000000; + TempSustLedgEntry := SustLedgEntry; + TempSustLedgEntry."Entry No." := NextSustLedgerPreviewEntryNo; TempSustLedgEntry."Document No." := '***'; TempSustLedgEntry.Insert(); + NextSustLedgerPreviewEntryNo += 1; HasSustainabilityEntry := true; end; @@ -87,11 +92,14 @@ codeunit 6233 "Sust. Preview Post Instance" TempSustValueEntry.Reset(); TempSustValueEntry.DeleteAll(); + + NextSustLedgerPreviewEntryNo := -2000000000; end; var TempSustLedgEntry: Record "Sustainability Ledger Entry" temporary; TempSustValueEntry: Record "Sustainability Value Entry" temporary; + NextSustLedgerPreviewEntryNo: Integer; HasSustainabilityEntry: Boolean; HasSustainabilityValueEntry: Boolean; } diff --git a/src/Apps/W1/Sustainability/app/src/Posting/SustPreviewPostingHandler.Codeunit.al b/src/Apps/W1/Sustainability/app/src/Posting/SustPreviewPostingHandler.Codeunit.al index 5572baf6b1f..30fed595077 100644 --- a/src/Apps/W1/Sustainability/app/src/Posting/SustPreviewPostingHandler.Codeunit.al +++ b/src/Apps/W1/Sustainability/app/src/Posting/SustPreviewPostingHandler.Codeunit.al @@ -7,6 +7,17 @@ codeunit 6228 "Sust. Preview Posting Handler" EventSubscriberInstance = Manual; SingleInstance = true; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sustainability Post Mgt", 'OnInsertLedgerEntryOnBeforeInsert', '', false, false)] + local procedure OnInsertLedgerEntryOnBeforeInsert(var SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; var IsHandled: Boolean) + var + SustPreviewPostInstance: Codeunit "Sust. Preview Post Instance"; + begin + if IsHandled then + exit; + SustPreviewPostInstance.InsertSustLedgEntry(SustainabilityLedgerEntry, true); + IsHandled := true; + end; + [EventSubscriber(ObjectType::Table, Database::"Sustainability Ledger Entry", 'OnAfterInsertEvent', '', false, false)] local procedure OnInsertSustLedgEntry(var Rec: Record "Sustainability Ledger Entry"; RunTrigger: Boolean) var diff --git a/src/Apps/W1/Sustainability/app/src/Posting/SustainabilityPostMgt.Codeunit.al b/src/Apps/W1/Sustainability/app/src/Posting/SustainabilityPostMgt.Codeunit.al index d69d2b0caa4..9e840d4de70 100644 --- a/src/Apps/W1/Sustainability/app/src/Posting/SustainabilityPostMgt.Codeunit.al +++ b/src/Apps/W1/Sustainability/app/src/Posting/SustainabilityPostMgt.Codeunit.al @@ -22,6 +22,7 @@ codeunit 6212 "Sustainability Post Mgt" var SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; FeatureTelemetry: Codeunit "Feature Telemetry"; + IsHandled: Boolean; SustainabilityLedgerEntryAddedLbl: Label 'Sustainability Ledger Entry Added', Locked = true; begin SustainabilityLedgerEntry.Init(); @@ -42,7 +43,11 @@ codeunit 6212 "Sustainability Post Mgt" UpdateCarbonFeeEmission(SustainabilityLedgerEntry); OnBeforeInsertSustainabilityLedgerEntry(SustainabilityLedgerEntry, SustainabilityJnlLine); - SustainabilityLedgerEntry.Insert(true); + + IsHandled := false; + OnInsertLedgerEntryOnBeforeInsert(SustainabilityLedgerEntry, IsHandled); + if not IsHandled then + SustainabilityLedgerEntry.Insert(true); end; procedure InsertValueEntry(SustainabilityJnlLine: Record "Sustainability Jnl. Line"; ValueEntry: Record "Value Entry"; ItemLedgerEntry: Record "Item Ledger Entry") @@ -520,4 +525,9 @@ codeunit 6212 "Sustainability Post Mgt" local procedure OnBeforeInsertSustainabilityLedgerEntry(var SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; SustainabilityJnlLine: Record "Sustainability Jnl. Line") begin end; + + [InternalEvent(false, false)] + local procedure OnInsertLedgerEntryOnBeforeInsert(var SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; var IsHandled: Boolean) + begin + end; } \ No newline at end of file diff --git a/src/Apps/W1/Sustainability/test/src/SustGeneralJournalTest.Codeunit.al b/src/Apps/W1/Sustainability/test/src/SustGeneralJournalTest.Codeunit.al index c04911e5c1c..bbf98fc0f4d 100644 --- a/src/Apps/W1/Sustainability/test/src/SustGeneralJournalTest.Codeunit.al +++ b/src/Apps/W1/Sustainability/test/src/SustGeneralJournalTest.Codeunit.al @@ -1,6 +1,7 @@ namespace Microsoft.Test.Sustainability; using Microsoft.Bank.BankAccount; +using Microsoft.Finance.GeneralLedger.Account; using Microsoft.Finance.GeneralLedger.Journal; using Microsoft.Finance.GeneralLedger.Posting; using Microsoft.Finance.GeneralLedger.Preview; @@ -867,6 +868,185 @@ codeunit 148188 "Sust. General Journal Test" Navigate.Run(); end; + [Test] + [HandlerFunctions('GLPostingPreviewSingleEntryHandler')] + procedure VerifyPreviewPostingOfGenJournalDoesNotConsumeSustainabilityLedgerEntryNo() + var + SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; + SustainabilityAccount: Record "Sustainability Account"; + GenJournalTemplate: Record "Gen. Journal Template"; + GenJournalBatch: Record "Gen. Journal Batch"; + BaselineGenJournalLine: Record "Gen. Journal Line"; + GenJournalLine: Record "Gen. Journal Line"; + BankAccount: Record "Bank Account"; + GLAccount: Record "G/L Account"; + Vendor: Record Vendor; + GenJnlPost: Codeunit "Gen. Jnl.-Post"; + CategoryCode: Code[20]; + SubcategoryCode: Code[20]; + AccountCode: Code[20]; + BaselineEntryNo: Integer; + Index: Integer; + EmissionCO2: Decimal; + EmissionCH4: Decimal; + EmissionN2O: Decimal; + begin + // [SCENARIO 640599] Preview Posting of a General Journal Line must not consume the Sustainability Ledger Entry identity. + LibrarySustainability.CleanUpBeforeTesting(); + + // [GIVEN] Create a Sustainability Account. + CreateSustainabilityAccount(AccountCode, CategoryCode, SubcategoryCode, LibraryRandom.RandInt(10)); + SustainabilityAccount.Get(AccountCode); + + // [GIVEN] Generate Emission. + EmissionCO2 := LibraryRandom.RandInt(20); + EmissionCH4 := LibraryRandom.RandInt(5); + EmissionN2O := LibraryRandom.RandInt(5); + + // [GIVEN] Create a Bank Account whose posting group has a G/L account so the line can post. + LibraryERM.CreateGLAccount(GLAccount); + LibraryERM.CreateBankAccount(BankAccount, GLAccount); + + // [GIVEN] Create a Vendor. + LibraryPurchase.CreateVendor(Vendor); + + // [GIVEN] Create a Gen Journal Template. + LibraryERM.CreateGenJournalTemplate(GenJournalTemplate); + + // [GIVEN] Create a Gen Journal Batch. + LibraryERM.CreateGenJournalBatch(GenJournalBatch, GenJournalTemplate.Name); + + // [GIVEN] Post a baseline General Journal Line to observe the committed Sustainability Ledger Entry identity. + CreateGenJournalLineWithEmission( + BaselineGenJournalLine, GenJournalBatch, Vendor."No.", BankAccount."No.", SustainabilityAccount."No.", + EmissionCO2, EmissionCH4, EmissionN2O); + LibraryERM.PostGeneralJnlLine(BaselineGenJournalLine); + + // [GIVEN] Record the committed baseline Entry No. + SustainabilityLedgerEntry.SetRange("Document No.", BaselineGenJournalLine."Document No."); + SustainabilityLedgerEntry.FindLast(); + BaselineEntryNo := SustainabilityLedgerEntry."Entry No."; + + // [GIVEN] Prepare a single General Journal Line with Sustainability emissions. + CreateGenJournalLineWithEmission( + GenJournalLine, GenJournalBatch, Vendor."No.", BankAccount."No.", SustainabilityAccount."No.", + EmissionCO2, EmissionCH4, EmissionN2O); + + // [GIVEN] Save a transaction. + Commit(); + + // [WHEN] Preview the General Journal Line three times. + GenJournalLine.SetRange("Journal Template Name", GenJournalBatch."Journal Template Name"); + GenJournalLine.SetRange("Journal Batch Name", GenJournalBatch.Name); + for Index := 1 to 3 do begin + asserterror GenJnlPost.Preview(GenJournalLine); + Assert.ExpectedError(''); + end; + + // [WHEN] Post the General Journal Line. + LibraryERM.PostGeneralJnlLine(GenJournalLine); + + // [THEN] The committed Sustainability Ledger Entry equals the baseline plus one, proving the three previews consumed no identity. + SustainabilityLedgerEntry.Reset(); + SustainabilityLedgerEntry.SetRange("Document No.", GenJournalLine."Document No."); + SustainabilityLedgerEntry.FindLast(); + Assert.AreEqual( + BaselineEntryNo + 1, + SustainabilityLedgerEntry."Entry No.", + StrSubstNo(ValueMustBeEqualErr, SustainabilityLedgerEntry.FieldCaption("Entry No."), BaselineEntryNo + 1, SustainabilityLedgerEntry.TableCaption())); + end; + + [Test] + [HandlerFunctions('GLPostingPreviewResetKeyDrillDownHandler')] + procedure VerifyRepeatedGenJournalPreviewResetsNegativeTemporaryKeys() + var + SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; + SustainabilityAccount: Record "Sustainability Account"; + GenJournalTemplate: Record "Gen. Journal Template"; + GenJournalBatch: Record "Gen. Journal Batch"; + GenJournalLine: array[2] of Record "Gen. Journal Line"; + BankAccount: Record "Bank Account"; + GLAccount: Record "G/L Account"; + Vendor: Record Vendor; + GenJnlPost: Codeunit "Gen. Jnl.-Post"; + CategoryCode: Code[20]; + SubcategoryCode: Code[20]; + AccountCode: Code[20]; + Index: Integer; + EmissionCO2: Decimal; + EmissionCH4: Decimal; + EmissionN2O: Decimal; + begin + // [SCENARIO 640599] Every repeated General Journal preview reuses the same reset pair of negative temporary Entry No. values. + LibrarySustainability.CleanUpBeforeTesting(); + + // [GIVEN] Create a Sustainability Account. + CreateSustainabilityAccount(AccountCode, CategoryCode, SubcategoryCode, LibraryRandom.RandInt(10)); + SustainabilityAccount.Get(AccountCode); + + // [GIVEN] Generate Emission. + EmissionCO2 := LibraryRandom.RandInt(20); + EmissionCH4 := LibraryRandom.RandInt(5); + EmissionN2O := LibraryRandom.RandInt(5); + + // [GIVEN] Create a Bank Account whose posting group has a G/L account so the line can post. + LibraryERM.CreateGLAccount(GLAccount); + LibraryERM.CreateBankAccount(BankAccount, GLAccount); + + // [GIVEN] Create a Vendor. + LibraryPurchase.CreateVendor(Vendor); + + // [GIVEN] Create a Gen Journal Template. + LibraryERM.CreateGenJournalTemplate(GenJournalTemplate); + + // [GIVEN] Create a Gen Journal Batch. + LibraryERM.CreateGenJournalBatch(GenJournalBatch, GenJournalTemplate.Name); + + // [GIVEN] Prepare two General Journal Lines each producing a preview Sustainability Ledger Entry. + CreateGenJournalLineWithEmission( + GenJournalLine[1], GenJournalBatch, Vendor."No.", BankAccount."No.", SustainabilityAccount."No.", + EmissionCO2, EmissionCH4, EmissionN2O); + CreateGenJournalLineWithEmission( + GenJournalLine[2], GenJournalBatch, Vendor."No.", BankAccount."No.", SustainabilityAccount."No.", + EmissionCO2, EmissionCH4, EmissionN2O); + + // [GIVEN] Save a transaction. + Commit(); + + // [WHEN] Preview the General Journal Lines multiple times. + GenJournalLine[1].SetRange("Journal Template Name", GenJournalBatch."Journal Template Name"); + GenJournalLine[1].SetRange("Journal Batch Name", GenJournalBatch.Name); + for Index := 1 to 2 do begin + // [THEN] The drilldown handler asserts the same reset key pair (-1999999999 then -2000000000) on every preview run. + asserterror GenJnlPost.Preview(GenJournalLine[1]); + Assert.ExpectedError(''); + end; + + // [THEN] No physical preview Sustainability Ledger Entry persists in the real table. + SustainabilityLedgerEntry.Reset(); + Assert.RecordIsEmpty(SustainabilityLedgerEntry); + end; + + local procedure CreateGenJournalLineWithEmission(var GenJournalLine: Record "Gen. Journal Line"; GenJournalBatch: Record "Gen. Journal Batch"; VendorNo: Code[20]; BankAccountNo: Code[20]; SustAccountNo: Code[20]; EmissionCO2: Decimal; EmissionCH4: Decimal; EmissionN2O: Decimal) + begin + LibraryERM.CreateGeneralJnlLine( + GenJournalLine, + GenJournalBatch."Journal Template Name", + GenJournalBatch.Name, + GenJournalLine."Document Type"::Invoice, + GenJournalLine."Account Type"::Vendor, + VendorNo, + -LibraryRandom.RandIntInRange(100, 200)); + + GenJournalLine.Validate("Bal. Account Type", GenJournalLine."Bal. Account Type"::"Bank Account"); + GenJournalLine.Validate("Bal. Account No.", BankAccountNo); + GenJournalLine.Validate("Sust. Account No.", SustAccountNo); + GenJournalLine.Validate("Total Emission CH4", EmissionCH4); + GenJournalLine.Validate("Total Emission N2O", EmissionN2O); + GenJournalLine.Validate("Total Emission CO2", EmissionCO2); + GenJournalLine.Modify(true); + end; + local procedure CreateSustainabilityAccount(var AccountCode: Code[20]; var CategoryCode: Code[20]; var SubcategoryCode: Code[20]; i: Integer): Record "Sustainability Account" begin CreateSustainabilitySubcategory(CategoryCode, SubcategoryCode, i); @@ -901,6 +1081,35 @@ codeunit 148188 "Sust. General Journal Test" GLPostingPreview.OK().Invoke(); end; + [PageHandler] + procedure GLPostingPreviewSingleEntryHandler(var GLPostingPreview: TestPage "G/L Posting Preview") + begin + GLPostingPreview.Filter.SetFilter("Table ID", Format(Database::"Sustainability Ledger Entry")); + GLPostingPreview."No. of Records".AssertEquals(1); + GLPostingPreview.OK().Invoke(); + end; + + [PageHandler] + procedure GLPostingPreviewResetKeyDrillDownHandler(var GLPostingPreview: TestPage "G/L Posting Preview") + var + SustainabilityLedgerEntries: TestPage "Sustainability Ledger Entries"; + begin + GLPostingPreview.Filter.SetFilter("Table ID", Format(Database::"Sustainability Ledger Entry")); + GLPostingPreview."No. of Records".AssertEquals(2); + + // Drill down to the temporary preview Sustainability Ledger Entries page (descending Entry No. order). + SustainabilityLedgerEntries.Trap(); + GLPostingPreview."No. of Records".DrillDown(); + + SustainabilityLedgerEntries.First(); + SustainabilityLedgerEntries."Entry No.".AssertEquals(-1999999999); + SustainabilityLedgerEntries.Next(); + SustainabilityLedgerEntries."Entry No.".AssertEquals(-2000000000); + SustainabilityLedgerEntries.Close(); + + GLPostingPreview.OK().Invoke(); + end; + [PageHandler] [Scope('OnPrem')] procedure NavigateFindEntriesHandler(var Navigate: TestPage Navigate) diff --git a/src/Apps/W1/Sustainability/test/src/SustPreviewTestSubscriber.Codeunit.al b/src/Apps/W1/Sustainability/test/src/SustPreviewTestSubscriber.Codeunit.al new file mode 100644 index 00000000000..5a5f4b05998 --- /dev/null +++ b/src/Apps/W1/Sustainability/test/src/SustPreviewTestSubscriber.Codeunit.al @@ -0,0 +1,31 @@ +namespace Microsoft.Test.Sustainability; + +using Microsoft.Finance.GeneralLedger.Preview; +using Microsoft.Sustainability.Journal; +using Microsoft.Sustainability.Posting; + +codeunit 148230 "Sust Preview Test Subscriber" +{ + EventSubscriberInstance = Manual; + TableNo = "Sustainability Jnl. Line"; + + trigger OnRun() + var + SustainabilityPostMgt: Codeunit "Sustainability Post Mgt"; + GenJnlPostPreview: Codeunit "Gen. Jnl.-Post Preview"; + begin + SustainabilityPostMgt.InsertLedgerEntry(Rec); + GenJnlPostPreview.ThrowError(); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Preview", 'OnRunPreview', '', false, false)] + local procedure OnRunPreview(var Result: Boolean; Subscriber: Variant; RecVar: Variant) + var + SustainabilityJnlLine: Record "Sustainability Jnl. Line"; + SustPreviewTestSubscriber: Codeunit "Sust Preview Test Subscriber"; + begin + SustPreviewTestSubscriber := Subscriber; + SustainabilityJnlLine.Copy(RecVar); + Result := SustPreviewTestSubscriber.Run(SustainabilityJnlLine); + end; +} diff --git a/src/Apps/W1/Sustainability/test/src/SustValueChainFixedAsset.Codeunit.al b/src/Apps/W1/Sustainability/test/src/SustValueChainFixedAsset.Codeunit.al index f71c06be4a8..da5339bd8ae 100644 --- a/src/Apps/W1/Sustainability/test/src/SustValueChainFixedAsset.Codeunit.al +++ b/src/Apps/W1/Sustainability/test/src/SustValueChainFixedAsset.Codeunit.al @@ -2,6 +2,7 @@ namespace Microsoft.Test.Sustainability; using Microsoft.Finance.GeneralLedger.Account; using Microsoft.Finance.GeneralLedger.Journal; +using Microsoft.Finance.GeneralLedger.Preview; using Microsoft.FixedAssets.Depreciation; using Microsoft.FixedAssets.FixedAsset; using Microsoft.FixedAssets.Journal; @@ -925,6 +926,116 @@ codeunit 148219 "Sust. Value Chain Fixed Asset" Assert.RecordCount(SustainabilityLedgerEntry, 0); end; + [Test] + [HandlerFunctions('GLPostingPreviewPageHandler')] + procedure TestSustFAJournalPreviewDoesNotConsumeLedgerEntryNo() + var + FAJournalLine: Record "FA Journal Line"; + SustainabilityAccount: Record "Sustainability Account"; + DepreciationBook: Record "Depreciation Book"; + FixedAsset: Record "Fixed Asset"; + EmissionFee: array[3] of Record "Emission Fee"; + SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; + FAJnlPost: Codeunit "FA. Jnl.-Post"; + CategoryCode: Code[20]; + SubcategoryCode: Code[20]; + AccountCode: Code[20]; + BaselineEntryNo: Integer; + BaselineCO2eEmission: Decimal; + ExpectedCO2eEmission: Decimal; + begin + // [SCENARIO 580142] [Sustainability] - Value Chain: Fixed Assets (🌱) + // [SCENARIO] FA journal posting preview must not consume the Sustainability Ledger Entry identity. + Initialize(); + + // [GIVEN] Update "Enable Value Chain Tracking" in Sustainability Setup. + LibrarySustainability.UpdateValueChainTrackingInSustainabilitySetup(true); + + // [GIVEN] Create a Sustainability Account. + CreateSustainabilityAccount(AccountCode, CategoryCode, SubcategoryCode, LibraryRandom.RandInt(10)); + SustainabilityAccount.Get(AccountCode); + + // [GIVEN] Create Emission Fee With Emission Scope and Country/Region. + CreateEmissionFeeWithEmissionScope(EmissionFee, SustainabilityAccount."Emission Scope", ''); + + // [GIVEN] Save Expected CO2e Emission for the baseline and preview lines. + BaselineCO2eEmission := LibraryRandom.RandDecInRange(1, 2, 2); + ExpectedCO2eEmission := LibraryRandom.RandDecInRange(1, 2, 2); + + // [GIVEN] Create a Fixed Asset. + CreateFixedAssetSetup(DepreciationBook); + LibraryFixedAsset.CreateFAWithPostingGroup(FixedAsset); + CreateFADepreciationBook(FixedAsset."No.", DepreciationBook.Code, FixedAsset."FA Posting Group"); + UpdateIntegrationInBook(DepreciationBook, false, false, false, false, false, false, false); + + // [GIVEN] Update "Default Sust. Account" in a Fixed Asset. + FixedAsset.Validate("Default Sust. Account", SustainabilityAccount."No."); + FixedAsset.Modify(true); + + // [GIVEN] Post a first FA Journal Line to establish a committed baseline Sustainability Ledger Entry. + CreateFAJournalLine(FAJournalLine, FixedAsset."No.", DepreciationBook.Code); + FAJournalLine.Validate("Sust. Account No.", SustainabilityAccount."No."); + FAJournalLine.Validate("Total CO2e", BaselineCO2eEmission); + FAJournalLine.Modify(true); + LibraryFixedAsset.PostFAJournalLine(FAJournalLine); + Commit(); + + // [GIVEN] Record the committed baseline Sustainability Ledger Entry No. + SustainabilityLedgerEntry.SetRange("Account No.", AccountCode); + SustainabilityLedgerEntry.FindLast(); + BaselineEntryNo := SustainabilityLedgerEntry."Entry No."; + + // [GIVEN] Create a second FA Journal Line with Sustainability data. + Clear(FAJournalLine); + CreateFAJournalLine(FAJournalLine, FixedAsset."No.", DepreciationBook.Code); + FAJournalLine.Validate("Sust. Account No.", SustainabilityAccount."No."); + FAJournalLine.Validate("Total CO2e", ExpectedCO2eEmission); + FAJournalLine.Modify(true); + Commit(); + + // [WHEN] Preview the second FA Journal Line through FA Jnl.-Post.Preview. + FAJournalLine.SetRange("Journal Template Name", FAJournalLine."Journal Template Name"); + FAJournalLine.SetRange("Journal Batch Name", FAJournalLine."Journal Batch Name"); + asserterror FAJnlPost.Preview(FAJournalLine); + + // [THEN] No errors occured - preview mode error only. + Assert.ExpectedError(''); + + // [WHEN] Post the second FA Journal Line. + LibraryFixedAsset.PostFAJournalLine(FAJournalLine); + + // [THEN] The posted Sustainability Ledger Entry No. is baseline plus one, so preview consumed no positive identity. + SustainabilityLedgerEntry.Reset(); + SustainabilityLedgerEntry.SetRange("Account No.", AccountCode); + SustainabilityLedgerEntry.FindLast(); + Assert.AreEqual( + BaselineEntryNo + 1, + SustainabilityLedgerEntry."Entry No.", + StrSubstNo(ValueMustBeEqualErr, SustainabilityLedgerEntry.FieldCaption("Entry No."), BaselineEntryNo + 1, SustainabilityLedgerEntry.TableCaption())); + + // [THEN] Verify the Sustainability field values on the posted entry. + Assert.AreEqual( + 0, + SustainabilityLedgerEntry."Emission CO2", + StrSubstNo(ValueMustBeEqualErr, SustainabilityLedgerEntry.FieldCaption("Emission CO2"), 0, SustainabilityLedgerEntry.TableCaption())); + Assert.AreEqual( + 0, + SustainabilityLedgerEntry."Emission CH4", + StrSubstNo(ValueMustBeEqualErr, SustainabilityLedgerEntry.FieldCaption("Emission CH4"), 0, SustainabilityLedgerEntry.TableCaption())); + Assert.AreEqual( + 0, + SustainabilityLedgerEntry."Emission N2O", + StrSubstNo(ValueMustBeEqualErr, SustainabilityLedgerEntry.FieldCaption("Emission N2O"), 0, SustainabilityLedgerEntry.TableCaption())); + Assert.AreEqual( + ExpectedCO2eEmission, + SustainabilityLedgerEntry."CO2e Emission", + StrSubstNo(ValueMustBeEqualErr, SustainabilityLedgerEntry.FieldCaption("CO2e Emission"), 0, SustainabilityLedgerEntry.TableCaption())); + Assert.AreEqual( + 0, + SustainabilityLedgerEntry."Carbon Fee", + StrSubstNo(ValueMustBeEqualErr, SustainabilityLedgerEntry.FieldCaption("Carbon Fee"), 0, SustainabilityLedgerEntry.TableCaption())); + end; + local procedure Initialize() var LibraryERMCountryData: Codeunit "Library - ERM Country Data"; @@ -1203,4 +1314,9 @@ codeunit 148219 "Sust. Value Chain Fixed Asset" procedure MessageHandler(Message: Text[1024]) begin end; + + [PageHandler] + procedure GLPostingPreviewPageHandler(var GLPostingPreview: TestPage "G/L Posting Preview") + begin + end; } \ No newline at end of file diff --git a/src/Apps/W1/Sustainability/test/src/SustainabilityPostingTest.Codeunit.al b/src/Apps/W1/Sustainability/test/src/SustainabilityPostingTest.Codeunit.al index 22ab1542415..f0c427267f9 100644 --- a/src/Apps/W1/Sustainability/test/src/SustainabilityPostingTest.Codeunit.al +++ b/src/Apps/W1/Sustainability/test/src/SustainabilityPostingTest.Codeunit.al @@ -66,6 +66,11 @@ codeunit 148184 "Sustainability Posting Test" FieldShouldNotBeEditableErr: Label '%1 should not be editable in Page %2.', Comment = '%1 = Field Caption , %2 = Page Caption'; FieldShouldBeEditableErr: Label '%1 should be editable in Page %2.', Comment = '%1 = Field Caption , %2 = Page Caption'; ItemOrCategoryFilterErr: Label 'You must not use Item No. Filter and Item Category Filter at the same time.'; + EntryNoShouldBeBaselinePlusOneErr: Label 'The committed Entry No. should be the baseline Entry No. plus one.', Locked = true; + EntryNoShouldBePositiveErr: Label 'The committed Entry No. should be a positive SQL-assigned key.', Locked = true; + EntryNoShouldBeConsecutiveErr: Label 'The committed Entry Nos. should be consecutive.', Locked = true; + TwoLedgerEntriesExpectedErr: Label 'Exactly two Sustainability Ledger Entries should be posted.', Locked = true; + PreviewKeyShouldBeNegativeErr: Label 'The preview Sustainability Ledger Entry should use a negative temporary key.', Locked = true; [Test] procedure TestInformationIsTransferredToLedgerEntry() @@ -1875,7 +1880,7 @@ codeunit 148184 "Sustainability Posting Test" // [GIVEN] Update "Buy-from Country/Region Code" in Sustainability Journal Line. SustainabilityJournalLine.Validate("Document No.", SustainabilityJournalMgt.GetDocumentNo(false, SustainabilityJnlBatch, '', SustainabilityJournalLine."Posting Date")); - SustainabilityJournalLine.Validate(Description, LibraryRandom.RandText(10)); + SustainabilityJournalLine.Validate(Description, LibraryUtility.GenerateGUID()); SustainabilityJournalLine.Validate("Unit of Measure", UnitOfMeasure.Code); SustainabilityJournalLine.Validate("Fuel/Electricity", LibraryRandom.RandIntInRange(1, 1)); SustainabilityJournalLine.Validate("Country/Region Code", CountryRegion.Code); @@ -5455,6 +5460,210 @@ codeunit 148184 "Sustainability Posting Test" VerifySustValueEntry(PostedAssemblyHeader."No.", CompItem."No.", -ExpectedCO2eOnLot[2]); end; + [Test] + [HandlerFunctions('ConfirmHandler,GLPostingPreviewHandler')] + procedure VerifyIdentityContinuesAfterRepeatedPreviewOfCorrectivePurchaseCreditMemo() + var + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; + AccountCode: Code[20]; + CategoryCode: Code[20]; + SubcategoryCode: Code[20]; + CrMemoNo: Code[20]; + PostedCrMemoNo: Code[20]; + BaselineEntryNo: Integer; + BaselineEmissionCO2: Decimal; + begin + // [SCENARIO 640599] Repeated preview of a corrective purchase credit memo consumes no + // Sustainability Ledger Entry identity, and posting creates the baseline entry plus one. + LibrarySustainability.CleanUpBeforeTesting(); + + // [GIVEN] "Enable Value Chain Tracking" is disabled so only a Sustainability Ledger Entry is created. + LibrarySustainability.UpdateValueChainTrackingInSustainabilitySetup(false); + + // [GIVEN] A Sustainability Account. + CreateSustainabilityAccount(AccountCode, CategoryCode, SubcategoryCode, LibraryRandom.RandInt(10)); + + // [GIVEN] A Purchase Order line with Sustainability emissions. + LibraryPurchase.CreatePurchHeader(PurchaseHeader, "Purchase Document Type"::Order, LibraryPurchase.CreateVendorNo()); + LibraryPurchase.CreatePurchaseLine( + PurchaseLine, PurchaseHeader, "Purchase Line Type"::Item, LibraryInventory.CreateItemNo(), LibraryRandom.RandIntInRange(10, 10)); + PurchaseLine.Validate("Direct Unit Cost", LibraryRandom.RandIntInRange(10, 200)); + PurchaseLine.Validate("Qty. to Receive", LibraryRandom.RandIntInRange(5, 5)); + PurchaseLine.Validate("Sust. Account No.", AccountCode); + PurchaseLine.Validate("Emission CO2", LibraryRandom.RandIntInRange(10, 20)); + PurchaseLine.Validate("Emission CH4", LibraryRandom.RandIntInRange(1, 5)); + PurchaseLine.Validate("Emission N2O", LibraryRandom.RandIntInRange(1, 5)); + PurchaseLine.Modify(true); + + // [GIVEN] A Reason Code on the Purchase Header. + UpdateReasonCodeinPurchaseHeader(PurchaseHeader); + + // [GIVEN] The source invoice is posted and a corrective credit memo is created. + CrMemoNo := CreateCorrectiveCreditMemo(PurchaseHeader); + + // [GIVEN] The committed source-invoice Sustainability Ledger Entry is the identity baseline. + SustainabilityLedgerEntry.Reset(); + Assert.RecordCount(SustainabilityLedgerEntry, 1); + SustainabilityLedgerEntry.FindFirst(); + BaselineEntryNo := SustainabilityLedgerEntry."Entry No."; + BaselineEmissionCO2 := SustainabilityLedgerEntry."Emission CO2"; + Assert.IsTrue(BaselineEntryNo > 0, EntryNoShouldBePositiveErr); + + // [GIVEN] The corrective credit memo is loaded and committed. + PurchaseHeader.Get(PurchaseHeader."Document Type"::"Credit Memo", CrMemoNo); + Commit(); + + // [WHEN] The corrective credit memo is previewed twice. + asserterror LibraryPurchase.PreviewPostPurchaseDocument(PurchaseHeader); + Assert.ExpectedError(''); + asserterror LibraryPurchase.PreviewPostPurchaseDocument(PurchaseHeader); + Assert.ExpectedError(''); + + // [WHEN] The corrective credit memo is posted. + PostedCrMemoNo := LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, true); + + // [THEN] The new committed Entry No. is the baseline plus one - no identity was consumed by preview. + SustainabilityLedgerEntry.Reset(); + SustainabilityLedgerEntry.SetRange("Document No.", PostedCrMemoNo); + SustainabilityLedgerEntry.FindFirst(); + Assert.AreEqual(BaselineEntryNo + 1, SustainabilityLedgerEntry."Entry No.", EntryNoShouldBeBaselinePlusOneErr); + + // [THEN] The credit memo preserves the reversed sign of the source-invoice emission. + Assert.AreEqual( + -BaselineEmissionCO2, + SustainabilityLedgerEntry."Emission CO2", + StrSubstNo(ValueMustBeEqualErr, SustainabilityLedgerEntry.FieldCaption("Emission CO2"), -BaselineEmissionCO2, SustainabilityLedgerEntry.TableCaption())); + end; + + [Test] + [HandlerFunctions('ConfirmHandler,MessageHandler')] + procedure VerifyNativeSustainabilityJournalPostingUsesPositiveConsecutiveEntryNos() + var + SustainabilityJnlBatch: Record "Sustainability Jnl. Batch"; + SustainabilityAccount: Record "Sustainability Account"; + SustainabilityJournalLine: Record "Sustainability Jnl. Line"; + SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; + SustainabilityJournalMgt: Codeunit "Sustainability Journal Mgt."; + FirstEntryNo: Integer; + begin + // [SCENARIO 640599] Native Sustainability journal posting keeps positive consecutive AutoIncrement keys. + LibrarySustainability.CleanUpBeforeTesting(); + + // [GIVEN] A Sustainability Journal Batch and a ready-to-post Account. + SustainabilityJnlBatch := SustainabilityJournalMgt.GetASustainabilityJournalBatch(false); + SustainabilityAccount := LibrarySustainability.GetAReadyToPostAccount(); + + // [GIVEN] Two valid Sustainability Journal Lines. + CreatePostableSustainabilityJnlLine(SustainabilityJournalLine, SustainabilityJnlBatch, SustainabilityAccount, 10000); + CreatePostableSustainabilityJnlLine(SustainabilityJournalLine, SustainabilityJnlBatch, SustainabilityAccount, 20000); + + // [WHEN] The journal lines are posted natively. + SustainabilityJournalLine.SetRange("Journal Template Name", SustainabilityJnlBatch."Journal Template Name"); + SustainabilityJournalLine.SetRange("Journal Batch Name", SustainabilityJnlBatch.Name); + Codeunit.Run(Codeunit::"Sustainability Jnl.-Post", SustainabilityJournalLine); + + // [THEN] Exactly two Sustainability Ledger Entries with positive consecutive SQL-assigned keys. + SustainabilityLedgerEntry.Reset(); + Assert.AreEqual(2, SustainabilityLedgerEntry.Count(), TwoLedgerEntriesExpectedErr); + SustainabilityLedgerEntry.FindSet(); + FirstEntryNo := SustainabilityLedgerEntry."Entry No."; + Assert.IsTrue(FirstEntryNo > 0, EntryNoShouldBePositiveErr); + SustainabilityLedgerEntry.Next(); + Assert.AreEqual(FirstEntryNo + 1, SustainabilityLedgerEntry."Entry No.", EntryNoShouldBeConsecutiveErr); + end; + + [Test] + [HandlerFunctions('ConfirmHandler,MessageHandler')] + procedure VerifyRecurringSustainabilityJournalPostingUsesPositiveConsecutiveEntryNos() + var + SustainabilityJnlBatch: Record "Sustainability Jnl. Batch"; + SustainabilityAccount: Record "Sustainability Account"; + SustainabilityJournalLine: Record "Sustainability Jnl. Line"; + SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; + SustainabilityJournalMgt: Codeunit "Sustainability Journal Mgt."; + RecurringFrequency: DateFormula; + FirstEntryNo: Integer; + begin + // [SCENARIO 640599] Recurring Sustainability journal posting keeps positive consecutive AutoIncrement keys. + LibrarySustainability.CleanUpBeforeTesting(); + + // [GIVEN] A recurring Sustainability Journal Batch and a ready-to-post Account. + SustainabilityJnlBatch := SustainabilityJournalMgt.GetASustainabilityJournalBatch(true); + SustainabilityAccount := LibrarySustainability.GetAReadyToPostAccount(); + Evaluate(RecurringFrequency, '<1M>'); + + // [GIVEN] Two valid recurring Sustainability Journal Lines. + CreatePostableSustainabilityJnlLine(SustainabilityJournalLine, SustainabilityJnlBatch, SustainabilityAccount, 10000); + SetRecurringFieldsOnJnlLine(SustainabilityJournalLine, RecurringFrequency); + CreatePostableSustainabilityJnlLine(SustainabilityJournalLine, SustainabilityJnlBatch, SustainabilityAccount, 20000); + SetRecurringFieldsOnJnlLine(SustainabilityJournalLine, RecurringFrequency); + + // [WHEN] The recurring journal lines are posted. + SustainabilityJournalLine.SetRange("Journal Template Name", SustainabilityJnlBatch."Journal Template Name"); + SustainabilityJournalLine.SetRange("Journal Batch Name", SustainabilityJnlBatch.Name); + Codeunit.Run(Codeunit::"Sustainability Recur Jnl.-Post", SustainabilityJournalLine); + + // [THEN] Exactly two Sustainability Ledger Entries with positive consecutive SQL-assigned keys. + SustainabilityLedgerEntry.Reset(); + Assert.AreEqual(2, SustainabilityLedgerEntry.Count(), TwoLedgerEntriesExpectedErr); + SustainabilityLedgerEntry.FindSet(); + FirstEntryNo := SustainabilityLedgerEntry."Entry No."; + Assert.IsTrue(FirstEntryNo > 0, EntryNoShouldBePositiveErr); + SustainabilityLedgerEntry.Next(); + Assert.AreEqual(FirstEntryNo + 1, SustainabilityLedgerEntry."Entry No.", EntryNoShouldBeConsecutiveErr); + end; + + [Test] + [HandlerFunctions('GLPostingPreviewNegativeKeyHandler')] + procedure VerifyCustomGenericPreviewUsesNegativeKeyWithoutConsumingIdentity() + var + SustainabilityJnlBatch: Record "Sustainability Jnl. Batch"; + SustainabilityAccount: Record "Sustainability Account"; + SustainabilityJournalLine: Record "Sustainability Jnl. Line"; + SustainabilityLedgerEntry: Record "Sustainability Ledger Entry"; + SustainabilityJournalMgt: Codeunit "Sustainability Journal Mgt."; + SustainabilityPostMgt: Codeunit "Sustainability Post Mgt"; + GenJnlPostPreview: Codeunit "Gen. Jnl.-Post Preview"; + SustPreviewTestSubscriber: Codeunit "Sust Preview Test Subscriber"; + BaselineEntryNo: Integer; + begin + // [SCENARIO 640599] A custom generic Gen. Jnl.-Post Preview subscriber that calls InsertLedgerEntry + // without enabling sequence preview mode uses a negative temporary key and consumes no identity. + LibrarySustainability.CleanUpBeforeTesting(); + + // [GIVEN] A prepared Sustainability Journal Line ready for InsertLedgerEntry. + SustainabilityJnlBatch := SustainabilityJournalMgt.GetASustainabilityJournalBatch(false); + SustainabilityAccount := LibrarySustainability.GetAReadyToPostAccount(); + SustainabilityJournalLine := LibrarySustainability.InsertSustainabilityJournalLine(SustainabilityJnlBatch, SustainabilityAccount, 10000); + SustainabilityJournalLine.Validate("Fuel/Electricity", LibraryRandom.RandIntInRange(1, 10)); + SustainabilityJournalLine.Modify(true); + + // [GIVEN] A committed baseline Sustainability Ledger Entry. + SustainabilityPostMgt.InsertLedgerEntry(SustainabilityJournalLine); + SustainabilityLedgerEntry.Reset(); + SustainabilityLedgerEntry.FindLast(); + BaselineEntryNo := SustainabilityLedgerEntry."Entry No."; + Assert.IsTrue(BaselineEntryNo > 0, EntryNoShouldBePositiveErr); + Commit(); + + // [WHEN] A custom generic preview inserts a Sustainability Ledger Entry (negative key asserted in the handler). + BindSubscription(SustPreviewTestSubscriber); + asserterror GenJnlPostPreview.Preview(SustPreviewTestSubscriber, SustainabilityJournalLine); + + // [THEN] Only the preview-mode error is raised. + Assert.ExpectedError(''); + UnbindSubscription(SustPreviewTestSubscriber); + + // [THEN] No identity was consumed: a subsequent normal insert yields the baseline plus one. + SustainabilityPostMgt.InsertLedgerEntry(SustainabilityJournalLine); + SustainabilityLedgerEntry.Reset(); + SustainabilityLedgerEntry.SetFilter("Entry No.", '>%1', BaselineEntryNo); + SustainabilityLedgerEntry.FindFirst(); + Assert.AreEqual(BaselineEntryNo + 1, SustainabilityLedgerEntry."Entry No.", EntryNoShouldBeBaselinePlusOneErr); + end; + local procedure CreateUserSetup(var UserSetup: Record "User Setup"; UserID: Code[50]) begin UserSetup.Init(); @@ -5487,6 +5696,20 @@ codeunit 148184 "Sustainability Posting Test" true, true, true, '', false); end; + local procedure CreatePostableSustainabilityJnlLine(var SustainabilityJournalLine: Record "Sustainability Jnl. Line"; SustainabilityJnlBatch: Record "Sustainability Jnl. Batch"; SustainabilityAccount: Record "Sustainability Account"; LineNo: Integer) + var + UnitOfMeasure: Record "Unit of Measure"; + SustainabilityJournalMgt: Codeunit "Sustainability Journal Mgt."; + begin + LibraryInventory.CreateUnitOfMeasureCode(UnitOfMeasure); + SustainabilityJournalLine := LibrarySustainability.InsertSustainabilityJournalLine(SustainabilityJnlBatch, SustainabilityAccount, LineNo); + SustainabilityJournalLine.Validate("Document No.", SustainabilityJournalMgt.GetDocumentNo(false, SustainabilityJnlBatch, '', SustainabilityJournalLine."Posting Date")); + SustainabilityJournalLine.Validate(Description, LibraryUtility.GenerateGUID()); + SustainabilityJournalLine.Validate("Unit of Measure", UnitOfMeasure.Code); + SustainabilityJournalLine.Validate("Fuel/Electricity", LibraryRandom.RandIntInRange(1, 10)); + SustainabilityJournalLine.Modify(true); + end; + local procedure UpdateReasonCodeinPurchaseHeader(var PurchaseHeader: Record "Purchase Header") var ReasonCode: Record "Reason Code"; @@ -5519,7 +5742,7 @@ codeunit 148184 "Sustainability Posting Test" // Create Corrective Credit Memo. CorrectPostedPurchInvoice.CreateCreditMemoCopyDocument(PurchInvHeader, PurchaseHeader); - PurchaseHeader.Validate("Vendor Cr. Memo No.", LibraryRandom.RandText(10)); + PurchaseHeader.Validate("Vendor Cr. Memo No.", LibraryUtility.GenerateGUID()); PurchaseHeader.Modify(); // Post Corrective Credit Memo. @@ -5600,7 +5823,7 @@ codeunit 148184 "Sustainability Posting Test" // Create Corrective Credit Memo. CorrectPostedPurchInvoice.CreateCreditMemoCopyDocument(PurchInvHeader, PurchaseHeader); - PurchaseHeader.Validate("Vendor Cr. Memo No.", LibraryRandom.RandText(10)); + PurchaseHeader.Validate("Vendor Cr. Memo No.", LibraryUtility.GenerateGUID()); PurchaseHeader.Modify(); // Open Purchase Cr Memo Statistics. @@ -5973,7 +6196,7 @@ codeunit 148184 "Sustainability Posting Test" // Create Corrective Credit Memo. CorrectPostedPurchInvoice.CreateCreditMemoCopyDocument(PurchInvHeader, PurchaseHeader); - PurchaseHeader.Validate("Vendor Cr. Memo No.", LibraryRandom.RandText(10)); + PurchaseHeader.Validate("Vendor Cr. Memo No.", LibraryUtility.GenerateGUID()); PurchaseHeader.Modify(true); exit(PurchaseHeader."No."); @@ -6019,7 +6242,7 @@ codeunit 148184 "Sustainability Posting Test" LibraryRandom.RandDecInDecimalRange(0.5, 1, 1)); end; - local procedure CreateAndPostPurchaseDocument(var PurchaseHeader: Record "Purchase Header"; ItemNo: Code[20]; Quantity: Decimal; LocationCode: Code[20]; CountryRegionCode: Code[10]; AccountCode: Code[20]; EmissionCO2PerUnit: Decimal; EmissionCH4PerUnit: Decimal; EmissionN2OPerUnit: Decimal): Code[20] + local procedure CreateAndPostPurchaseDocument(var PurchaseHeader: Record "Purchase Header"; ItemNo: Code[20]; Quantity: Decimal; LocationCode: Code[10]; CountryRegionCode: Code[10]; AccountCode: Code[20]; EmissionCO2PerUnit: Decimal; EmissionCH4PerUnit: Decimal; EmissionN2OPerUnit: Decimal): Code[20] var PurchaseLine: Record "Purchase Line"; begin @@ -6062,6 +6285,13 @@ codeunit 148184 "Sustainability Posting Test" LibraryInventory.ClearItemJournal(ItemJournalTemplate, ItemJournalBatch); end; + local procedure SetRecurringFieldsOnJnlLine(var SustainabilityJournalLine: Record "Sustainability Jnl. Line"; RecurringFrequency: DateFormula) + begin + SustainabilityJournalLine.Validate("Recurring Method", SustainabilityJournalLine."Recurring Method"::"F Fixed"); + SustainabilityJournalLine.Validate("Recurring Frequency", RecurringFrequency); + SustainabilityJournalLine.Modify(true); + end; + local procedure VerifySustValueEntry(DocumentNo: Code[20]; ItemNo: Code[20]; ExpectedCO2eEmission: Decimal) var SustainabilityValueEntry: Record "Sustainability Value Entry"; @@ -6193,6 +6423,24 @@ codeunit 148184 "Sustainability Posting Test" GLPostingPreview.OK().Invoke(); end; + [PageHandler] + procedure GLPostingPreviewNegativeKeyHandler(var GLPostingPreview: TestPage "G/L Posting Preview") + var + SustainabilityLedgerEntries: TestPage "Sustainability Ledger Entries"; + begin + GLPostingPreview.Filter.SetFilter("Table ID", Format(Database::"Sustainability Ledger Entry")); + GLPostingPreview."No. of Records".AssertEquals(1); + + // Drill down to the temporary preview Sustainability Ledger Entries and assert the negative preview key. + SustainabilityLedgerEntries.Trap(); + GLPostingPreview."No. of Records".DrillDown(); + SustainabilityLedgerEntries.First(); + Assert.IsTrue(SustainabilityLedgerEntries."Entry No.".AsInteger() < 0, PreviewKeyShouldBeNegativeErr); + SustainabilityLedgerEntries.Close(); + + GLPostingPreview.OK().Invoke(); + end; + [PageHandler] [Scope('OnPrem')] procedure GLPostingPreviewHandlerForSales(var GLPostingPreview: TestPage "G/L Posting Preview")