diff --git a/src/Layers/APAC/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/APAC/BaseApp/Sales/Customer/CustomerCard.Page.al index 6fe594e412..be36d9b2ab 100644 --- a/src/Layers/APAC/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/APAC/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2452,6 +2452,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2510,12 +2511,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/BE/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/BE/BaseApp/Sales/Customer/CustomerCard.Page.al index da61a60c5e..1c3b8231b4 100644 --- a/src/Layers/BE/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/BE/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2406,6 +2406,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2464,12 +2465,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/ES/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/ES/BaseApp/Sales/Customer/CustomerCard.Page.al index 2580cd9a73..b22dba24fb 100644 --- a/src/Layers/ES/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/ES/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2444,6 +2444,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2502,12 +2503,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/FR/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/FR/BaseApp/Sales/Customer/CustomerCard.Page.al index 6514fbfb7b..6aefbf928a 100644 --- a/src/Layers/FR/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/FR/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2450,6 +2450,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2508,12 +2509,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/GB/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/GB/BaseApp/Sales/Customer/CustomerCard.Page.al index 93d63aee9c..04fca04161 100644 --- a/src/Layers/GB/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/GB/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2440,6 +2440,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2501,12 +2502,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/IT/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/IT/BaseApp/Sales/Customer/CustomerCard.Page.al index fe12742e22..e7958ba098 100644 --- a/src/Layers/IT/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/IT/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2534,6 +2534,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2592,12 +2593,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/NA/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/NA/BaseApp/Sales/Customer/CustomerCard.Page.al index 3c45633ae7..00e3d00210 100644 --- a/src/Layers/NA/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/NA/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2551,6 +2551,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2609,12 +2610,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/NL/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/NL/BaseApp/Sales/Customer/CustomerCard.Page.al index 228ccd9a9c..85c5ea26f2 100644 --- a/src/Layers/NL/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/NL/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2396,6 +2396,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2454,12 +2455,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/NO/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/NO/BaseApp/Sales/Customer/CustomerCard.Page.al index 43c879be66..3cbd4c78cd 100644 --- a/src/Layers/NO/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/NO/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2412,6 +2412,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2470,12 +2471,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/RU/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/RU/BaseApp/Sales/Customer/CustomerCard.Page.al index 8fafeea501..2106016f9b 100644 --- a/src/Layers/RU/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/RU/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2522,6 +2522,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2584,12 +2585,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/W1/BaseApp/Sales/Customer/CustomerCard.Page.al b/src/Layers/W1/BaseApp/Sales/Customer/CustomerCard.Page.al index a80df697cc..25eace6196 100644 --- a/src/Layers/W1/BaseApp/Sales/Customer/CustomerCard.Page.al +++ b/src/Layers/W1/BaseApp/Sales/Customer/CustomerCard.Page.al @@ -2382,6 +2382,7 @@ page 21 "Customer Card" CRMCouplingManagement: Codeunit "CRM Coupling Management"; WorkflowWebhookManagement: Codeunit "Workflow Webhook Management"; begin + Rec.SetRange("Date Filter", 0D, WorkDate()); if NewMode then CreateCustomerFromTemplate() else @@ -2440,12 +2441,13 @@ page 21 "Customer Card" begin OnBeforeOnOpenPage(Rec); - if Rec.GetFilter("Date Filter") = '' then - Rec.SetRange("Date Filter", 0D, WorkDate()); if GuiAllowed() then OnOpenPageFunc() - else + else begin + if Rec.GetFilter("Date Filter") = '' then + Rec.SetRange("Date Filter", 0D, WorkDate()); OnOpenBackground(); + end; OnAfterOnOpenPage(Rec, xRec); end; diff --git a/src/Layers/W1/Tests/ERM/ERMCustomerStatistics.Codeunit.al b/src/Layers/W1/Tests/ERM/ERMCustomerStatistics.Codeunit.al index 26b7019b52..562b1d73df 100644 --- a/src/Layers/W1/Tests/ERM/ERMCustomerStatistics.Codeunit.al +++ b/src/Layers/W1/Tests/ERM/ERMCustomerStatistics.Codeunit.al @@ -31,6 +31,9 @@ codeunit 134389 "ERM Customer Statistics" PaymentsLCYAndAmountLCYMustMatchErr: Label 'Payemnts (LCY) and Amount (LCY) must match.'; CustomerCardFactboxTotalErr: Label 'Customer card factbox total is not Correct'; LotNoErr: Label 'Lot No. should have value.'; + DateFilterNotResetErr: Label 'Date Filter upper bound on the Customer Card should be reset to WORKDATE() before StartBackgroundCalculations captures Rec.GetView().'; + DateFilterLeakedErr: Label 'Date Filter on the Customer Card should no longer contain the leaked wider upper bound.'; + BalanceLCYShouldIncludeBothInvoicesErr: Label 'Balance (LCY) should include both invoices.'; [Test] [Scope('OnPrem')] @@ -491,20 +494,21 @@ codeunit 134389 "ERM Customer Statistics" // [SCENARIO 258948] When Drill Down on Balance Due (LCY) is called from Customer Card, Due Date filter on opened Customer Ledger Entries Page = Date Filter. Initialize(); - // [GIVEN] Setup new Customer with a customer ledger entry + // [GIVEN] Setup new Customer with a customer ledger entry due on or before the work date LibrarySales.CreateCustomer(Customer); CreateBasicCustLedgerEntry(CustLedgerEntry, Customer."No."); - CustLedgerEntry.Validate("Due Date", LibraryRandom.RandDate(100)); + CustLedgerEntry.Validate("Due Date", CalcDate('<-1M>', WorkDate())); CustLedgerEntry.Modify(true); // [WHEN] The user drills down on Balance Due (LCY) field from Customer Card CustomerCard.OpenView(); CustomerLedgerEntries.Trap(); - CustomerCard.FILTER.SetFilter("Date Filter", Format(CustLedgerEntry."Due Date")); + // [WHEN] A wider Date Filter is inherited on the Customer Card (document/journal path) + CustomerCard.FILTER.SetFilter("Date Filter", StrSubstNo('..%1', CalcDate('<1M>', WorkDate()))); CustomerCard.GotoRecord(Customer); CustomerCard."Balance Due (LCY)".DrillDown(); - // [THEN] Customer Ledger Entries window opens, Due Date filter = Date Filter from Customer List. + // [THEN] The Customer Card normalizes the Date Filter to 0D..WORKDATE (Bug 640988), and the opened Customer Ledger Entries Page Due Date filter = that Date Filter. Assert.AreEqual(CustomerCard.FILTER.GetFilter("Date Filter"), CustomerLedgerEntries.FILTER.GetFilter("Due Date"), ''); // [THEN] Customer Ledger Entries window opens, showing the ledger entries for the selected customer @@ -1119,6 +1123,62 @@ codeunit 134389 "ERM Customer Statistics" Assert.AreEqual(CustomerStatistics.GetTotalAmountLCY.AsDecimal(), Customer.GetTotalAmountLCY(), CustomerCardFactboxTotalErr); end; + + [Test] + procedure OverdueBalanceIsolatedFromLeakedWideDateFilterOnCustomer() + var + Customer: Record Customer; + CustomerForBackgroundTask: Record Customer; + CustomerCard: TestPage "Customer Card"; + EarliestDate: Date; + LatestDate: Date; + OverdueAmount: Decimal; + NotOverdueAmount: Decimal; + begin + // [FEATURE] [Overdue Balance] [Cue] + // [SCENARIO 640988] The "Balance Due" overdue payments cue on the Customer Card (page variable OverdueBalance, filled by the "Customer Card Calculations" background task from Rec.GetView) and the "Balance Due (LCY)" FlowField both return only overdue amounts, even when a wider "Date Filter" is inherited on the Customer record from a document or journal. + Initialize(); + + // [GIVEN] Reference dates that bracket both WORKDATE and Today so the fixture is deterministic regardless of their relative order. + if WorkDate() < Today() then begin + EarliestDate := WorkDate(); + LatestDate := Today(); + end else begin + EarliestDate := Today(); + LatestDate := WorkDate(); + end; + + // [GIVEN] Create customer with two posted sales invoices. + LibrarySales.CreateCustomer(Customer); + // [GIVEN] Invoice "A" is overdue against both WORKDATE and Today: Due Date < min(WORKDATE, Today), Amount = "X". + OverdueAmount := CreateAndPostInvoiceWithDueDate(Customer."No.", CalcDate('<-2M>', EarliestDate), CalcDate('<-1M>', EarliestDate)); + // [GIVEN] Invoice "B" is not yet due against either WORKDATE or Today: Due Date > max(WORKDATE, Today), Amount = "Y". + NotOverdueAmount := CreateAndPostInvoiceWithDueDate(Customer."No.", WorkDate(), CalcDate('<2M>', LatestDate)); + + // [WHEN] The Customer Card is opened for the customer with a leaked wider "Date Filter" applied by the caller (document/journal path). + CustomerCard.OpenView(); + CustomerCard.FILTER.SetFilter("Date Filter", StrSubstNo('..%1', CalcDate('<3M>', LatestDate))); + CustomerCard.FILTER.SetFilter("No.", Customer."No."); + + // [THEN] OnAfterGetCurrRecordFunc resets the "Date Filter" on the Card record to 0D..WORKDATE before StartBackgroundCalculations captures the view, so the leaked upper bound past WORKDATE no longer applies on the page. + Assert.IsTrue(StrPos(CustomerCard.FILTER.GetFilter("Date Filter"), Format(WorkDate())) > 0, DateFilterNotResetErr); + Assert.IsTrue(StrPos(CustomerCard.FILTER.GetFilter("Date Filter"), Format(CalcDate('<3M>', LatestDate))) = 0, DateFilterLeakedErr); + + // [THEN] "Balance Due (LCY)" FlowField shown on the Customer Card reflects only the overdue invoice amount. + Assert.AreEqual(OverdueAmount, CustomerCard."Balance Due (LCY)".AsDecimal(), OverDueBalanceErr); + // [THEN] "Balance Due" cue (OverdueBalance page variable, populated by the background task) also reflects only the overdue invoice amount. + Assert.AreEqual(OverdueAmount, CustomerCard."Balance Due".AsDecimal(), OverDueBalanceErr); + // [THEN] "Balance (LCY)" on the Customer Card still sums both invoices. + Assert.AreEqual(OverdueAmount + NotOverdueAmount, CustomerCard."Balance (LCY)".AsDecimal(), BalanceLCYShouldIncludeBothInvoicesErr); + + // [THEN] The "Balance Due" overdue payments cue (OverdueBalance page variable) is populated by the "Customer Card Calculations" background task which does Customer.SetView() and then Customer.CalcOverdueBalance(). Replaying that exact call path with the Date Filter now captured on the Card returns only the overdue amount, proving the cue value is isolated from the leaked wider Date Filter. + CustomerForBackgroundTask.Get(Customer."No."); + CustomerForBackgroundTask.SetFilter("Date Filter", CustomerCard.FILTER.GetFilter("Date Filter")); + Assert.AreEqual(Round(OverdueAmount), CustomerForBackgroundTask.CalcOverdueBalance(), OverDueBalanceErr); + + CustomerCard.Close(); + end; + local procedure Initialize() var Currency: Record Currency; @@ -1242,6 +1302,21 @@ codeunit 134389 "ERM Customer Statistics" exit(SalesHeader."Amount Including VAT"); end; + local procedure CreateAndPostInvoiceWithDueDate(CustomerNo: Code[20]; PostingDate: Date; DueDate: Date) AmountInclVAT: Decimal + var + SalesHeader: Record "Sales Header"; + SalesLine: Record "Sales Line"; + begin + CreateSalesHeader(SalesHeader, SalesHeader."Document Type"::Invoice, CustomerNo, PostingDate); + SalesHeader.Validate("Due Date", DueDate); + SalesHeader.Modify(true); + LibrarySales.CreateSalesLine( + SalesLine, SalesHeader, SalesLine.Type::Item, CreateItemWithUnitPrice(), LibraryRandom.RandDec(10, 2)); + SalesHeader.CalcFields("Amount Including VAT"); + AmountInclVAT := SalesHeader."Amount Including VAT"; + LibrarySales.PostSalesDocument(SalesHeader, true, true); + end; + local procedure CreateAndPostSalesDocument(CustomerNo: Code[20]; CurrencyCode: Code[10]): Decimal var SalesHeader: Record "Sales Header";