From 7d1c77df5b286979ba3ff5ae58a2e5a4195d176b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miljan=20Milosavljevi=C4=87?= Date: Mon, 3 Aug 2026 11:39:09 +0200 Subject: [PATCH] feat: post G/L Account contract lines to their own account Subscription Billing contract lines of type "G/L Account" always posted to the Customer/Vendor Subscription Contract Account from the General Posting Setup, ignoring the account actually selected on the contract line. - Without deferrals, CustomerDeferralsMngmt/VendorDeferralsMngmt now skip the General Posting Setup account lookup for "G/L Account" lines so the invoice posts directly to the line's account. - With deferrals, a new "G/L Account No." field (27) on Cust./Vend. Sub. Contract Deferral carries the account from the sales/purchase line, and is surfaced on the Customer/Vendor Contract Deferrals pages. - ContractDeferralsRelease now posts to that account when releasing the deferral, falling back to the General Posting Setup account when it is blank, and only requires the General Posting Setup account to be filled when no contract-line account is available. - Added TDD tests to CustomerDeferralsTest and VendorDeferralsTest covering both the immediate-posting and deferral-release paths, with a shared fixture in ContractTestLibrary. Fixes #9885 Co-Authored-By: Claude Opus 4.8 Co-Authored-By: Claude Sonnet 5 --- .../CustomerDeferralsMngmt.Codeunit.al | 2 + .../VendorDeferralsMngmt.Codeunit.al | 2 + .../Pages/CustomerContractDeferrals.Page.al | 3 + .../Pages/VendorContractDeferrals.Page.al | 3 + .../ContractDeferralsRelease.Report.al | 23 ++-- .../Tables/CustSubContractDeferral.Table.al | 11 ++ .../Tables/VendSubContractDeferral.Table.al | 11 ++ .../Test/Base/ContractTestLibrary.Codeunit.al | 54 ++++---- .../CustomerDeferralsTest.Codeunit.al | 117 ++++++++++++++++++ .../Deferrals/VendorDeferralsTest.Codeunit.al | 113 +++++++++++++++++ 10 files changed, 308 insertions(+), 31 deletions(-) diff --git a/src/Apps/W1/Subscription Billing/App/Deferrals/Codeunits/CustomerDeferralsMngmt.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Deferrals/Codeunits/CustomerDeferralsMngmt.Codeunit.al index 06535d0d88f..13fe823a98c 100644 --- a/src/Apps/W1/Subscription Billing/App/Deferrals/Codeunits/CustomerDeferralsMngmt.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/App/Deferrals/Codeunits/CustomerDeferralsMngmt.Codeunit.al @@ -34,6 +34,8 @@ codeunit 8067 "Customer Deferrals Mngmt." GeneralPostingSetup.TestField("Cust. Sub. Contr. Def Account"); SalesAccount := GeneralPostingSetup."Cust. Sub. Contr. Def Account"; end else begin + if SalesLine.Type = SalesLine.Type::"G/L Account" then + exit; // post directly to the G/L account selected on the contract line GeneralPostingSetup.TestField("Cust. Sub. Contract Account"); SalesAccount := GeneralPostingSetup."Cust. Sub. Contract Account"; end; diff --git a/src/Apps/W1/Subscription Billing/App/Deferrals/Codeunits/VendorDeferralsMngmt.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Deferrals/Codeunits/VendorDeferralsMngmt.Codeunit.al index b006de302ee..c31c1674d1e 100644 --- a/src/Apps/W1/Subscription Billing/App/Deferrals/Codeunits/VendorDeferralsMngmt.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/App/Deferrals/Codeunits/VendorDeferralsMngmt.Codeunit.al @@ -36,6 +36,8 @@ codeunit 8068 "Vendor Deferrals Mngmt." GeneralPostingSetup.TestField("Vend. Sub. Contr. Def. Account"); SalesAccount := GeneralPostingSetup."Vend. Sub. Contr. Def. Account"; end else begin + if PurchLine.Type = PurchLine.Type::"G/L Account" then + exit; // post directly to the G/L account selected on the contract line GeneralPostingSetup.TestField("Vend. Sub. Contract Account"); SalesAccount := GeneralPostingSetup."Vend. Sub. Contract Account"; end; diff --git a/src/Apps/W1/Subscription Billing/App/Deferrals/Pages/CustomerContractDeferrals.Page.al b/src/Apps/W1/Subscription Billing/App/Deferrals/Pages/CustomerContractDeferrals.Page.al index 73c640f242c..402f52f0991 100644 --- a/src/Apps/W1/Subscription Billing/App/Deferrals/Pages/CustomerContractDeferrals.Page.al +++ b/src/Apps/W1/Subscription Billing/App/Deferrals/Pages/CustomerContractDeferrals.Page.al @@ -101,6 +101,9 @@ page 8079 "Customer Contract Deferrals" field("Gen. Prod. Posting Group"; Rec."Gen. Prod. Posting Group") { } + field("G/L Account No."; Rec."G/L Account No.") + { + } field("G/L Entry No."; Rec."G/L Entry No.") { ToolTip = 'Specifies the number of the G/L item with which the deferral was released.'; diff --git a/src/Apps/W1/Subscription Billing/App/Deferrals/Pages/VendorContractDeferrals.Page.al b/src/Apps/W1/Subscription Billing/App/Deferrals/Pages/VendorContractDeferrals.Page.al index 1eeb308695e..a5f929d15f6 100644 --- a/src/Apps/W1/Subscription Billing/App/Deferrals/Pages/VendorContractDeferrals.Page.al +++ b/src/Apps/W1/Subscription Billing/App/Deferrals/Pages/VendorContractDeferrals.Page.al @@ -101,6 +101,9 @@ page 8081 "Vendor Contract Deferrals" field("Gen. Prod. Posting Group"; Rec."Gen. Prod. Posting Group") { } + field("G/L Account No."; Rec."G/L Account No.") + { + } field("G/L Entry No."; Rec."G/L Entry No.") { ToolTip = 'Specifies the number of the G/L item with which the deferral was released.'; diff --git a/src/Apps/W1/Subscription Billing/App/Deferrals/Reports/ContractDeferralsRelease.Report.al b/src/Apps/W1/Subscription Billing/App/Deferrals/Reports/ContractDeferralsRelease.Report.al index 4e988842330..4a529eb75a8 100644 --- a/src/Apps/W1/Subscription Billing/App/Deferrals/Reports/ContractDeferralsRelease.Report.al +++ b/src/Apps/W1/Subscription Billing/App/Deferrals/Reports/ContractDeferralsRelease.Report.al @@ -151,7 +151,7 @@ report 8051 "Contract Deferrals Release" end; if (CustomerContractDeferral.Amount <> 0) or (CustomerContractDeferral."Discount Amount" <> 0) then - CheckGenPostingSetup(CustomerContractDeferral."Gen. Bus. Posting Group", CustomerContractDeferral."Gen. Prod. Posting Group", Enum::"Service Partner"::Customer); + CheckGenPostingSetup(CustomerContractDeferral."Gen. Bus. Posting Group", CustomerContractDeferral."Gen. Prod. Posting Group", Enum::"Service Partner"::Customer, CustomerContractDeferral."G/L Account No." <> ''); CustomerContractDeferral.Released := true; CustomerContractDeferral."Release Posting Date" := PostingDate; @@ -164,7 +164,7 @@ report 8051 "Contract Deferrals Release" CustomerContractDeferral."Subscription Contract No.", CustomerContractDeferral."Entry No.", CustomerContractDeferral."Dimension Set ID", - GenPostingSetup."Cust. Sub. Contract Account", + GetContractAccount(CustomerContractDeferral."G/L Account No.", GenPostingSetup."Cust. Sub. Contract Account"), GenPostingSetup."Cust. Sub. Contr. Def Account", CustomerContractDeferral."Gen. Bus. Posting Group", CustomerContractDeferral."Gen. Prod. Posting Group", @@ -206,7 +206,7 @@ report 8051 "Contract Deferrals Release" end; if (VendorContractDeferral.Amount <> 0) or (VendorContractDeferral."Discount Amount" <> 0) then - CheckGenPostingSetup(VendorContractDeferral."Gen. Bus. Posting Group", VendorContractDeferral."Gen. Prod. Posting Group", Enum::"Service Partner"::Vendor); + CheckGenPostingSetup(VendorContractDeferral."Gen. Bus. Posting Group", VendorContractDeferral."Gen. Prod. Posting Group", Enum::"Service Partner"::Vendor, VendorContractDeferral."G/L Account No." <> ''); VendorContractDeferral.Released := true; VendorContractDeferral."Release Posting Date" := PostingDate; @@ -219,7 +219,7 @@ report 8051 "Contract Deferrals Release" VendorContractDeferral."Subscription Contract No.", VendorContractDeferral."Entry No.", VendorContractDeferral."Dimension Set ID", - GenPostingSetup."Vend. Sub. Contract Account", + GetContractAccount(VendorContractDeferral."G/L Account No.", GenPostingSetup."Vend. Sub. Contract Account"), GenPostingSetup."Vend. Sub. Contr. Def. Account", VendorContractDeferral."Gen. Bus. Posting Group", VendorContractDeferral."Gen. Prod. Posting Group", @@ -256,25 +256,34 @@ report 8051 "Contract Deferrals Release" end; end; - local procedure CheckGenPostingSetup(GenBusPostingGroup: Code[20]; GenProdPostingGroup: Code[20]; Partner: Enum "Service Partner") + local procedure CheckGenPostingSetup(GenBusPostingGroup: Code[20]; GenProdPostingGroup: Code[20]; Partner: Enum "Service Partner"; ContractAccountTakenFromDeferral: Boolean) begin GenPostingSetup.Get(GenBusPostingGroup, GenProdPostingGroup); case Partner of Enum::"Service Partner"::Customer: begin - GenPostingSetup.TestField("Cust. Sub. Contract Account"); + if not ContractAccountTakenFromDeferral then + GenPostingSetup.TestField("Cust. Sub. Contract Account"); if LineDiscountPosting then GenPostingSetup.TestField("Sales Line Disc. Account"); end; Enum::"Service Partner"::Vendor: begin - GenPostingSetup.TestField("Vend. Sub. Contract Account"); + if not ContractAccountTakenFromDeferral then + GenPostingSetup.TestField("Vend. Sub. Contract Account"); if LineDiscountPosting then GenPostingSetup.TestField("Purch. Line Disc. Account"); end; end; end; + local procedure GetContractAccount(DeferralGLAccountNo: Code[20]; GenPostingSetupContractAccount: Code[20]): Code[20] + begin + if DeferralGLAccountNo <> '' then + exit(DeferralGLAccountNo); + exit(GenPostingSetupContractAccount); + end; + local procedure GetPostingAmount(Amount: Decimal; DiscountAmount: Decimal): Decimal begin if LineDiscountPosting then diff --git a/src/Apps/W1/Subscription Billing/App/Deferrals/Tables/CustSubContractDeferral.Table.al b/src/Apps/W1/Subscription Billing/App/Deferrals/Tables/CustSubContractDeferral.Table.al index 613788f4a74..628be5a2174 100644 --- a/src/Apps/W1/Subscription Billing/App/Deferrals/Tables/CustSubContractDeferral.Table.al +++ b/src/Apps/W1/Subscription Billing/App/Deferrals/Tables/CustSubContractDeferral.Table.al @@ -1,6 +1,7 @@ namespace Microsoft.SubscriptionBilling; using Microsoft.Finance.Dimension; +using Microsoft.Finance.GeneralLedger.Account; using Microsoft.Finance.GeneralLedger.Ledger; using Microsoft.Finance.GeneralLedger.Setup; using Microsoft.Sales.Customer; @@ -135,6 +136,14 @@ table 8066 "Cust. Sub. Contract Deferral" { Caption = 'Currency Code'; } + field(27; "G/L Account No."; Code[20]) + { + Caption = 'G/L Account No.'; + ToolTip = 'Specifies the G/L account selected on the Subscription Contract line. If it is filled, releasing the deferral posts to this account instead of the Customer Subscription Contract Account from the General Posting Setup.'; + DataClassification = CustomerContent; + Editable = false; + TableRelation = "G/L Account"; + } field(74; "Gen. Bus. Posting Group"; Code[20]) { Caption = 'Gen. Bus. Posting Group'; @@ -195,6 +204,8 @@ table 8066 "Cust. Sub. Contract Deferral" Rec."Currency Code" := SalesLine."Currency Code"; Rec."Gen. Bus. Posting Group" := SalesLine."Gen. Bus. Posting Group"; Rec."Gen. Prod. Posting Group" := SalesLine."Gen. Prod. Posting Group"; + if SalesLine.Type = SalesLine.Type::"G/L Account" then + Rec."G/L Account No." := SalesLine."No."; OnAfterInitFromSalesLine(Rec, SalesLine, Sign); end; diff --git a/src/Apps/W1/Subscription Billing/App/Deferrals/Tables/VendSubContractDeferral.Table.al b/src/Apps/W1/Subscription Billing/App/Deferrals/Tables/VendSubContractDeferral.Table.al index cc2f4454bab..3f7f46ba718 100644 --- a/src/Apps/W1/Subscription Billing/App/Deferrals/Tables/VendSubContractDeferral.Table.al +++ b/src/Apps/W1/Subscription Billing/App/Deferrals/Tables/VendSubContractDeferral.Table.al @@ -1,6 +1,7 @@ namespace Microsoft.SubscriptionBilling; using Microsoft.Finance.Dimension; +using Microsoft.Finance.GeneralLedger.Account; using Microsoft.Finance.GeneralLedger.Ledger; using Microsoft.Finance.GeneralLedger.Setup; using Microsoft.Purchases.Document; @@ -136,6 +137,14 @@ table 8072 "Vend. Sub. Contract Deferral" { Caption = 'Currency Code'; } + field(27; "G/L Account No."; Code[20]) + { + Caption = 'G/L Account No.'; + ToolTip = 'Specifies the G/L account selected on the Subscription Contract line. If it is filled, releasing the deferral posts to this account instead of the Vendor Subscription Contract Account from the General Posting Setup.'; + DataClassification = CustomerContent; + Editable = false; + TableRelation = "G/L Account"; + } field(74; "Gen. Bus. Posting Group"; Code[20]) { Caption = 'Gen. Bus. Posting Group'; @@ -198,6 +207,8 @@ table 8072 "Vend. Sub. Contract Deferral" Rec."Currency Code" := PurchaseLine."Currency Code"; Rec."Gen. Bus. Posting Group" := PurchaseLine."Gen. Bus. Posting Group"; Rec."Gen. Prod. Posting Group" := PurchaseLine."Gen. Prod. Posting Group"; + if PurchaseLine.Type = PurchaseLine.Type::"G/L Account" then + Rec."G/L Account No." := PurchaseLine."No."; OnAfterInitFromPurchaseLine(Rec, PurchaseLine, Sign); end; diff --git a/src/Apps/W1/Subscription Billing/Test/Base/ContractTestLibrary.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Base/ContractTestLibrary.Codeunit.al index c74c72dffec..db7c16b90a2 100644 --- a/src/Apps/W1/Subscription Billing/Test/Base/ContractTestLibrary.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Base/ContractTestLibrary.Codeunit.al @@ -732,35 +732,41 @@ codeunit 139685 "Contract Test Library" procedure CreateServiceObjectForGLAccountWithServiceCommitments(var ServiceObject: Record "Subscription Header"; var GLAccount: Record "G/L Account"; NoOfCustomerServCommLinesToCreate: Integer; NoOfVendorServCommLinesToCreate: Integer; BillingBasePeriodText: Text; BillingRhythmText: Text) + begin + CreateServiceObjectForGLAccountWithServiceCommitments(ServiceObject, GLAccount, NoOfCustomerServCommLinesToCreate, NoOfVendorServCommLinesToCreate, BillingBasePeriodText, BillingRhythmText, WorkDate(), 0); + end; + + procedure CreateServiceObjectForGLAccountWithServiceCommitments(var ServiceObject: Record "Subscription Header"; var GLAccount: Record "G/L Account"; + NoOfCustomerServCommLinesToCreate: Integer; NoOfVendorServCommLinesToCreate: Integer; BillingBasePeriodText: Text; BillingRhythmText: Text; + SubscriptionLineStartDate: Date; CalculationBaseAmount: Decimal) var - ServiceCommitment: Record "Subscription Line"; i: Integer; begin CreateServiceObjectForGLAccount(ServiceObject, GLAccount); - for i := 1 to NoOfCustomerServCommLinesToCreate do begin - ServiceCommitment.Init(); - ServiceCommitment."Subscription Header No." := ServiceObject."No."; - ServiceCommitment."Entry No." := 0; - ServiceCommitment.Description := ServiceObject.Description; - ServiceCommitment."Invoicing via" := ServiceCommitment."Invoicing via"::Contract; - ServiceCommitment.Partner := ServiceCommitment.Partner::Customer; - ServiceCommitment.Validate("Subscription Line Start Date", WorkDate()); - Evaluate(ServiceCommitment."Billing Base Period", BillingBasePeriodText); - Evaluate(ServiceCommitment."Billing Rhythm", BillingRhythmText); - ServiceCommitment.Insert(false); - end; - for i := 1 to NoOfVendorServCommLinesToCreate do begin - ServiceCommitment.Init(); - ServiceCommitment."Subscription Header No." := ServiceObject."No."; - ServiceCommitment."Entry No." := 0; - ServiceCommitment.Description := ServiceObject.Description; - ServiceCommitment."Invoicing via" := ServiceCommitment."Invoicing via"::Contract; - ServiceCommitment.Partner := ServiceCommitment.Partner::Vendor; - ServiceCommitment.Validate("Subscription Line Start Date", WorkDate()); - Evaluate(ServiceCommitment."Billing Base Period", BillingBasePeriodText); - Evaluate(ServiceCommitment."Billing Rhythm", BillingRhythmText); - ServiceCommitment.Insert(false); + for i := 1 to NoOfCustomerServCommLinesToCreate do + InsertSubscriptionLineForServiceObject(ServiceObject, Enum::"Service Partner"::Customer, BillingBasePeriodText, BillingRhythmText, SubscriptionLineStartDate, CalculationBaseAmount); + for i := 1 to NoOfVendorServCommLinesToCreate do + InsertSubscriptionLineForServiceObject(ServiceObject, Enum::"Service Partner"::Vendor, BillingBasePeriodText, BillingRhythmText, SubscriptionLineStartDate, CalculationBaseAmount); + end; + + local procedure InsertSubscriptionLineForServiceObject(ServiceObject: Record "Subscription Header"; ServicePartner: Enum "Service Partner"; BillingBasePeriodText: Text; BillingRhythmText: Text; SubscriptionLineStartDate: Date; CalculationBaseAmount: Decimal) + var + ServiceCommitment: Record "Subscription Line"; + begin + ServiceCommitment.Init(); + ServiceCommitment."Subscription Header No." := ServiceObject."No."; + ServiceCommitment."Entry No." := 0; + ServiceCommitment.Description := ServiceObject.Description; + ServiceCommitment."Invoicing via" := ServiceCommitment."Invoicing via"::Contract; + ServiceCommitment.Partner := ServicePartner; + ServiceCommitment.Validate("Subscription Line Start Date", SubscriptionLineStartDate); + Evaluate(ServiceCommitment."Billing Base Period", BillingBasePeriodText); + Evaluate(ServiceCommitment."Billing Rhythm", BillingRhythmText); + if CalculationBaseAmount <> 0 then begin + ServiceCommitment.Validate("Calculation Base %", 100); + ServiceCommitment.Validate("Calculation Base Amount", CalculationBaseAmount); end; + ServiceCommitment.Insert(false); end; procedure CreateServiceObjectForItem(var ServiceObject: Record "Subscription Header"; ItemNo: Code[20]) diff --git a/src/Apps/W1/Subscription Billing/Test/Deferrals/CustomerDeferralsTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Deferrals/CustomerDeferralsTest.Codeunit.al index 1ec5b89884f..6ccb53f0e59 100644 --- a/src/Apps/W1/Subscription Billing/Test/Deferrals/CustomerDeferralsTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Deferrals/CustomerDeferralsTest.Codeunit.al @@ -2,6 +2,7 @@ namespace Microsoft.SubscriptionBilling; using Microsoft.Finance.Currency; using Microsoft.Finance.Deferral; +using Microsoft.Finance.GeneralLedger.Account; using Microsoft.Finance.GeneralLedger.Ledger; using Microsoft.Finance.GeneralLedger.Setup; using Microsoft.Inventory.Item; @@ -28,6 +29,7 @@ codeunit 139912 "Customer Deferrals Test" CustomerContractDeferral: Record "Cust. Sub. Contract Deferral"; SalesCrMemoDeferral: Record "Cust. Sub. Contract Deferral"; SalesInvoiceDeferral: Record "Cust. Sub. Contract Deferral"; + GLAccount: Record "G/L Account"; GLSetup: Record "General Ledger Setup"; GeneralPostingSetup: Record "General Posting Setup"; Item: Record Item; @@ -77,6 +79,9 @@ codeunit 139912 "Customer Deferrals Test" NumberOfDaysRemainingMismatchErr: Label 'Number of Days should equal remaining days in month.'; NumberOfDaysScheduleMismatchErr: Label 'Number of Days should equal actual schedule days, not full month.'; NumberOfDaysDateRangeMismatchErr: Label 'Number of Days should equal actual date range days.'; + DeferralsMissingLineGLAccountErr: Label 'Every deferral entry must carry the G/L account from the contract line.'; + ReleasedAmountNotPostedToLineGLAccountErr: Label 'Releasing the contract deferrals must post to the G/L account selected on the contract line.'; + RevenueNotPostedToLineGLAccountErr: Label 'The revenue must be posted directly to the G/L account selected on the contract line when no contract deferrals are created.'; #region Tests [Test] @@ -1145,6 +1150,96 @@ codeunit 139912 "Customer Deferrals Test" Assert.AreEqual(ExpectedLastMonthDays, CustomerContractDeferral."Number of Days", LastPartialMonthDaysMismatchErr); end; + [Test] + [HandlerFunctions('CreateCustomerBillingDocsContractPageHandler,MessageHandler')] + procedure GLAccountContractLinePostsToLineGLAccountWithoutDeferrals() + var + GLEntry: Record "G/L Entry"; + ExpectedAmount: Decimal; + begin + // [SCENARIO] When a contract line of type G/L Account is invoiced without contract deferrals, + // the revenue is posted directly to the G/L account selected on the contract line instead of + // the Customer Subscription Contract Account from the General Posting Setup. + Initialize(); + + // [GIVEN] A customer contract with a G/L Account contract line and contract deferrals disabled + CreateCustomerContractWithGLAccountLine('<2M-CM>'); + ContractTestLibrary.DisableDeferralsForCustomerContract(CustomerContract, false); + CreateBillingProposalAndCreateBillingDocuments('<2M-CM>', '<8M+CM>'); + + // [GIVEN] The Customer Subscription Contract Account is not set in the General Posting Setup + GeneralPostingSetup.Get(Customer."Gen. Bus. Posting Group", GLAccount."Gen. Prod. Posting Group"); + GeneralPostingSetup."Cust. Sub. Contract Account" := ''; + GeneralPostingSetup.Modify(false); + + // [WHEN] The contract invoice is posted + SalesLine.SetRange("Document No.", SalesHeader."No."); + SalesLine.SetRange(Type, SalesLine.Type::"G/L Account"); + SalesLine.CalcSums(Amount); + ExpectedAmount := SalesLine.Amount; + PostedDocumentNo := LibrarySales.PostSalesDocument(SalesHeader, true, true); + + // [THEN] No deferrals are created and the revenue is posted to the G/L account from the contract line + CustomerContractDeferral.SetRange("Document No.", PostedDocumentNo); + Assert.RecordIsEmpty(CustomerContractDeferral); + GLEntry.SetRange("Document No.", PostedDocumentNo); + GLEntry.SetRange("G/L Account No.", GLAccount."No."); + GLEntry.CalcSums(Amount); + Assert.AreEqual(-ExpectedAmount, GLEntry.Amount, RevenueNotPostedToLineGLAccountErr); + end; + + [Test] + [HandlerFunctions('CreateCustomerBillingDocsContractPageHandler,ContractDeferralsReleaseRequestPageHandler,MessageHandler')] + procedure GLAccountContractLineReleasesDeferralsToLineGLAccount() + var + GLEntry: Record "G/L Entry"; + ContractDeferralsRelease: Report "Contract Deferrals Release"; + TotalDeferralCount: Integer; + begin + // [SCENARIO] When a contract line of type G/L Account is invoiced with contract deferrals, + // the deferral entries carry the G/L account from the contract line and releasing them posts + // to that account instead of the Customer Subscription Contract Account from the General Posting Setup. + Initialize(); + SetPostingAllowTo(0D); + + // [GIVEN] A customer contract with a G/L Account contract line and contract deferrals enabled + CreateCustomerContractWithGLAccountLine('<2M-CM>'); + CreateBillingProposalAndCreateBillingDocuments('<2M-CM>', '<8M+CM>'); + + // [GIVEN] The Customer Subscription Contract Account is not set in the General Posting Setup + GeneralPostingSetup.Get(Customer."Gen. Bus. Posting Group", GLAccount."Gen. Prod. Posting Group"); + GeneralPostingSetup."Cust. Sub. Contract Account" := ''; + GeneralPostingSetup.Modify(false); + + // [WHEN] The contract invoice is posted + PostSalesDocumentAndFetchDeferrals(); + + // [THEN] Every deferral entry carries the G/L account from the contract line + TotalDeferralCount := CustomerContractDeferral.Count(); + CustomerContractDeferral.SetRange("G/L Account No.", GLAccount."No."); + Assert.RecordCount(CustomerContractDeferral, TotalDeferralCount); + Assert.AreNotEqual(0, TotalDeferralCount, DeferralsMissingLineGLAccountErr); + CustomerContractDeferral.FindFirst(); + + // [THEN] The invoice posting does not post to the G/L account from the contract line + GLEntry.SetRange("Document No.", PostedDocumentNo); + GLEntry.SetRange("G/L Account No.", GLAccount."No."); + Assert.RecordIsEmpty(GLEntry); + + // [WHEN] The contract deferrals are released for the first deferral posting date + PostingDate := CustomerContractDeferral."Posting Date"; + Commit(); // close transaction before report is called + ContractDeferralsRelease.Run(); // ContractDeferralsReleaseRequestPageHandler + + // [THEN] The released amount is posted to the G/L account from the contract line + CustomerContractDeferral.SetRange("G/L Account No."); + CustomerContractDeferral.SetRange(Released, true); + CustomerContractDeferral.CalcSums(Amount); + GLEntry.CalcSums(Amount); + Assert.AreEqual(CustomerContractDeferral.Amount, GLEntry.Amount, ReleasedAmountNotPostedToLineGLAccountErr); + Assert.AreNotEqual(0, GLEntry.Amount, ReleasedAmountNotPostedToLineGLAccountErr); + end; + #endregion Tests #region Procedures @@ -1232,6 +1327,28 @@ codeunit 139912 "Customer Deferrals Test" ContractTestLibrary.CreateCustomerContractAndCreateContractLinesForItems(CustomerContract, ServiceObject, Customer."No."); end; + local procedure CreateCustomerContractWithGLAccountLine(BillingDateFormula: Text) + var + TempServiceCommitment: Record "Subscription Line" temporary; + begin + ContractTestLibrary.CreateCustomerInLCY(Customer); + GLAccount.Get(LibraryERM.CreateGLAccountWithSalesSetup()); + ContractTestLibrary.CreateServiceObjectForGLAccountWithServiceCommitments(ServiceObject, GLAccount, 1, 0, '<1M>', '<1M>', CalcDate(BillingDateFormula, WorkDate()), 1200); + ServiceObject.SetHideValidationDialog(true); + ServiceObject.Validate("End-User Customer No.", Customer."No."); + ServiceObject.Modify(false); + + ContractTestLibrary.CreateCustomerContract(CustomerContract, Customer."No."); + ContractTestLibrary.FillTempServiceCommitment(TempServiceCommitment, ServiceObject, CustomerContract); + CustomerContract.CreateCustomerContractLinesFromServiceCommitments(TempServiceCommitment); + ContractTestLibrary.SetGeneralPostingSetup(Customer."Gen. Bus. Posting Group", GLAccount."Gen. Prod. Posting Group", false, Enum::"Service Partner"::Customer); + GeneralPostingSetup.Get(Customer."Gen. Bus. Posting Group", GLAccount."Gen. Prod. Posting Group"); + if GeneralPostingSetup."Sales Line Disc. Account" = '' then begin + GeneralPostingSetup."Sales Line Disc. Account" := LibraryERM.CreateGLAccountNo(); + GeneralPostingSetup.Modify(false); + end; + end; + local procedure CreateSalesDocumentsFromCustomerContractWODeferrals() var SubscriptionLine: Record "Subscription Line"; diff --git a/src/Apps/W1/Subscription Billing/Test/Deferrals/VendorDeferralsTest.Codeunit.al b/src/Apps/W1/Subscription Billing/Test/Deferrals/VendorDeferralsTest.Codeunit.al index 6dea777684c..418dde854f8 100644 --- a/src/Apps/W1/Subscription Billing/Test/Deferrals/VendorDeferralsTest.Codeunit.al +++ b/src/Apps/W1/Subscription Billing/Test/Deferrals/VendorDeferralsTest.Codeunit.al @@ -26,6 +26,7 @@ codeunit 139913 "Vendor Deferrals Test" BillingLine: Record "Billing Line"; BillingTemplate: Record "Billing Template"; CurrExchRate: Record "Currency Exchange Rate"; + LineGLAccount: Record "G/L Account"; GLSetup: Record "General Ledger Setup"; GeneralPostingSetup: Record "General Posting Setup"; Item: Record Item; @@ -65,6 +66,9 @@ codeunit 139913 "Vendor Deferrals Test" IsInitialized: Boolean; ReleasedContractDeferralErr: Label 'Released Contract Deferrals were not reversed properly'; AmountNotMovedFromDeferralsAccountErr: Label 'Amount was not moved from Deferrals Account to Contract Account'; + CostNotPostedToLineGLAccountErr: Label 'The cost must be posted directly to the G/L account selected on the contract line when no contract deferrals are created.'; + DeferralsMissingLineGLAccountErr: Label 'Every deferral entry must carry the G/L account from the contract line.'; + ReleasedAmountNotPostedToLineGLAccountErr: Label 'Releasing the contract deferrals must post to the G/L account selected on the contract line.'; #region Tests @@ -1070,6 +1074,96 @@ codeunit 139913 "Vendor Deferrals Test" Assert.AreEqual(GLAmountBeforeRelease - VendorContractDeferral.Amount, GLAmountAfterRelease, AmountNotMovedFromDeferralsAccountErr); end; + [Test] + [HandlerFunctions('CreateVendorBillingDocsContractPageHandler,MessageHandler')] + procedure GLAccountContractLinePostsToLineGLAccountWithoutDeferrals() + var + GLEntry: Record "G/L Entry"; + ExpectedAmount: Decimal; + begin + // [SCENARIO] When a contract line of type G/L Account is invoiced without contract deferrals, + // the cost is posted directly to the G/L account selected on the contract line instead of + // the Vendor Subscription Contract Account from the General Posting Setup. + Initialize(); + + // [GIVEN] A vendor contract with a G/L Account contract line and contract deferrals disabled + CreateVendorContractWithGLAccountLine('<2M-CM>'); + ContractTestLibrary.DisableDeferralsForVendorContract(VendorContract, false); + CreateBillingProposalAndCreateBillingDocuments('<2M-CM>', '<8M+CM>'); + + // [GIVEN] The Vendor Subscription Contract Account is not set in the General Posting Setup + GeneralPostingSetup.Get(Vendor."Gen. Bus. Posting Group", LineGLAccount."Gen. Prod. Posting Group"); + GeneralPostingSetup."Vend. Sub. Contract Account" := ''; + GeneralPostingSetup.Modify(false); + + // [WHEN] The contract invoice is posted + PurchaseLine.SetRange("Document No.", PurchaseHeader."No."); + PurchaseLine.SetRange(Type, PurchaseLine.Type::"G/L Account"); + PurchaseLine.CalcSums(Amount); + ExpectedAmount := PurchaseLine.Amount; + PostedDocumentNo := LibraryPurchase.PostPurchaseDocument(PurchaseHeader, true, true); + + // [THEN] No deferrals are created and the cost is posted to the G/L account from the contract line + VendorContractDeferral.SetRange("Document No.", PostedDocumentNo); + Assert.RecordIsEmpty(VendorContractDeferral); + GLEntry.SetRange("Document No.", PostedDocumentNo); + GLEntry.SetRange("G/L Account No.", LineGLAccount."No."); + GLEntry.CalcSums(Amount); + Assert.AreEqual(ExpectedAmount, GLEntry.Amount, CostNotPostedToLineGLAccountErr); + end; + + [Test] + [HandlerFunctions('CreateVendorBillingDocsContractPageHandler,ContractDeferralsReleaseRequestPageHandler,MessageHandler')] + procedure GLAccountContractLineReleasesDeferralsToLineGLAccount() + var + GLEntry: Record "G/L Entry"; + ContractDeferralsRelease: Report "Contract Deferrals Release"; + TotalDeferralCount: Integer; + begin + // [SCENARIO] When a contract line of type G/L Account is invoiced with contract deferrals, + // the deferral entries carry the G/L account from the contract line and releasing them posts + // to that account instead of the Vendor Subscription Contract Account from the General Posting Setup. + Initialize(); + SetPostingAllowTo(0D); + + // [GIVEN] A vendor contract with a G/L Account contract line and contract deferrals enabled + CreateVendorContractWithGLAccountLine('<2M-CM>'); + CreateBillingProposalAndCreateBillingDocuments('<2M-CM>', '<8M+CM>'); + + // [GIVEN] The Vendor Subscription Contract Account is not set in the General Posting Setup + GeneralPostingSetup.Get(Vendor."Gen. Bus. Posting Group", LineGLAccount."Gen. Prod. Posting Group"); + GeneralPostingSetup."Vend. Sub. Contract Account" := ''; + GeneralPostingSetup.Modify(false); + + // [WHEN] The contract invoice is posted + PostPurchDocumentAndFetchDeferrals(); + + // [THEN] Every deferral entry carries the G/L account from the contract line + TotalDeferralCount := VendorContractDeferral.Count(); + VendorContractDeferral.SetRange("G/L Account No.", LineGLAccount."No."); + Assert.RecordCount(VendorContractDeferral, TotalDeferralCount); + Assert.AreNotEqual(0, TotalDeferralCount, DeferralsMissingLineGLAccountErr); + VendorContractDeferral.FindFirst(); + + // [THEN] The invoice posting does not post to the G/L account from the contract line + GLEntry.SetRange("Document No.", PostedDocumentNo); + GLEntry.SetRange("G/L Account No.", LineGLAccount."No."); + Assert.RecordIsEmpty(GLEntry); + + // [WHEN] The contract deferrals are released for the first deferral posting date + PostingDate := VendorContractDeferral."Posting Date"; + Commit(); // close transaction before report is called + ContractDeferralsRelease.Run(); // ContractDeferralsReleaseRequestPageHandler + + // [THEN] The released amount is posted to the G/L account from the contract line + VendorContractDeferral.SetRange("G/L Account No."); + VendorContractDeferral.SetRange(Released, true); + VendorContractDeferral.CalcSums(Amount); + GLEntry.CalcSums(Amount); + Assert.AreEqual(VendorContractDeferral.Amount, GLEntry.Amount, ReleasedAmountNotPostedToLineGLAccountErr); + Assert.AreNotEqual(0, GLEntry.Amount, ReleasedAmountNotPostedToLineGLAccountErr); + end; + #endregion Tests #region Procedures @@ -1207,6 +1301,25 @@ codeunit 139913 "Vendor Deferrals Test" ContractTestLibrary.CreateVendorContractAndCreateContractLinesForItems(VendorContract, ServiceObject, Vendor."No."); end; + local procedure CreateVendorContractWithGLAccountLine(BillingDateFormula: Text) + var + TempServiceCommitment: Record "Subscription Line" temporary; + begin + ContractTestLibrary.CreateVendorInLCY(Vendor); + LineGLAccount.Get(LibraryERM.CreateGLAccountWithPurchSetup()); + ContractTestLibrary.CreateServiceObjectForGLAccountWithServiceCommitments(ServiceObject, LineGLAccount, 0, 1, '<1M>', '<1M>', CalcDate(BillingDateFormula, WorkDate()), 1200); + + ContractTestLibrary.CreateVendorContract(VendorContract, Vendor."No."); + ContractTestLibrary.FillTempServiceCommitmentForVendor(TempServiceCommitment, ServiceObject, VendorContract); + ContractTestLibrary.CreateVendorContractLinesFromServiceCommitments(VendorContract, TempServiceCommitment); + ContractTestLibrary.SetGeneralPostingSetup(Vendor."Gen. Bus. Posting Group", LineGLAccount."Gen. Prod. Posting Group", false, Enum::"Service Partner"::Vendor); + GeneralPostingSetup.Get(Vendor."Gen. Bus. Posting Group", LineGLAccount."Gen. Prod. Posting Group"); + if GeneralPostingSetup."Purch. Line Disc. Account" = '' then begin + GeneralPostingSetup."Purch. Line Disc. Account" := LibraryERM.CreateGLAccountNo(); + GeneralPostingSetup.Modify(false); + end; + end; + local procedure FetchAndTestUpdatedVendorContractDeferral() var UpdatedVendorContractDeferral: Record "Vend. Sub. Contract Deferral";