diff --git a/src/Apps/W1/PaymentPractices/App/app.json b/src/Apps/W1/PaymentPractices/App/app.json
index 6f4428a507..72e77013eb 100644
--- a/src/Apps/W1/PaymentPractices/App/app.json
+++ b/src/Apps/W1/PaymentPractices/App/app.json
@@ -16,8 +16,8 @@
"platform": "29.0.0.0",
"idRanges": [
{
- "from": 685,
- "to": 694
+ "from": 680,
+ "to": 698
}
],
"resourceExposurePolicy": {
@@ -29,5 +29,12 @@
"target": "Cloud",
"features": [
"TranslationFile"
+ ],
+ "internalsVisibleTo": [
+ {
+ "id": "cc329ed7-8840-45f6-860b-3eb99c408998",
+ "name": "Payment Practices Test Library",
+ "publisher": "Microsoft"
+ }
]
}
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/Enums/PaymPracReportingScheme.Enum.al b/src/Apps/W1/PaymentPractices/App/src/Core/Enums/PaymPracReportingScheme.Enum.al
new file mode 100644
index 0000000000..65d275c006
--- /dev/null
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/Enums/PaymPracReportingScheme.Enum.al
@@ -0,0 +1,23 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Finance.Analysis;
+
+enum 680 "Paym. Prac. Reporting Scheme" implements PaymentPracticeSchemeHandler
+{
+ Extensible = true;
+
+ value(0; Standard)
+ {
+ Implementation = PaymentPracticeSchemeHandler = "Paym. Prac. Standard Handler";
+ }
+ value(1; "Dispute & Retention")
+ {
+ Implementation = PaymentPracticeSchemeHandler = "Paym. Prac. Dispute Ret. Hdlr";
+ }
+ value(2; "Small Business")
+ {
+ Implementation = PaymentPracticeSchemeHandler = "Paym. Prac. Small Bus. Handler";
+ }
+}
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracDisputeRetHdlr.Codeunit.al b/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracDisputeRetHdlr.Codeunit.al
new file mode 100644
index 0000000000..e8b59c3e1f
--- /dev/null
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracDisputeRetHdlr.Codeunit.al
@@ -0,0 +1,66 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Finance.Analysis;
+
+codeunit 681 "Paym. Prac. Dispute Ret. Hdlr" implements PaymentPracticeSchemeHandler
+{
+ Access = Internal;
+
+ procedure ValidateHeader(var PaymentPracticeHeader: Record "Payment Practice Header")
+ begin
+ // Dispute & Retention: no additional header type restrictions
+ end;
+
+ procedure UpdatePaymentPracData(var PaymentPracticeData: Record "Payment Practice Data"): Boolean
+ begin
+ if PaymentPracticeData."Source Type" = PaymentPracticeData."Source Type"::Vendor then
+ if PaymentPracticeData."SCF Payment Date" <> 0D then begin
+ PaymentPracticeData."Actual Payment Days" := PaymentPracticeData."SCF Payment Date" - PaymentPracticeData."Invoice Received Date";
+ if PaymentPracticeData."Actual Payment Days" < 0 then
+ PaymentPracticeData."Actual Payment Days" := 0;
+ end;
+
+ if (not PaymentPracticeData."Invoice Is Open") and
+ (PaymentPracticeData."Actual Payment Days" > PaymentPracticeData."Agreed Payment Days")
+ then
+ PaymentPracticeData."Overdue Due to Dispute" := PaymentPracticeData."Dispute Status" <> '';
+
+ exit(true);
+ end;
+
+ procedure CalculateHeaderTotals(var PaymentPracticeHeader: Record "Payment Practice Header"; var PaymentPracticeData: Record "Payment Practice Data")
+ var
+ TotalPayments: Integer;
+ TotalAmount: Decimal;
+ TotalOverdueAmount: Decimal;
+ OverdueCount: Integer;
+ OverdueDueToDisputeCount: Integer;
+ begin
+ if PaymentPracticeData.FindSet() then
+ repeat
+ if not PaymentPracticeData."Invoice Is Open" then begin
+ TotalPayments += 1;
+ TotalAmount += PaymentPracticeData."Invoice Amount";
+ if PaymentPracticeData."Actual Payment Days" > PaymentPracticeData."Agreed Payment Days" then begin
+ OverdueCount += 1;
+ TotalOverdueAmount += PaymentPracticeData."Invoice Amount";
+ if PaymentPracticeData."Overdue Due to Dispute" then
+ OverdueDueToDisputeCount += 1;
+ end;
+ end;
+ until PaymentPracticeData.Next() = 0;
+
+ PaymentPracticeHeader."Total Number of Payments" := TotalPayments;
+ PaymentPracticeHeader."Total Amount of Payments" := TotalAmount;
+ PaymentPracticeHeader."Total Amt. of Overdue Payments" := TotalOverdueAmount;
+ if OverdueCount > 0 then
+ PaymentPracticeHeader."Pct Overdue Due to Dispute" := OverdueDueToDisputeCount / OverdueCount * 100;
+ end;
+
+ procedure CalculateLineTotals(var PaymentPracticeLine: Record "Payment Practice Line"; var PaymentPracticeData: Record "Payment Practice Data")
+ begin
+ // Dispute & Retention: no additional line totals
+ end;
+}
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracPeriodAggregator.Codeunit.al b/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracPeriodAggregator.Codeunit.al
index 9c8f0aa311..146f4a4f1e 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracPeriodAggregator.Codeunit.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracPeriodAggregator.Codeunit.al
@@ -26,10 +26,12 @@ codeunit 685 "Paym. Prac. Period Aggregator" implements PaymentPracticeLinesAggr
var
PaymentPracticeLine: Record "Payment Practice Line";
PaymentPeriod: Record "Payment Period";
+ SchemeHandler: Interface PaymentPracticeSchemeHandler;
SourceType: Integer;
NextLineNo: Integer;
begin
NextLineNo := 1;
+ SchemeHandler := PaymentPracticeHeader."Reporting Scheme";
PaymentPeriod.SetCurrentKey("Days From");
PaymentPeriod.SetAscending("Days From", true);
foreach SourceType in PaymentPracticeData."Source Type".Ordinals() do begin
@@ -37,7 +39,12 @@ codeunit 685 "Paym. Prac. Period Aggregator" implements PaymentPracticeLinesAggr
if not PaymentPracticeData.IsEmpty() then
if PaymentPeriod.FindSet() then
repeat
- InsertPeriodLine(PaymentPracticeLine, PaymentPracticeData, PaymentPeriod, PaymentPracticeHeader."No.", NextLineNo);
+ InsertPeriodLine(PaymentPracticeLine, PaymentPracticeData, PaymentPeriod, PaymentPracticeHeader."No.", NextLineNo, SourceType);
+ ApplyPeriodFilter(PaymentPracticeData, PaymentPeriod);
+ SchemeHandler.CalculateLineTotals(PaymentPracticeLine, PaymentPracticeData);
+ ResetPeriodFilter(PaymentPracticeData);
+ if (PaymentPracticeLine."Invoice Count" <> 0) or (PaymentPracticeLine."Invoice Value" <> 0) then
+ PaymentPracticeLine.Modify();
until PaymentPeriod.Next() = 0;
end;
end;
@@ -47,7 +54,7 @@ codeunit 685 "Paym. Prac. Period Aggregator" implements PaymentPracticeLinesAggr
end;
- local procedure InsertPeriodLine(var PaymentPracticeLine: Record "Payment Practice Line"; var PaymentPracticeData: Record "Payment Practice Data"; PaymentPeriod: Record "Payment Period"; HeaderNo: Integer; var NextLineNo: Integer)
+ local procedure InsertPeriodLine(var PaymentPracticeLine: Record "Payment Practice Line"; var PaymentPracticeData: Record "Payment Practice Data"; PaymentPeriod: Record "Payment Period"; HeaderNo: Integer; var NextLineNo: Integer; SourceType: Integer)
begin
PaymentPracticeLine.Init();
PaymentPracticeLine."Header No." := HeaderNo;
@@ -57,10 +64,23 @@ codeunit 685 "Paym. Prac. Period Aggregator" implements PaymentPracticeLinesAggr
PaymentPracticeLine."Payment Period Code" := PaymentPeriod.Code;
PaymentPracticeLine."Payment Period Description" := PaymentPeriod.Description;
SetPercentPaidInPeriod(PaymentPracticeData, PaymentPeriod."Days From", PaymentPeriod."Days To", PaymentPracticeLine."Pct Paid in Period", PaymentPracticeLine."Pct Paid in Period (Amount)");
- PaymentPracticeLine."Source Type" := PaymentPracticeData."Source Type";
+ PaymentPracticeLine."Source Type" := "Paym. Prac. Header Type".FromInteger(SourceType);
PaymentPracticeLine.Insert();
end;
+ local procedure ApplyPeriodFilter(var PaymentPracticeData: Record "Payment Practice Data"; PaymentPeriod: Record "Payment Period")
+ begin
+ if PaymentPeriod."Days To" = 0 then
+ PaymentPracticeData.SetFilter("Actual Payment Days", '>=%1', PaymentPeriod."Days From")
+ else
+ PaymentPracticeData.SetRange("Actual Payment Days", PaymentPeriod."Days From", PaymentPeriod."Days To");
+ end;
+
+ local procedure ResetPeriodFilter(var PaymentPracticeData: Record "Payment Practice Data")
+ begin
+ PaymentPracticeData.SetRange("Actual Payment Days");
+ end;
+
local procedure SetPercentPaidInPeriod(var PaymentPracticeData: Record "Payment Practice Data"; DaysFrom: Integer; DaysTo: Integer; var PercentPaidInPeriodByNumber: Decimal; var PercentPaidInPeriodByAmount: Decimal)
var
Total: Integer;
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracSizeAggregator.Codeunit.al b/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracSizeAggregator.Codeunit.al
index 6b47f4f6a5..750c2b8352 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracSizeAggregator.Codeunit.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracSizeAggregator.Codeunit.al
@@ -15,6 +15,7 @@ codeunit 686 "Paym. Prac. Size Aggregator" implements PaymentPracticeLinesAggreg
PaymentPracticeMath: Codeunit "Payment Practice Math";
FeatureTelemetry: Codeunit "Feature Telemetry";
WrongHeaderTypeErr: Label 'Payment Practice Header Type must be Vendor for this aggregation type.';
+ WrongHeaderAggErr: Label 'Payment Practice Aggregation Type must be Period for the Small Business reporting scheme.';
procedure PrepareLayout();
var
@@ -28,9 +29,11 @@ codeunit 686 "Paym. Prac. Size Aggregator" implements PaymentPracticeLinesAggreg
var
PaymentPracticeLine: Record "Payment Practice Line";
CompanySize: Record "Company Size";
+ SchemeHandler: Interface PaymentPracticeSchemeHandler;
NextLineNo: Integer;
begin
NextLineNo := 1;
+ SchemeHandler := PaymentPracticeHeader."Reporting Scheme";
if CompanySize.FindSet() then
repeat
PaymentPracticeLine.Init();
@@ -45,9 +48,12 @@ codeunit 686 "Paym. Prac. Size Aggregator" implements PaymentPracticeLinesAggreg
PaymentPracticeLine."Average Actual Payment Period" := PaymentPracticeMath.GetAverageActualPaymentTime(PaymentPracticeData);
PaymentPracticeLine."Average Agreed Payment Period" := PaymentPracticeMath.GetAverageAgreedPaymentTime(PaymentPracticeData);
PaymentPracticeLine."Pct Paid on Time" := PaymentPracticeMath.GetPercentOfOnTimePayments(PaymentPracticeData);
- PaymentPracticeData.SetRange("Company Size Code");
PaymentPracticeLine.Insert();
+ SchemeHandler.CalculateLineTotals(PaymentPracticeLine, PaymentPracticeData);
+ if (PaymentPracticeLine."Invoice Count" <> 0) or (PaymentPracticeLine."Invoice Value" <> 0) then
+ PaymentPracticeLine.Modify();
+ PaymentPracticeData.SetRange("Company Size Code");
until CompanySize.Next() = 0;
end;
@@ -55,5 +61,7 @@ codeunit 686 "Paym. Prac. Size Aggregator" implements PaymentPracticeLinesAggreg
begin
if PaymentPracticeHeader."Header Type" in [PaymentPracticeHeader."Header Type"::Customer, PaymentPracticeHeader."Header Type"::"Vendor+Customer"] then
Error(WrongHeaderTypeErr);
+ if PaymentPracticeHeader."Reporting Scheme" = PaymentPracticeHeader."Reporting Scheme"::"Small Business" then
+ Error(WrongHeaderAggErr);
end;
-}
+}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracSmallBusHandler.Codeunit.al b/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracSmallBusHandler.Codeunit.al
new file mode 100644
index 0000000000..c086efb75d
--- /dev/null
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracSmallBusHandler.Codeunit.al
@@ -0,0 +1,99 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Finance.Analysis;
+
+using Microsoft.Purchases.Vendor;
+
+codeunit 682 "Paym. Prac. Small Bus. Handler" implements PaymentPracticeSchemeHandler
+{
+ Access = Internal;
+
+ var
+ PaymentPracticeMath: Codeunit "Payment Practice Math";
+ SmallBusinessCache: Dictionary of [Code[20], Boolean];
+ WrongHeaderTypeErr: Label 'Payment Practice Header Type must be Vendor for the Small Business reporting scheme.';
+ WrongHeaderAggErr: Label 'Payment Practice Aggregation Type must be Period for the Small Business reporting scheme.';
+
+ procedure ValidateHeader(var PaymentPracticeHeader: Record "Payment Practice Header")
+ begin
+ if PaymentPracticeHeader."Header Type" <> PaymentPracticeHeader."Header Type"::Vendor then
+ Error(WrongHeaderTypeErr);
+ if PaymentPracticeHeader."Aggregation Type" <> PaymentPracticeHeader."Aggregation Type"::Period then
+ Error(WrongHeaderAggErr);
+ end;
+
+ procedure UpdatePaymentPracData(var PaymentPracticeData: Record "Payment Practice Data"): Boolean
+ var
+ Vendor: Record Vendor;
+ CompanySize: Record "Company Size";
+ IsSmallBusiness: Boolean;
+ begin
+ if PaymentPracticeData."Source Type" <> PaymentPracticeData."Source Type"::Vendor then
+ exit(false);
+
+ if SmallBusinessCache.Get(PaymentPracticeData."CV No.", IsSmallBusiness) then
+ exit(IsSmallBusiness);
+
+ Vendor.SetLoadFields("Company Size Code");
+ if not Vendor.Get(PaymentPracticeData."CV No.") then begin
+ SmallBusinessCache.Add(PaymentPracticeData."CV No.", false);
+ exit(false);
+ end;
+
+ if CompanySize.Get(Vendor."Company Size Code") then
+ IsSmallBusiness := CompanySize."Small Business"
+ else
+ IsSmallBusiness := false;
+
+ SmallBusinessCache.Add(PaymentPracticeData."CV No.", IsSmallBusiness);
+ exit(IsSmallBusiness);
+ end;
+
+ procedure CalculateHeaderTotals(var PaymentPracticeHeader: Record "Payment Practice Header"; var PaymentPracticeData: Record "Payment Practice Data")
+ var
+ TotalCount: Integer;
+ TotalValue: Decimal;
+ ModePaymentTime: Integer;
+ ModePaymentTimeMin: Integer;
+ ModePaymentTimeMax: Integer;
+ MedianPaymentTime: Decimal;
+ P80PaymentTime: Integer;
+ P95PaymentTime: Integer;
+ PctPeppolEnabled: Decimal;
+ PctSmallBusinessPayments: Decimal;
+ begin
+ PaymentPracticeMath.CalculateHeaderStatistics(
+ PaymentPracticeData, TotalCount, TotalValue,
+ ModePaymentTime, ModePaymentTimeMin, ModePaymentTimeMax,
+ MedianPaymentTime, P80PaymentTime, P95PaymentTime,
+ PctPeppolEnabled, PctSmallBusinessPayments);
+
+ PaymentPracticeHeader."Total Number of Payments" := TotalCount;
+ PaymentPracticeHeader."Total Amount of Payments" := TotalValue;
+ PaymentPracticeHeader."Mode Payment Time" := ModePaymentTime;
+ PaymentPracticeHeader."Mode Payment Time Min." := ModePaymentTimeMin;
+ PaymentPracticeHeader."Mode Payment Time Max." := ModePaymentTimeMax;
+ PaymentPracticeHeader."Median Payment Time" := MedianPaymentTime;
+ PaymentPracticeHeader."80th Percentile Payment Time" := P80PaymentTime;
+ PaymentPracticeHeader."95th Percentile Payment Time" := P95PaymentTime;
+ PaymentPracticeHeader."Pct Peppol Enabled" := PctPeppolEnabled;
+ PaymentPracticeHeader."Pct Small Business Payments" := PctSmallBusinessPayments;
+ end;
+
+ procedure CalculateLineTotals(var PaymentPracticeLine: Record "Payment Practice Line"; var PaymentPracticeData: Record "Payment Practice Data")
+ var
+ InvoiceCount: Integer;
+ InvoiceValue: Decimal;
+ begin
+ PaymentPracticeData.SetRange("Invoice Is Open", false);
+ InvoiceCount := PaymentPracticeData.Count();
+ PaymentPracticeData.CalcSums("Invoice Amount");
+ InvoiceValue := PaymentPracticeData."Invoice Amount";
+ PaymentPracticeData.SetRange("Invoice Is Open");
+
+ PaymentPracticeLine."Invoice Count" := InvoiceCount;
+ PaymentPracticeLine."Invoice Value" := InvoiceValue;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracStandardHandler.Codeunit.al b/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracStandardHandler.Codeunit.al
new file mode 100644
index 0000000000..67e0b376c8
--- /dev/null
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/Implementations/PaymPracStandardHandler.Codeunit.al
@@ -0,0 +1,30 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Finance.Analysis;
+
+codeunit 680 "Paym. Prac. Standard Handler" implements PaymentPracticeSchemeHandler
+{
+ Access = Internal;
+
+ procedure ValidateHeader(var PaymentPracticeHeader: Record "Payment Practice Header")
+ begin
+ // Standard scheme: no additional validation
+ end;
+
+ procedure UpdatePaymentPracData(var PaymentPracticeData: Record "Payment Practice Data"): Boolean
+ begin
+ exit(true);
+ end;
+
+ procedure CalculateHeaderTotals(var PaymentPracticeHeader: Record "Payment Practice Header"; var PaymentPracticeData: Record "Payment Practice Data")
+ begin
+ // Standard scheme: no additional header totals
+ end;
+
+ procedure CalculateLineTotals(var PaymentPracticeLine: Record "Payment Practice Line"; var PaymentPracticeData: Record "Payment Practice Data")
+ begin
+ // Standard scheme: no additional line totals
+ end;
+}
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/Interfaces/PaymentPracticeSchemeHandler.Interface.al b/src/Apps/W1/PaymentPractices/App/src/Core/Interfaces/PaymentPracticeSchemeHandler.Interface.al
new file mode 100644
index 0000000000..70e7964d12
--- /dev/null
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/Interfaces/PaymentPracticeSchemeHandler.Interface.al
@@ -0,0 +1,38 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Finance.Analysis;
+
+interface PaymentPracticeSchemeHandler
+{
+ ///
+ /// Validates the Payment Practice Header before data generation.
+ ///
+ /// The header to validate.
+ procedure ValidateHeader(var PaymentPracticeHeader: Record "Payment Practice Header")
+
+ ///
+ /// Enriches or filters a Payment Practice Data row before insertion.
+ /// Returns true to include the row, false to skip it.
+ ///
+ /// The data row to enrich/filter.
+ /// True to include the row, false to skip.
+ procedure UpdatePaymentPracData(var PaymentPracticeData: Record "Payment Practice Data"): Boolean
+
+ ///
+ /// Calculates scheme-specific header totals after standard totals are generated.
+ ///
+ /// The header to update with totals.
+ /// The data to aggregate from.
+ procedure CalculateHeaderTotals(var PaymentPracticeHeader: Record "Payment Practice Header"; var PaymentPracticeData: Record "Payment Practice Data")
+
+ ///
+ /// Calculates scheme-specific line totals for the currently visible slice of data.
+ /// The caller is responsible for applying any filters (period, company size, etc.) on
+ /// PaymentPracticeData before invoking this method, and for restoring them afterwards.
+ ///
+ /// The line to update with totals.
+ /// The data to aggregate from. Filters set by the caller define the slice.
+ procedure CalculateLineTotals(var PaymentPracticeLine: Record "Payment Practice Line"; var PaymentPracticeData: Record "Payment Practice Data")
+}
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPracticeBuilders.Codeunit.al b/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPracticeBuilders.Codeunit.al
index fc3d2ab1bf..8b73e380e4 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPracticeBuilders.Codeunit.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPracticeBuilders.Codeunit.al
@@ -17,9 +17,15 @@ codeunit 688 "Payment Practice Builders"
var
Vendor: Record Vendor;
VendorLedgerEntry: Record "Vendor Ledger Entry";
+ SchemeHandler: Interface PaymentPracticeSchemeHandler;
LastVendNo: Code[20];
begin
+ SchemeHandler := PaymentPracticeHeader."Reporting Scheme";
LastVendNo := '';
+ Vendor.SetLoadFields("No.", "Exclude from Pmt. Practices", "Company Size Code");
+ VendorLedgerEntry.SetLoadFields(
+ "Entry No.", "Vendor No.", "External Document No.", "Document No.", "Posting Date", "Invoice Received Date", "Document Date",
+ "Due Date", Open, "Closed at Date", "Closed by Entry No.", "SCF Payment Date", "Dispute Status");
VendorLedgerEntry.SetCurrentKey("Vendor No.");
VendorLedgerEntry.SetRange("Document Type", VendorLedgerEntry."Document Type"::Invoice);
VendorLedgerEntry.SetRange("Posting Date", PaymentPracticeHeader."Starting Date", PaymentPracticeHeader."Ending Date");
@@ -39,7 +45,8 @@ codeunit 688 "Payment Practice Builders"
PaymentPracticeData."Header No." := PaymentPracticeHeader."No.";
PaymentPracticeData.CopyFromInvoiceVendLedgEntry(VendorLedgerEntry);
PaymentPracticeData."Company Size Code" := Vendor."Company Size Code";
- PaymentPracticeData.Insert();
+ if SchemeHandler.UpdatePaymentPracData(PaymentPracticeData) then
+ PaymentPracticeData.Insert();
end;
until VendorLedgerEntry.Next() = 0;
end;
@@ -48,9 +55,15 @@ codeunit 688 "Payment Practice Builders"
var
Customer: Record Customer;
CustLedgerEntry: Record "Cust. Ledger Entry";
+ SchemeHandler: Interface PaymentPracticeSchemeHandler;
LastCustNo: Code[20];
begin
+ SchemeHandler := PaymentPracticeHeader."Reporting Scheme";
LastCustNo := '';
+ Customer.SetLoadFields("No.", "Exclude from Pmt. Practices");
+ CustLedgerEntry.SetLoadFields(
+ "Entry No.", "Customer No.", "External Document No.", "Document No.", "Posting Date", "Document Date",
+ "Due Date", Open, "Closed at Date", "Closed by Entry No.", "Dispute Status");
CustLedgerEntry.SetCurrentKey("Customer No.");
CustLedgerEntry.SetRange("Document Type", CustLedgerEntry."Document Type"::Invoice);
CustLedgerEntry.SetRange("Posting Date", PaymentPracticeHeader."Starting Date", PaymentPracticeHeader."Ending Date");
@@ -69,7 +82,8 @@ codeunit 688 "Payment Practice Builders"
PaymentPracticeData.Init();
PaymentPracticeData."Header No." := PaymentPracticeHeader."No.";
PaymentPracticeData.CopyFromInvoiceCustLedgEntry(CustLedgerEntry);
- PaymentPracticeData.Insert();
+ if SchemeHandler.UpdatePaymentPracData(PaymentPracticeData) then
+ PaymentPracticeData.Insert();
end;
until CustLedgerEntry.Next() = 0;
end;
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPracticeMath.Codeunit.al b/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPracticeMath.Codeunit.al
index f410a7c11c..7af4253f14 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPracticeMath.Codeunit.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPracticeMath.Codeunit.al
@@ -4,6 +4,8 @@
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Finance.Analysis;
+using Microsoft.Purchases.Vendor;
+
codeunit 693 "Payment Practice Math"
{
Access = internal;
@@ -72,4 +74,249 @@ codeunit 693 "Payment Practice Math"
foreach Number in List do
Total += Number;
end;
-}
+
+ ///
+ /// Aggregates all closed-invoice statistics required for a Small Business reporting-scheme header in a single
+ /// pass over the filtered set. This consolidates what previously required
+ /// 5+ independent full-table scans (mode, per-vendor mode min/max, median/P80/P95, % Peppol enabled, % small
+ /// business payments) into one iteration plus one small in-memory post-processing step.
+ ///
+ /// The payment practice data to evaluate. Filters are temporarily adjusted but restored before returning.
+ /// Output: number of closed invoice rows.
+ /// Output: sum of "Invoice Amount" across closed invoices.
+ /// Output: most frequent "Actual Payment Days" across all closed invoices.
+ /// Output: smallest per-vendor mode of "Actual Payment Days".
+ /// Output: largest per-vendor mode of "Actual Payment Days".
+ /// Output: median of "Actual Payment Days" across closed invoices.
+ /// Output: 80th percentile of "Actual Payment Days".
+ /// Output: 95th percentile of "Actual Payment Days".
+ /// Output: percentage of closed-invoice rows whose vendor has a GLN.
+ /// Output: percentage of closed-invoice value attributable to small-business vendors.
+ procedure CalculateHeaderStatistics(var PaymentPracticeData: Record "Payment Practice Data"; var TotalCount: Integer; var TotalValue: Decimal; var ModePaymentTime: Integer; var ModePaymentTimeMin: Integer; var ModePaymentTimeMax: Integer; var MedianPaymentTime: Decimal; var P80PaymentTime: Integer; var P95PaymentTime: Integer; var PctPeppolEnabled: Decimal; var PctSmallBusinessPayments: Decimal)
+ var
+ Vendor: Record Vendor;
+ CompanySize: Record "Company Size";
+ AllPaymentTimes: List of [Integer];
+ PerVendorTimes: Dictionary of [Code[20], List of [Integer]];
+ VendorTimes: List of [Integer];
+ ModesPerVendor: List of [Integer];
+ VendorGLNCache: Dictionary of [Code[20], Boolean];
+ SmallBusinessCache: Dictionary of [Code[20], Boolean];
+ HasGLN: Boolean;
+ IsSmallBusiness: Boolean;
+ PeppolCount: Integer;
+ SmallBusinessValue: Decimal;
+ PaymentTime: Integer;
+ CVNo: Code[20];
+ CompanySizeCode: Code[20];
+ begin
+ TotalCount := 0;
+ TotalValue := 0;
+ ModePaymentTime := 0;
+ ModePaymentTimeMin := 0;
+ ModePaymentTimeMax := 0;
+ MedianPaymentTime := 0;
+ P80PaymentTime := 0;
+ P95PaymentTime := 0;
+ PctPeppolEnabled := 0;
+ PctSmallBusinessPayments := 0;
+
+ PaymentPracticeData.SetRange("Invoice Is Open", false);
+ if PaymentPracticeData.FindSet() then
+ repeat
+ TotalCount += 1;
+ TotalValue += PaymentPracticeData."Invoice Amount";
+
+ PaymentTime := PaymentPracticeData."Actual Payment Days";
+ AllPaymentTimes.Add(PaymentTime);
+
+ CVNo := PaymentPracticeData."CV No.";
+ if PerVendorTimes.Get(CVNo, VendorTimes) then begin
+ VendorTimes.Add(PaymentTime);
+ PerVendorTimes.Set(CVNo, VendorTimes);
+ end else begin
+ Clear(VendorTimes);
+ VendorTimes.Add(PaymentTime);
+ PerVendorTimes.Add(CVNo, VendorTimes);
+ end;
+
+ // Peppol enabled (vendor GLN), cached per vendor
+ if not VendorGLNCache.Get(CVNo, HasGLN) then begin
+ Vendor.SetLoadFields(GLN);
+ HasGLN := Vendor.Get(CVNo) and (Vendor.GLN <> '');
+ VendorGLNCache.Add(CVNo, HasGLN);
+ end;
+ if HasGLN then
+ PeppolCount += 1;
+
+ // Small business value, using "Company Size Code" already stored on the data row (cached per code)
+ CompanySizeCode := PaymentPracticeData."Company Size Code";
+ if not SmallBusinessCache.Get(CompanySizeCode, IsSmallBusiness) then begin
+ IsSmallBusiness := (CompanySizeCode <> '') and CompanySize.Get(CompanySizeCode) and CompanySize."Small Business";
+ SmallBusinessCache.Add(CompanySizeCode, IsSmallBusiness);
+ end;
+ if IsSmallBusiness then
+ SmallBusinessValue += PaymentPracticeData."Invoice Amount";
+ until PaymentPracticeData.Next() = 0;
+ PaymentPracticeData.SetRange("Invoice Is Open");
+
+ if AllPaymentTimes.Count() > 0 then begin
+ ModePaymentTime := MostFrequentValue(AllPaymentTimes);
+ SortIntegerList(AllPaymentTimes);
+ MedianPaymentTime := MedianFromSorted(AllPaymentTimes);
+ P80PaymentTime := PercentileFromSorted(AllPaymentTimes, 80);
+ P95PaymentTime := PercentileFromSorted(AllPaymentTimes, 95);
+ end;
+
+ foreach CVNo in PerVendorTimes.Keys() do begin
+ VendorTimes := PerVendorTimes.Get(CVNo);
+ ModesPerVendor.Add(MostFrequentValue(VendorTimes));
+ end;
+ ModePaymentTimeMin := MinOfList(ModesPerVendor);
+ ModePaymentTimeMax := MaxOfList(ModesPerVendor);
+
+ if TotalCount > 0 then
+ PctPeppolEnabled := PeppolCount / TotalCount * 100;
+ if TotalValue <> 0 then
+ PctSmallBusinessPayments := SmallBusinessValue / TotalValue * 100;
+ end;
+
+ ///
+ /// Calculates the median value from a list of integers that has already been sorted in ascending order.
+ /// For lists with an even number of elements, returns the average of the two middle values.
+ ///
+ /// The sorted list of integers to evaluate. Must be sorted in ascending order and contain at least one element.
+ /// The median value as a decimal.
+ local procedure MedianFromSorted(var SortedList: List of [Integer]): Decimal
+ var
+ MiddleIndex: Integer;
+ begin
+ MiddleIndex := SortedList.Count() div 2;
+ if SortedList.Count() mod 2 = 0 then
+ exit((SortedList.Get(MiddleIndex) + SortedList.Get(MiddleIndex + 1)) / 2)
+ else
+ exit(SortedList.Get(MiddleIndex + 1));
+ end;
+
+ ///
+ /// Returns the value at the specified percentile from a list of integers that has already been sorted in ascending order.
+ /// The index is clamped to the valid range [1, Count].
+ ///
+ /// The sorted list of integers to evaluate. Must be sorted in ascending order and contain at least one element.
+ /// The percentile to compute (e.g. 80 for the 80th percentile).
+ /// The integer value at the specified percentile.
+ local procedure PercentileFromSorted(var SortedList: List of [Integer]; P: Integer): Integer
+ var
+ Index: Integer;
+ begin
+ Index := SortedList.Count() * P div 100;
+ if Index < 1 then
+ Index := 1;
+ if Index > SortedList.Count() then
+ Index := SortedList.Count();
+ exit(SortedList.Get(Index));
+ end;
+
+ ///
+ /// Returns the smallest value in the supplied integer list.
+ ///
+ /// The list of integers to evaluate.
+ /// The minimum value in the list, or 0 if the list is empty.
+ local procedure MinOfList(var List: List of [Integer]): Integer
+ var
+ Value: Integer;
+ MinValue: Integer;
+ begin
+ if List.Count() = 0 then
+ exit(0);
+
+ MinValue := List.Get(1);
+ foreach Value in List do
+ if Value < MinValue then
+ MinValue := Value;
+
+ exit(MinValue);
+ end;
+
+ ///
+ /// Returns the largest value in the supplied integer list.
+ ///
+ /// The list of integers to evaluate.
+ /// The maximum value in the list, or 0 if the list is empty.
+ local procedure MaxOfList(var List: List of [Integer]): Integer
+ var
+ Value: Integer;
+ MaxValue: Integer;
+ begin
+ if List.Count() = 0 then
+ exit(0);
+
+ MaxValue := List.Get(1);
+
+ foreach Value in List do
+ if Value > MaxValue then
+ MaxValue := Value;
+
+ exit(MaxValue);
+ end;
+
+ ///
+ /// Returns the most frequently occurring value (statistical mode) in the supplied integer list.
+ /// When several values share the highest frequency, the smallest of those values is returned for deterministic behavior.
+ ///
+ /// The list of integers to evaluate.
+ /// The most frequent value, or 0 if the list is empty.
+ local procedure MostFrequentValue(var List: List of [Integer]): Integer
+ var
+ ValueFrequencies: Dictionary of [Integer, Integer];
+ CurrentValue: Integer;
+ CurrentFrequency: Integer;
+ HighestFrequency: Integer;
+ MostFrequent: Integer;
+ begin
+ if List.Count() = 0 then
+ exit(0);
+
+ foreach CurrentValue in List do
+ if ValueFrequencies.ContainsKey(CurrentValue) then
+ ValueFrequencies.Set(CurrentValue, ValueFrequencies.Get(CurrentValue) + 1)
+ else
+ ValueFrequencies.Add(CurrentValue, 1);
+
+ HighestFrequency := 0;
+ MostFrequent := 0;
+ foreach CurrentValue in ValueFrequencies.Keys() do begin
+ CurrentFrequency := ValueFrequencies.Get(CurrentValue);
+ if (CurrentFrequency > HighestFrequency) or ((CurrentFrequency = HighestFrequency) and (CurrentValue < MostFrequent)) then begin
+ HighestFrequency := CurrentFrequency;
+ MostFrequent := CurrentValue;
+ end;
+ end;
+
+ exit(MostFrequent);
+ end;
+
+ ///
+ /// Sorts the supplied integer list in ascending order in place using a simple insertion sort.
+ ///
+ /// The list of integers to sort. Modified in place.
+ local procedure SortIntegerList(var List: List of [Integer])
+ var
+ i: Integer;
+ j: Integer;
+ CurrentValue: Integer;
+ begin
+ // Insertion sort, O(n^2) worst case
+ for i := 2 to List.Count() do begin
+ CurrentValue := List.Get(i);
+ j := i - 1;
+ while j >= 1 do begin
+ if List.Get(j) <= CurrentValue then
+ break;
+ List.Set(j + 1, List.Get(j));
+ j -= 1;
+ end;
+ List.Set(j + 1, CurrentValue);
+ end;
+ end;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPractices.Codeunit.al b/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPractices.Codeunit.al
index 1298d87e81..05ab1297c4 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPractices.Codeunit.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/PaymentPractices.Codeunit.al
@@ -4,6 +4,8 @@
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Finance.Analysis;
+using System.Environment;
+
codeunit 689 "Payment Practices"
{
var
@@ -12,9 +14,14 @@ codeunit 689 "Payment Practices"
procedure Generate(var PaymentPracticeHeader: Record "Payment Practice Header") DataIsNotEmpty: Boolean
var
PaymentPracticeData: Record "Payment Practice Data";
+ SchemeHandler: Interface PaymentPracticeSchemeHandler;
begin
PaymentPracticeHeader.TestField("Starting Date");
PaymentPracticeHeader.TestField("Ending Date");
+
+ SchemeHandler := PaymentPracticeHeader."Reporting Scheme";
+ SchemeHandler.ValidateHeader(PaymentPracticeHeader);
+
PaymentPracticeData.Reset();
PaymentPracticeData.SetRange("Header No.", PaymentPracticeHeader."No.");
PaymentPracticeData.DeleteAll();
@@ -29,10 +36,27 @@ codeunit 689 "Payment Practices"
end;
local procedure GenerateTotals(var PaymentPracticeData: Record "Payment Practice Data"; var PaymentPracticeHeader: Record "Payment Practice Header")
+ var
+ SchemeHandler: Interface PaymentPracticeSchemeHandler;
begin
PaymentPracticeHeader."Average Actual Payment Period" := PaymentPracticeMath.GetAverageActualPaymentTime(PaymentPracticeData);
PaymentPracticeHeader."Average Agreed Payment Period" := PaymentPracticeMath.GetAverageAgreedPaymentTime(PaymentPracticeData);
PaymentPracticeHeader."Pct Paid on Time" := PaymentPracticeMath.GetPercentOfOnTimePayments(PaymentPracticeData);
+
+ // Reset fields before calculating scheme-specific totals
+ PaymentPracticeHeader."Mode Payment Time" := 0;
+ PaymentPracticeHeader."Mode Payment Time Min." := 0;
+ PaymentPracticeHeader."Mode Payment Time Max." := 0;
+ PaymentPracticeHeader."Median Payment Time" := 0;
+ PaymentPracticeHeader."80th Percentile Payment Time" := 0;
+ PaymentPracticeHeader."95th Percentile Payment Time" := 0;
+ PaymentPracticeHeader."Pct Peppol Enabled" := 0;
+ PaymentPracticeHeader."Pct Small Business Payments" := 0;
+
+ PaymentPracticeData.Reset();
+ PaymentPracticeData.SetRange("Header No.", PaymentPracticeHeader."No.");
+ SchemeHandler := PaymentPracticeHeader."Reporting Scheme";
+ SchemeHandler.CalculateHeaderTotals(PaymentPracticeHeader, PaymentPracticeData);
end;
local procedure GenerateData(var PaymentPracticeData: Record "Payment Practice Data"; PaymentPracticeHeader: Record "Payment Practice Header"; PaymentPracticeDataGenerator: Interface PaymentPracticeDataGenerator)
@@ -44,4 +68,29 @@ codeunit 689 "Payment Practices"
begin
PaymentPracticeLinesAggregator.GenerateLines(PaymentPracticeData, PaymentPracticeHeader);
end;
+
+ procedure DetectReportingScheme(): Enum "Paym. Prac. Reporting Scheme"
+ var
+ EnvironmentInformation: Codeunit "Environment Information";
+ ReportingScheme: Enum "Paym. Prac. Reporting Scheme";
+ IsHandled: Boolean;
+ begin
+ OnBeforeDetectReportingScheme(ReportingScheme, IsHandled);
+ if IsHandled then
+ exit(ReportingScheme);
+
+ case EnvironmentInformation.GetApplicationFamily() of
+ 'GB':
+ exit("Paym. Prac. Reporting Scheme"::"Dispute & Retention");
+ 'AU', 'NZ':
+ exit("Paym. Prac. Reporting Scheme"::"Small Business");
+ else
+ exit("Paym. Prac. Reporting Scheme"::Standard);
+ end;
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnBeforeDetectReportingScheme(var ReportingScheme: Enum "Paym. Prac. Reporting Scheme"; var IsHandled: Boolean)
+ begin
+ end;
}
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/Permissions/PaymPracObjects.PermissionSet.al b/src/Apps/W1/PaymentPractices/App/src/Core/Permissions/PaymPracObjects.PermissionSet.al
index 1971adb6ff..e927dc130d 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Core/Permissions/PaymPracObjects.PermissionSet.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/Permissions/PaymPracObjects.PermissionSet.al
@@ -25,6 +25,9 @@ permissionset 685 "Paym. Prac. Objects"
codeunit "Paym. Prac. Period Aggregator" = X,
codeunit "Paym. Prac. Size Aggregator" = X,
codeunit "Paym. Prac. Vendor Generator" = X,
+ codeunit "Paym. Prac. Standard Handler" = X,
+ codeunit "Paym. Prac. Dispute Ret. Hdlr" = X,
+ codeunit "Paym. Prac. Small Bus. Handler" = X,
codeunit "Install Payment Practices" = X,
codeunit "Payment Practice Builders" = X,
codeunit "Payment Practice Math" = X,
diff --git a/src/Apps/W1/PaymentPractices/App/src/Core/UpgradePaymentPractices.Codeunit.al b/src/Apps/W1/PaymentPractices/App/src/Core/UpgradePaymentPractices.Codeunit.al
new file mode 100644
index 0000000000..354fe5907f
--- /dev/null
+++ b/src/Apps/W1/PaymentPractices/App/src/Core/UpgradePaymentPractices.Codeunit.al
@@ -0,0 +1,52 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Finance.Analysis;
+
+using System.Upgrade;
+
+codeunit 683 "Upgrade Payment Practices"
+{
+ Access = Internal;
+ Subtype = Upgrade;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+ Permissions = tabledata "Payment Practice Header" = RM;
+
+ var
+ UpgradeTag: Codeunit "Upgrade Tag";
+
+ trigger OnUpgradePerCompany()
+ begin
+ BackfillReportingScheme();
+ end;
+
+ local procedure BackfillReportingScheme()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ PaymentPractices: Codeunit "Payment Practices";
+ ReportingScheme: Enum "Paym. Prac. Reporting Scheme";
+ begin
+ if UpgradeTag.HasUpgradeTag(GetReportingSchemeUpgradeTag()) then
+ exit;
+
+ ReportingScheme := PaymentPractices.DetectReportingScheme();
+
+ PaymentPracticeHeader.SetRange("Reporting Scheme", 0);
+ PaymentPracticeHeader.ModifyAll("Reporting Scheme", ReportingScheme);
+
+ UpgradeTag.SetUpgradeTag(GetReportingSchemeUpgradeTag());
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Upgrade Tag", 'OnGetPerCompanyUpgradeTags', '', false, false)]
+ local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]])
+ begin
+ PerCompanyUpgradeTags.Add(GetReportingSchemeUpgradeTag());
+ end;
+
+ local procedure GetReportingSchemeUpgradeTag(): Code[250]
+ begin
+ exit('MS-597313-PaymPracReportingScheme-20260513');
+ end;
+}
diff --git a/src/Apps/W1/PaymentPractices/App/src/Pages/PaymPracVendLedgEntr.PageExt.al b/src/Apps/W1/PaymentPractices/App/src/Pages/PaymPracVendLedgEntr.PageExt.al
new file mode 100644
index 0000000000..2221689cc0
--- /dev/null
+++ b/src/Apps/W1/PaymentPractices/App/src/Pages/PaymPracVendLedgEntr.PageExt.al
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Finance.Analysis;
+
+using Microsoft.Purchases.Payables;
+
+pageextension 681 "Paym. Prac. Vend. Ledg. Entr." extends "Vendor Ledger Entries"
+{
+ layout
+ {
+ addafter("Invoice Received Date")
+ {
+ field("SCF Payment Date"; Rec."SCF Payment Date")
+ {
+ ApplicationArea = Basic, Suite;
+ Visible = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPeriods.Page.al b/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPeriods.Page.al
index 1fdd261dcb..9c7826b096 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPeriods.Page.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPeriods.Page.al
@@ -4,6 +4,8 @@
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Finance.Analysis;
+using System.Utilities;
+
page 685 "Payment Periods"
{
ApplicationArea = Basic, Suite;
@@ -42,6 +44,36 @@ page 685 "Payment Periods"
actions
{
+ area(Processing)
+ {
+ action(RestoreDefaults)
+ {
+ Caption = 'Restore Default Periods';
+ ToolTip = 'Deletes all payment periods and restores the default periods for the current environment.';
+ Image = Restore;
+
+ trigger OnAction()
+ var
+ PaymentPeriod: Record "Payment Period";
+ ConfirmManagement: Codeunit "Confirm Management";
+ begin
+ if not ConfirmManagement.GetResponseOrDefault(RestoreDefaultsQst, false) then
+ exit;
+
+ PaymentPeriod.DeleteAll();
+ PaymentPeriod.SetupDefaults();
+ CurrPage.Update(false);
+ end;
+ }
+ }
+ area(Promoted)
+ {
+ actionref(RestoreDefaults_Promoted; RestoreDefaults)
+ {
+ }
+ }
}
-}
+ var
+ RestoreDefaultsQst: Label 'This will replace all payment periods with the default periods for your environment. Do you want to continue?';
+}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeCard.Page.al b/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeCard.Page.al
index c899463801..51334192cd 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeCard.Page.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeCard.Page.al
@@ -4,6 +4,8 @@
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Finance.Analysis;
+using Microsoft.Foundation.Reporting;
+using Microsoft.Purchases.Payables;
using System.Telemetry;
using System.Utilities;
@@ -25,6 +27,10 @@ page 687 "Payment Practice Card"
{
ToolTip = 'Specifies the number of the payment practice header.';
}
+ field("Reporting Scheme"; Rec."Reporting Scheme")
+ {
+ Visible = false;
+ }
field("Aggregation Type"; Rec."Aggregation Type")
{
ToolTip = 'Specifies the aggregation type of the payment practice.';
@@ -35,6 +41,7 @@ page 687 "Payment Practice Card"
}
field("Startind Date"; Rec."Starting Date")
{
+ Caption = 'Starting Date';
ToolTip = 'Specifies the starting date of the payment practice.';
}
field("Ending Date"; Rec."Ending Date")
@@ -65,7 +72,7 @@ page 687 "Payment Practice Card"
}
group("Statistics")
{
- Caption = 'Statistics';
+ Caption = 'Payment Statistics';
field("Average Agreed Payment Period"; Rec."Average Agreed Payment Period")
{
ToolTip = 'Specifies the average agreed payment period.';
@@ -93,6 +100,87 @@ page 687 "Payment Practice Card"
ShowHeaderDataLines();
end;
}
+ group("Dispute and Retention")
+ {
+ Caption = 'Dispute and Retention';
+ Visible = Rec."Reporting Scheme" = Rec."Reporting Scheme"::"Dispute & Retention";
+
+ field("Total Number of Payments"; Rec."Total Number of Payments")
+ {
+ }
+ field("Total Amount of Payments"; Rec."Total Amount of Payments")
+ {
+ }
+ field("Total Amt. of Overdue Payments"; Rec."Total Amt. of Overdue Payments")
+ {
+ }
+ field("Pct Overdue Due to Dispute"; Rec."Pct Overdue Due to Dispute")
+ {
+ }
+ }
+ group("Small Business Scheme")
+ {
+ Caption = 'Small Business Scheme';
+ Visible = Rec."Reporting Scheme" = Rec."Reporting Scheme"::"Small Business";
+
+ field("Mode Payment Time"; Rec."Mode Payment Time")
+ {
+ trigger OnDrillDown()
+ begin
+ ShowHeaderDataLines();
+ end;
+ }
+
+ field("Mode Payment Time Min."; Rec."Mode Payment Time Min.")
+ {
+ trigger OnDrillDown()
+ begin
+ ShowHeaderDataLines();
+ end;
+ }
+ field("Mode Payment Time Max."; Rec."Mode Payment Time Max.")
+ {
+ trigger OnDrillDown()
+ begin
+ ShowHeaderDataLines();
+ end;
+ }
+ field("Median Payment Time"; Rec."Median Payment Time")
+ {
+ trigger OnDrillDown()
+ begin
+ ShowHeaderDataLines();
+ end;
+ }
+ field("80th Percentile Payment Time"; Rec."80th Percentile Payment Time")
+ {
+ trigger OnDrillDown()
+ begin
+ ShowHeaderDataLines();
+ end;
+ }
+ field("95th Percentile Payment Time"; Rec."95th Percentile Payment Time")
+ {
+ trigger OnDrillDown()
+ begin
+ ShowHeaderDataLines();
+ end;
+ }
+ field("Pct Peppol Enabled"; Rec."Pct Peppol Enabled")
+ {
+ trigger OnDrillDown()
+ begin
+ ShowHeaderDataLines();
+ end;
+ }
+ field("Pct Small Business Payments"; Rec."Pct Small Business Payments")
+ {
+ trigger OnDrillDown()
+ begin
+ ShowVendorInvoicesInReportPeriod();
+ end;
+ }
+ }
}
part(Lines; "Payment Practice Lines")
{
@@ -138,7 +226,7 @@ page 687 "Payment Practice Card"
trigger OnAction()
begin
- PrepareLayout(Rec."Aggregation Type");
+ PrepareLayout(Rec."Aggregation Type", Rec."Reporting Scheme");
Rec.SetRecFilter();
Report.Run(Report::"Payment Practice", false, true, Rec);
FeatureTelemetry.LogUptake('0000KSV', 'Payment Practices', "Feature Uptake Status"::Used);
@@ -158,7 +246,12 @@ page 687 "Payment Practice Card"
trigger OnOpenPage()
begin
+ UpdateVisibility();
CurrPage.Update();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
UpdateVisibility();
end;
@@ -167,9 +260,15 @@ page 687 "Payment Practice Card"
LinesWillBeDeletedQst: Label 'All previously generated lines will be deleted. Do you want to continue?';
NoEntriesFoundMsg: Label 'The payment practice generator found no entries corresponding to the header type, starting and ending date.';
- local procedure PrepareLayout(PaymentPracticeLinesAggregator: Interface PaymentPracticeLinesAggregator)
+ local procedure PrepareLayout(PaymentPracticeLinesAggregator: Interface PaymentPracticeLinesAggregator; ReportingScheme: Enum "Paym. Prac. Reporting Scheme")
+ var
+ DesignTimeReportSelection: Codeunit "Design-time Report Selection";
begin
PaymentPracticeLinesAggregator.PrepareLayout();
+ if ReportingScheme = ReportingScheme::"Small Business" then begin
+ DesignTimeReportSelection.SetSelectedLayout('PaymentPractice_SmallBusinessLayout');
+ FeatureTelemetry.LogUsage('0000KSU', 'Payment Practices', 'Small Business layout used.');
+ end;
end;
local procedure ShowHeaderDataLines()
@@ -180,9 +279,17 @@ page 687 "Payment Practice Card"
Page.RunModal(Page::"Payment Practice Data List", PaymentPracticeData);
end;
+ local procedure ShowVendorInvoicesInReportPeriod()
+ var
+ VendorLedgerEntry: Record "Vendor Ledger Entry";
+ begin
+ VendorLedgerEntry.SetRange("Document Type", VendorLedgerEntry."Document Type"::Invoice);
+ VendorLedgerEntry.SetRange("Posting Date", Rec."Starting Date", Rec."Ending Date");
+ Page.RunModal(Page::"Vendor Ledger Entries", VendorLedgerEntry);
+ end;
+
local procedure UpdateVisibility()
begin
CurrPage.Lines.Page.UpdateVisibility(Rec."Aggregation Type", Rec."Header Type");
- CurrPage.Update();
end;
-}
+}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeDataList.Page.al b/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeDataList.Page.al
index b3c4ef342b..7e0f4f2a2d 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeDataList.Page.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeDataList.Page.al
@@ -4,6 +4,8 @@
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Finance.Analysis;
+using Microsoft.Purchases.Vendor;
+
page 686 "Payment Practice Data List"
{
ApplicationArea = All;
@@ -25,7 +27,7 @@ page 686 "Payment Practice Data List"
}
field("Payment Entry No."; Rec."Pmt. Entry No.")
{
- ToolTip = 'Specifies the closing payment entry number that is associated with the source invoicy entry, if any was applied.';
+ ToolTip = 'Specifies the closing payment entry number that is associated with the source invoice entry, if any was applied.';
}
field("Invoice Posting Date"; Rec."Invoice Posting Date")
{
@@ -41,7 +43,7 @@ page 686 "Payment Practice Data List"
}
field("Pmt. Posting Date"; Rec."Pmt. Posting Date")
{
- ToolTip = 'Specifies the posting date of the payment entry that is associated with the source invoicy entry, if any was applied.';
+ ToolTip = 'Specifies the posting date of the payment entry that is associated with the source invoice entry, if any was applied.';
}
field("Invoice Is Open"; Rec."Invoice Is Open")
{
@@ -55,6 +57,20 @@ page 686 "Payment Practice Data List"
{
ToolTip = 'Specifies the company size code of the vendor that is the source for this entry.';
}
+ field("Small Business"; IsSmallBusiness)
+ {
+ Caption = 'Small Business';
+ Visible = false;
+ Editable = false;
+ ToolTip = 'Specifies whether the vendor is classified as a small business.';
+ }
+ field("PEPPOL Enabled"; IsPeppolEnabled)
+ {
+ Caption = 'PEPPOL Enabled';
+ Visible = false;
+ Editable = false;
+ ToolTip = 'Specifies whether the vendor has a GLN and is PEPPOL enabled.';
+ }
field("Agreed Payment Days"; Rec."Agreed Payment Days")
{
ToolTip = 'Specifies the number of days that was the agreed period for payment for the invoice.';
@@ -65,7 +81,46 @@ page 686 "Payment Practice Data List"
Style = Unfavorable;
StyleExpr = Rec."Actual Payment Days" > Rec."Agreed Payment Days";
}
+ field("Dispute Status"; Rec."Dispute Status")
+ {
+ Visible = false;
+ }
+ field("Overdue Due to Dispute"; Rec."Overdue Due to Dispute")
+ {
+ Visible = false;
+ }
+ field("SCF Payment Date"; Rec."SCF Payment Date")
+ {
+ Visible = false;
+ }
}
}
}
-}
+
+ trigger OnAfterGetRecord()
+ var
+ CompanySize: Record "Company Size";
+ Vendor: Record Vendor;
+ begin
+ IsSmallBusiness := false;
+ if not CompanySizeCache.Get(Rec."Company Size Code", IsSmallBusiness) then begin
+ if CompanySize.Get(Rec."Company Size Code") then
+ IsSmallBusiness := CompanySize."Small Business";
+ CompanySizeCache.Add(Rec."Company Size Code", IsSmallBusiness);
+ end;
+
+ IsPeppolEnabled := false;
+ if not VendorGLNCache.Get(Rec."CV No.", IsPeppolEnabled) then begin
+ Vendor.SetLoadFields(GLN);
+ if Vendor.Get(Rec."CV No.") then
+ IsPeppolEnabled := Vendor.GLN <> '';
+ VendorGLNCache.Add(Rec."CV No.", IsPeppolEnabled);
+ end;
+ end;
+
+ var
+ CompanySizeCache: Dictionary of [Code[20], Boolean];
+ VendorGLNCache: Dictionary of [Code[20], Boolean];
+ IsSmallBusiness: Boolean;
+ IsPeppolEnabled: Boolean;
+}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeLines.Page.al b/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeLines.Page.al
index 90f6b89a53..59de6bb343 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeLines.Page.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Pages/PaymentPracticeLines.Page.al
@@ -93,6 +93,14 @@ page 688 "Payment Practice Lines"
Editable = false;
ToolTip = 'Specifies whether the line has been modified manually.';
}
+ field("Invoice Count"; Rec."Invoice Count")
+ {
+ Editable = false;
+ }
+ field("Invoice Value"; Rec."Invoice Value")
+ {
+ Editable = false;
+ }
}
}
}
diff --git a/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice Small Business.docx b/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice Small Business.docx
new file mode 100644
index 0000000000..d04a6fccd5
Binary files /dev/null and b/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice Small Business.docx differ
diff --git a/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice by Period.docx b/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice by Period.docx
index 8bcbadc684..f7bb36940f 100644
Binary files a/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice by Period.docx and b/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice by Period.docx differ
diff --git a/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice by Vendor Size.docx b/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice by Vendor Size.docx
index e57cebc5f9..f9ffa1aa90 100644
Binary files a/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice by Vendor Size.docx and b/src/Apps/W1/PaymentPractices/App/src/Reports/Payment Practice by Vendor Size.docx differ
diff --git a/src/Apps/W1/PaymentPractices/App/src/Reports/PaymentPractice.Report.al b/src/Apps/W1/PaymentPractices/App/src/Reports/PaymentPractice.Report.al
index 26b02209ed..310eda630e 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Reports/PaymentPractice.Report.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Reports/PaymentPractice.Report.al
@@ -29,6 +29,22 @@ report 685 "Payment Practice"
column(Average_Actual_Payment_Period_Caption; FieldCaption("Average Actual Payment Period")) { }
column(Pct_Paid_on_Time; "Pct Paid on Time") { }
column(Pct_Paid_on_Time_Caption; FieldCaption("Pct Paid on Time")) { }
+ column(Mode_Payment_Time; "Mode Payment Time") { }
+ column(Mode_Payment_Time_Caption; FieldCaption("Mode Payment Time")) { }
+ column(Mode_Payment_Time_Min; "Mode Payment Time Min.") { }
+ column(Mode_Payment_Time_Min_Caption; FieldCaption("Mode Payment Time Min.")) { }
+ column(Mode_Payment_Time_Max; "Mode Payment Time Max.") { }
+ column(Mode_Payment_Time_Max_Caption; FieldCaption("Mode Payment Time Max.")) { }
+ column(Median_Payment_Time; "Median Payment Time") { }
+ column(Median_Payment_Time_Caption; FieldCaption("Median Payment Time")) { }
+ column(Percentile_80th_Payment_Time; "80th Percentile Payment Time") { }
+ column(Percentile_80th_Payment_Time_Caption; FieldCaption("80th Percentile Payment Time")) { }
+ column(Percentile_95th_Payment_Time; "95th Percentile Payment Time") { }
+ column(Percentile_95th_Payment_Time_Caption; FieldCaption("95th Percentile Payment Time")) { }
+ column(Pct_Peppol_Enabled; "Pct Peppol Enabled") { }
+ column(Pct_Peppol_Enabled_Caption; FieldCaption("Pct Peppol Enabled")) { }
+ column(Pct_Small_Business_Payments; "Pct Small Business Payments") { }
+ column(Pct_Small_Business_Payments_Caption; FieldCaption("Pct Small Business Payments")) { }
dataitem(PaymentPracticeLine; "Payment Practice Line")
{
@@ -67,6 +83,13 @@ report 685 "Payment Practice"
Summary = 'Payment Practice by Period';
LayoutFile = 'src/Reports/Payment Practice by Period.docx';
}
+ layout(PaymentPractice_SmallBusinessLayout)
+ {
+ Type = Word;
+ Caption = 'Payment Practice Small Business';
+ Summary = 'Payment Practice Small Business';
+ LayoutFile = 'src/Reports/Payment Practice Small Business.docx';
+ }
layout(PaymentPractice_VendorSizeLayout)
{
Type = Word;
@@ -75,4 +98,4 @@ report 685 "Payment Practice"
LayoutFile = 'src/Reports/Payment Practice by Vendor Size.docx';
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/App/src/Tables/PaymPracVendLedgEntry.TableExt.al b/src/Apps/W1/PaymentPractices/App/src/Tables/PaymPracVendLedgEntry.TableExt.al
new file mode 100644
index 0000000000..fcba3ab6cd
--- /dev/null
+++ b/src/Apps/W1/PaymentPractices/App/src/Tables/PaymPracVendLedgEntry.TableExt.al
@@ -0,0 +1,20 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Finance.Analysis;
+
+using Microsoft.Purchases.Payables;
+
+tableextension 681 "Paym. Prac. Vend. Ledg. Entry" extends "Vendor Ledger Entry"
+{
+ fields
+ {
+ field(680; "SCF Payment Date"; Date)
+ {
+ Caption = 'SCF Payment Date';
+ ToolTip = 'Specifies when the supplier received payment from a finance provider under a supply chain finance arrangement. When filled in, replaces the payment posting date for calculating actual payment days.';
+ DataClassification = CustomerContent;
+ }
+ }
+}
diff --git a/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPeriod.Table.al b/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPeriod.Table.al
index 8dd0d1f413..b01503de5c 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPeriod.Table.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPeriod.Table.al
@@ -144,11 +144,9 @@ table 685 "Payment Period"
local procedure InsertDefaultPeriods_AUNZ()
begin
- InsertPeriod('P0_21', 0, 21);
- InsertPeriod('P22_30', 22, 30);
+ InsertPeriod('P0_30', 0, 30);
InsertPeriod('P31_60', 31, 60);
- InsertPeriod('P61_90', 61, 120);
- InsertPeriod('P121+', 121, 0);
+ InsertPeriod('P61+', 61, 0);
end;
[IntegrationEvent(false, false)]
@@ -156,4 +154,3 @@ table 685 "Payment Period"
begin
end;
}
-
diff --git a/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeData.Table.al b/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeData.Table.al
index 6e5640a5d8..0adcdd0dde 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeData.Table.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeData.Table.al
@@ -5,6 +5,7 @@
namespace Microsoft.Finance.Analysis;
using Microsoft.Purchases.Payables;
+using Microsoft.Sales.Customer;
using Microsoft.Sales.Receivables;
table 686 "Payment Practice Data"
@@ -78,6 +79,20 @@ table 686 "Payment Practice Data"
AutoFormatType = 1;
AutoFormatExpression = '';
}
+ field(20; "Dispute Status"; Code[10])
+ {
+ TableRelation = "Dispute Status";
+ ToolTip = 'Specifies whether the invoice is flagged as disputed. Copied from the vendor ledger entry during data generation.';
+ }
+ field(21; "Overdue Due to Dispute"; Boolean)
+ {
+ Editable = false;
+ ToolTip = 'Specifies whether the invoice is overdue due to a dispute. This field is automatically calculated based on whether the invoice is overdue and has a dispute status.';
+ }
+ field(22; "SCF Payment Date"; Date)
+ {
+ ToolTip = 'Specifies when the supplier received payment under a supply chain finance arrangement. When filled in, replaces the payment posting date for calculating actual payment days.';
+ }
}
@@ -112,6 +127,8 @@ table 686 "Payment Practice Data"
"Invoice Amount" := -VendorLedgerEntry.Amount;
"Pmt. Posting Date" := VendorLedgerEntry."Closed at Date";
"Pmt. Entry No." := VendorLedgerEntry."Closed by Entry No.";
+ "SCF Payment Date" := VendorLedgerEntry."SCF Payment Date";
+ "Dispute Status" := VendorLedgerEntry."Dispute Status";
if "Invoice Posting Date" <> 0D then
"Agreed Payment Days" := "Due Date" - "Invoice Received Date";
if "Pmt. Posting Date" <> 0D then
@@ -133,6 +150,7 @@ table 686 "Payment Practice Data"
"Invoice Amount" := -CustLedgerEntry.Amount;
"Pmt. Posting Date" := CustLedgerEntry."Closed at Date";
"Pmt. Entry No." := CustLedgerEntry."Closed by Entry No.";
+ "Dispute Status" := CustLedgerEntry."Dispute Status";
if "Invoice Posting Date" <> 0D then
"Agreed Payment Days" := "Due Date" - "Invoice Received Date";
if "Pmt. Posting Date" <> 0D then
diff --git a/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeHeader.Table.al b/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeHeader.Table.al
index b97262898e..c7c2e3a73d 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeHeader.Table.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeHeader.Table.al
@@ -105,6 +105,114 @@ table 687 "Payment Practice Header"
{
}
+ field(15; "Reporting Scheme"; Enum "Paym. Prac. Reporting Scheme")
+ {
+ Editable = false;
+ ToolTip = 'Specifies which reporting scheme is used, such as Standard, Dispute & Retention, or Small Business. Controls which fields and calculations apply.';
+ }
+ field(20; "Total Number of Payments"; Integer)
+ {
+ Editable = false;
+ ToolTip = 'Specifies the total number of payments made during the reporting period.';
+ }
+ field(21; "Total Amount of Payments"; Decimal)
+ {
+ Editable = false;
+ AutoFormatType = 1;
+ AutoFormatExpression = '';
+ ToolTip = 'Specifies the total value of payments made during the reporting period.';
+ }
+ field(22; "Total Amt. of Overdue Payments"; Decimal)
+ {
+ Editable = false;
+ AutoFormatType = 1;
+ AutoFormatExpression = '';
+ ToolTip = 'Specifies the total value of payments not made within the agreed payment terms.';
+ }
+ field(23; "Pct Overdue Due to Dispute"; Decimal)
+ {
+ Editable = false;
+ AutoFormatType = 0;
+ ToolTip = 'Specifies the percentage of payments not made within agreed terms that are due to disputes.';
+ }
+ field(24; "Mode Payment Time"; Integer)
+ {
+ ToolTip = 'Specifies the most frequently occurring number of days taken to pay invoices during the reporting period.';
+
+ trigger OnValidate()
+ begin
+ Rec."Modified Manually" := true;
+ end;
+ }
+ field(25; "Mode Payment Time Min."; Integer)
+ {
+ ToolTip = 'Specifies the lowest mode payment time, in days, found across all vendors in the reporting period. The value is calculated by determining the mode payment time for each vendor, which is the most frequently occurring number of days taken to pay invoices for that vendor during the reporting period, and then finding the lowest value among those.';
+
+ trigger OnValidate()
+ begin
+ Rec."Modified Manually" := true;
+ end;
+ }
+ field(26; "Mode Payment Time Max."; Integer)
+ {
+ ToolTip = 'Specifies the highest mode payment time, in days, found across all vendors in the reporting period. The value is calculated by determining the mode payment time for each vendor, which is the most frequently occurring number of days taken to pay invoices for that vendor during the reporting period, and then finding the highest value among those.';
+
+ trigger OnValidate()
+ begin
+ Rec."Modified Manually" := true;
+ end;
+ }
+ field(27; "Median Payment Time"; Decimal)
+ {
+ AutoFormatType = 0;
+ DecimalPlaces = 2;
+ ToolTip = 'Specifies the median number of days taken to pay invoices during the reporting period.';
+
+ trigger OnValidate()
+ begin
+ Rec."Modified Manually" := true;
+ end;
+ }
+ field(28; "80th Percentile Payment Time"; Integer)
+ {
+ ToolTip = 'Specifies the number of days within which 80 percent of invoices were paid during the reporting period.';
+
+ trigger OnValidate()
+ begin
+ Rec."Modified Manually" := true;
+ end;
+ }
+ field(29; "95th Percentile Payment Time"; Integer)
+ {
+ ToolTip = 'Specifies the number of days within which 95 percent of invoices were paid during the reporting period.';
+
+ trigger OnValidate()
+ begin
+ Rec."Modified Manually" := true;
+ end;
+ }
+ field(30; "Pct Peppol Enabled"; Decimal)
+ {
+ AutoFormatType = 0;
+ DecimalPlaces = 2;
+ ToolTip = 'Specifies the percentage of invoices that were PEPPOL enabled. An invoice is considered PEPPOL enabled if the vendor has a GLN.';
+
+ trigger OnValidate()
+ begin
+ Rec."Modified Manually" := true;
+ end;
+ }
+ field(31; "Pct Small Business Payments"; Decimal)
+ {
+ AutoFormatType = 0;
+ DecimalPlaces = 2;
+ ToolTip = 'Specifies the value of payments made to small business vendors as a percentage of total payments in the reporting period, including partial payments. A vendor is classified as a small business based on the company size code on the vendor record.';
+
+ trigger OnValidate()
+ begin
+ Rec."Modified Manually" := true;
+ end;
+ }
}
keys
@@ -118,6 +226,7 @@ table 687 "Payment Practice Header"
trigger OnInsert()
begin
UpdateNo();
+ DetectReportingScheme();
end;
trigger OnDelete()
@@ -168,4 +277,12 @@ table 687 "Payment Practice Header"
if Rec."Starting Date" > Rec."Ending Date" then
Error(DateValidationErr);
end;
-}
+
+ local procedure DetectReportingScheme()
+ var
+ PaymentPractices: Codeunit "Payment Practices";
+ begin
+ "Reporting Scheme" := PaymentPractices.DetectReportingScheme();
+ end;
+
+}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeLine.Table.al b/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeLine.Table.al
index c7fbf37242..aa7d2b5438 100644
--- a/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeLine.Table.al
+++ b/src/Apps/W1/PaymentPractices/App/src/Tables/PaymentPracticeLine.Table.al
@@ -84,7 +84,16 @@ table 688 "Payment Practice Line"
}
field(13; "Modified Manually"; Boolean)
{
-
+ }
+ field(15; "Invoice Count"; Integer)
+ {
+ ToolTip = 'Specifies the number of invoices in this period.';
+ }
+ field(16; "Invoice Value"; Decimal)
+ {
+ AutoFormatType = 1;
+ AutoFormatExpression = '';
+ ToolTip = 'Specifies the total value of invoices in this period.';
}
}
diff --git a/src/Apps/W1/PaymentPractices/Test Library/ExtensionLogo.png b/src/Apps/W1/PaymentPractices/Test Library/ExtensionLogo.png
new file mode 100644
index 0000000000..4d2c9a626c
Binary files /dev/null and b/src/Apps/W1/PaymentPractices/Test Library/ExtensionLogo.png differ
diff --git a/src/Apps/W1/PaymentPractices/Test Library/app.json b/src/Apps/W1/PaymentPractices/Test Library/app.json
new file mode 100644
index 0000000000..3302636634
--- /dev/null
+++ b/src/Apps/W1/PaymentPractices/Test Library/app.json
@@ -0,0 +1,46 @@
+{
+ "id": "cc329ed7-8840-45f6-860b-3eb99c408998",
+ "name": "Payment Practices Test Library",
+ "publisher": "Microsoft",
+ "brief": "Test library for Payment Practices app.",
+ "description": "Test library for Payment Practices app.",
+ "version": "29.0.0.0",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120",
+ "help": "https://go.microsoft.com/fwlink/?LinkId=724011",
+ "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2204541",
+ "url": "https://go.microsoft.com/fwlink/?LinkId=724011",
+ "logo": "ExtensionLogo.png",
+ "dependencies": [
+ {
+ "id": "64977288-facd-4b48-aaaa-bb0e288edfb3",
+ "name": "Payment Practices",
+ "publisher": "Microsoft",
+ "version": "29.0.0.0"
+ },
+ {
+ "id": "5d86850b-0d76-4eca-bd7b-951ad998e997",
+ "name": "Tests-TestLibraries",
+ "publisher": "Microsoft",
+ "version": "29.0.0.0"
+ }
+ ],
+ "screenshots": [],
+ "platform": "29.0.0.0",
+ "idRanges": [
+ {
+ "from": 134196,
+ "to": 134196
+ }
+ ],
+ "resourceExposurePolicy": {
+ "allowDebugging": false,
+ "allowDownloadingSource": true,
+ "includeSourceInSymbolFile": true
+ },
+ "application": "29.0.0.0",
+ "target": "Cloud",
+ "features": [
+ "TranslationFile"
+ ]
+}
diff --git a/src/Apps/W1/PaymentPractices/Test Library/src/PaymentPracticesLibrary.Codeunit.al b/src/Apps/W1/PaymentPractices/Test Library/src/PaymentPracticesLibrary.Codeunit.al
new file mode 100644
index 0000000000..bae148f35e
--- /dev/null
+++ b/src/Apps/W1/PaymentPractices/Test Library/src/PaymentPracticesLibrary.Codeunit.al
@@ -0,0 +1,301 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Test.Finance.Analysis;
+
+using Microsoft.Finance.Analysis;
+using Microsoft.Finance.GeneralLedger.Journal;
+using Microsoft.Purchases.Payables;
+using Microsoft.Purchases.Vendor;
+using Microsoft.Sales.Customer;
+using Microsoft.Sales.Receivables;
+
+codeunit 134196 "Payment Practices Library"
+{
+ var
+ Assert: Codeunit Assert;
+ LibraryUtility: Codeunit "Library - Utility";
+ LibraryPurchase: Codeunit "Library - Purchase";
+ LibraryRandom: Codeunit "Library - Random";
+
+ procedure CreatePaymentPracticeHeader(var PaymentPracticeHeader: Record "Payment Practice Header"; HeaderType: Enum "Paym. Prac. Header Type"; AggregationType: Enum "Paym. Prac. Aggregation Type"; StartingDate: Date; EndingDate: Date)
+ begin
+ PaymentPracticeHeader.Init();
+ PaymentPracticeHeader."Header Type" := HeaderType;
+ PaymentPracticeHeader."Aggregation Type" := AggregationType;
+ PaymentPracticeHeader."Starting Date" := StartingDate;
+ PaymentPracticeHeader."Ending Date" := EndingDate;
+ PaymentPracticeHeader.Insert();
+ end;
+
+ procedure CreatePaymentPracticeHeader(var PaymentPracticeHeader: Record "Payment Practice Header"; HeaderType: Enum "Paym. Prac. Header Type"; AggregationType: Enum "Paym. Prac. Aggregation Type"; ReportingScheme: Enum "Paym. Prac. Reporting Scheme"; StartingDate: Date; EndingDate: Date)
+ begin
+ PaymentPracticeHeader.Init();
+ PaymentPracticeHeader."Header Type" := HeaderType;
+ PaymentPracticeHeader."Aggregation Type" := AggregationType;
+ PaymentPracticeHeader."Reporting Scheme" := ReportingScheme;
+ PaymentPracticeHeader."Starting Date" := StartingDate;
+ PaymentPracticeHeader."Ending Date" := EndingDate;
+ PaymentPracticeHeader.Insert();
+ end;
+
+ procedure CreatePaymentPracticeHeaderSimple(var PaymentPracticeHeader: Record "Payment Practice Header")
+ begin
+ CreatePaymentPracticeHeader(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::"Company Size", WorkDate() - 180, WorkDate() + 180);
+ end;
+
+ procedure CreatePaymentPracticeHeaderSimple(var PaymentPracticeHeader: Record "Payment Practice Header"; HeaderType: Enum "Paym. Prac. Header Type"; AggregationType: Enum "Paym. Prac. Aggregation Type")
+ begin
+ CreatePaymentPracticeHeader(PaymentPracticeHeader, HeaderType, AggregationType, WorkDate() - 180, WorkDate() + 180);
+ end;
+
+ procedure CreateCompanySizeCode(): Code[20]
+ begin
+ exit(CreateCompanySizeCode(false));
+ end;
+
+ procedure CreateCompanySizeCode(IsSmallBusiness: Boolean): Code[20]
+ var
+ CompanySize: Record "Company Size";
+ begin
+ CompanySize.Init();
+ CompanySize.Code := LibraryUtility.GenerateGUID();
+ CompanySize.Description := CompanySize.Code;
+ CompanySize."Small Business" := IsSmallBusiness;
+ CompanySize.Insert();
+ exit(CompanySize.Code);
+ end;
+
+ procedure CreateVendorNoWithSizeAndExcl(CompanySizeCode: Code[20]; ExclFromPaymentPractice: Boolean) VendorNo: Code[20]
+ var
+ Vendor: Record Vendor;
+ begin
+ LibraryPurchase.CreateVendor(Vendor);
+ SetCompanySize(Vendor, CompanySizeCode);
+ SetExcludeFromPaymentPractices(Vendor, ExclFromPaymentPractice);
+ exit(Vendor."No.");
+ end;
+
+ procedure InitializeCompanySizes(var CompanySizeCodes: array[3] of Code[20])
+ var
+ i: Integer;
+ begin
+ for i := 1 to ArrayLen(CompanySizeCodes) do
+ CompanySizeCodes[i] := CreateCompanySizeCode();
+ end;
+
+ procedure InitializePaymentPeriods(var PaymentPeriods: array[3] of Record "Payment Period")
+ var
+ PaymentPeriod: Record "Payment Period";
+ i: Integer;
+ begin
+ PaymentPeriod.SetCurrentKey("Days From");
+ PaymentPeriod.FindSet();
+ for i := 1 to ArrayLen(PaymentPeriods) do begin
+ PaymentPeriods[i] := PaymentPeriod;
+ PaymentPeriod.Next();
+ end;
+ end;
+
+ procedure InitAndGetLastPaymentPeriod(var PaymentPeriod: Record "Payment Period")
+ begin
+ PaymentPeriod.SetRange("Days To", 0);
+ PaymentPeriod.FindLast();
+ end;
+
+ procedure SetCompanySize(var Vendor: Record Vendor; CompanySizeCode: Code[20])
+ begin
+ Vendor."Company Size Code" := CompanySizeCode;
+ Vendor.Modify();
+ end;
+
+ procedure SetExcludeFromPaymentPractices(var Vendor: Record Vendor; NewExcludeFromPaymentPractice: Boolean)
+ begin
+ Vendor."Exclude from Pmt. Practices" := NewExcludeFromPaymentPractice;
+ Vendor.Modify();
+ end;
+
+ procedure SetExcludeFromPaymentPractices(var Customer: Record Customer; NewExcludeFromPaymentPractice: Boolean)
+ begin
+ Customer."Exclude from Pmt. Practices" := NewExcludeFromPaymentPractice;
+ Customer.Modify();
+ end;
+
+ procedure SetExcludeFromPaymentPracticesOnAllVendorsAndCustomers()
+ var
+ Vendor: Record Vendor;
+ Customer: Record Customer;
+ begin
+ Vendor.ModifyAll("Exclude from Pmt. Practices", true);
+ Customer.ModifyAll("Exclude from Pmt. Practices", true);
+ end;
+
+ procedure VerifyLinesCount(PaymentPracticeHeader: Record "Payment Practice Header"; NumberOfLines: Integer)
+ var
+ PaymentPracticeLine: Record "Payment Practice Line";
+ begin
+ PaymentPracticeLine.SetRange("Header No.", PaymentPracticeHeader."No.");
+ Assert.RecordCount(PaymentPracticeLine, NumberOfLines);
+ end;
+
+ procedure VerifyPeriodLine(PaymentPracticeHeaderNo: Integer; SourceType: Enum "Paym. Prac. Header Type"; PaymentPeriodDescription: Text[250]; PctInPeriodExpected: Decimal; PctInPeriodAmountExpected: Decimal)
+ var
+ PaymentPracticeLine: Record "Payment Practice Line";
+ begin
+ PaymentPracticeLine.SetRange("Header No.", PaymentPracticeHeaderNo);
+#pragma warning disable AA0210
+ PaymentPracticeLine.SetRange("Payment Period Description", PaymentPeriodDescription);
+ PaymentPracticeLine.SetRange("Source Type", SourceType);
+#pragma warning restore AA0210
+ PaymentPracticeLine.FindFirst();
+ Assert.AreNearlyEqual(PctInPeriodExpected, PaymentPracticeLine."Pct Paid in Period", 0.1, '"Pct Paid in Period" is not as expected');
+ Assert.AreNearlyEqual(PctInPeriodAmountExpected, PaymentPracticeLine."Pct Paid in Period (Amount)", 0.1, '"Pct Paid in Period (Amount)" is not as expected');
+ end;
+
+
+ procedure VerifyBufferCount(PaymentPracticeHeader: Record "Payment Practice Header"; NumberOfLines: Integer; SourceType: Enum "Paym. Prac. Header Type")
+ var
+ PaymentPracticeData: Record "Payment Practice Data";
+ begin
+ PaymentPracticeData.SetRange("Header No.", PaymentPracticeHeader."No.");
+ PaymentPracticeData.SetRange("Source Type", SourceType);
+ Assert.RecordCount(PaymentPracticeData, NumberOfLines);
+ end;
+
+ procedure CreateDefaultPaymentPeriodTemplates()
+ var
+ PaymentPeriod: Record "Payment Period";
+ begin
+ PaymentPeriod.DeleteAll();
+ PaymentPeriod.SetupDefaults();
+ end;
+
+
+
+
+ procedure CleanupPaymentPracticeHeaders()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ PaymentPracticeData: Record "Payment Practice Data";
+ begin
+ PaymentPracticeData.DeleteAll();
+ PaymentPracticeHeader.DeleteAll();
+ end;
+
+ procedure CreatePaymentPracticeHeaderWithScheme(var PaymentPracticeHeader: Record "Payment Practice Header"; HeaderType: Enum "Paym. Prac. Header Type"; AggregationType: Enum "Paym. Prac. Aggregation Type"; ReportingScheme: Enum "Paym. Prac. Reporting Scheme")
+ begin
+ PaymentPracticeHeader.Init();
+ PaymentPracticeHeader."Header Type" := HeaderType;
+ PaymentPracticeHeader."Aggregation Type" := AggregationType;
+ PaymentPracticeHeader."Reporting Scheme" := ReportingScheme;
+ PaymentPracticeHeader."Starting Date" := WorkDate() - 180;
+ PaymentPracticeHeader."Ending Date" := WorkDate() + 180;
+ PaymentPracticeHeader.Insert();
+ end;
+
+
+ procedure DisputeRetCalcHeaderTotals(var PaymentPracticeHeader: Record "Payment Practice Header"; var PaymentPracticeData: Record "Payment Practice Data")
+ var
+ SchemeHandler: Interface PaymentPracticeSchemeHandler;
+ begin
+ SchemeHandler := "Paym. Prac. Reporting Scheme"::"Dispute & Retention";
+ SchemeHandler.CalculateHeaderTotals(PaymentPracticeHeader, PaymentPracticeData);
+ end;
+
+
+ procedure MockVendLedgerEntry(VendorNo: Code[20]; var VendorLedgerEntry: Record "Vendor Ledger Entry"; DocType: Enum "Gen. Journal Document Type"; PostingDate: Date; DueDate: Date; PmtPostingDate: Date; IsOpen: Boolean)
+ begin
+ VendorLedgerEntry.Init();
+ VendorLedgerEntry."Entry No." := LibraryUtility.GetNewRecNo(VendorLedgerEntry, VendorLedgerEntry.FieldNo("Entry No."));
+ VendorLedgerEntry."Document Type" := DocType;
+ VendorLedgerEntry."Posting Date" := PostingDate;
+ VendorLedgerEntry."Document Date" := PostingDate;
+ VendorLedgerEntry."Vendor No." := VendorNo;
+ VendorLedgerEntry."Due Date" := DueDate;
+ VendorLedgerEntry.Open := IsOpen;
+ VendorLedgerEntry."Closed at Date" := PmtPostingDate;
+ VendorLedgerEntry.Amount := LibraryRandom.RandDec(1000, 2);
+ VendorLedgerEntry.Insert();
+ end;
+
+ procedure MockVendorInvoice(VendorNo: Code[20]; PostingDate: Date; DueDate: Date) InvoiceAmount: Decimal;
+ var
+ VendorLedgerEntry: Record "Vendor Ledger Entry";
+ begin
+ MockVendLedgerEntry(VendorNo, VendorLedgerEntry, "Gen. Journal Document Type"::Invoice, PostingDate, DueDate, 0D, true);
+ VendorLedgerEntry.CalcFields("Amount (LCY)");
+ InvoiceAmount := VendorLedgerEntry."Amount (LCY)";
+ end;
+
+ procedure MockVendorInvoiceAndPayment(VendorNo: Code[20]; PostingDate: Date; DueDate: Date; PaymentPostingDate: Date) InvoiceAmount: Decimal;
+ var
+ VendorLedgerEntry: Record "Vendor Ledger Entry";
+ begin
+ MockVendLedgerEntry(VendorNo, VendorLedgerEntry, "Gen. Journal Document Type"::Invoice, PostingDate, DueDate, PaymentPostingDate, false);
+ VendorLedgerEntry.CalcFields("Amount (LCY)");
+ InvoiceAmount := VendorLedgerEntry."Amount (LCY)";
+ end;
+
+ procedure MockVendorInvoiceAndPaymentInPeriod(VendorNo: Code[20]; StartingDate: Date; PaidInDays_min: Integer; PaidInDays_max: Integer) InvoiceAmount: Decimal;
+ var
+ PostingDate: Date;
+ DueDate: Date;
+ PaymentPostingDate: Date;
+ begin
+ PostingDate := StartingDate;
+ DueDate := StartingDate;
+ if PaidInDays_max <> 0 then
+ PaymentPostingDate := PostingDate + LibraryRandom.RandIntInRange(PaidInDays_min, PaidInDays_max)
+ else
+ PaymentPostingDate := PostingDate + PaidInDays_min + LibraryRandom.RandInt(10);
+ InvoiceAmount := MockVendorInvoiceAndPayment(VendorNo, PostingDate, DueDate, PaymentPostingDate);
+ end;
+
+ procedure MockCustLedgerEntry(CustomerNo: Code[20]; var CustLedgerEntry: Record "Cust. Ledger Entry"; DocType: Enum "Gen. Journal Document Type"; PostingDate: Date; DueDate: Date; PmtPostingDate: Date; IsOpen: Boolean)
+ begin
+ CustLedgerEntry.Init();
+ CustLedgerEntry."Entry No." := LibraryUtility.GetNewRecNo(CustLedgerEntry, CustLedgerEntry.FieldNo("Entry No."));
+ CustLedgerEntry."Document Type" := DocType;
+ CustLedgerEntry."Posting Date" := PostingDate;
+ CustLedgerEntry."Document Date" := PostingDate;
+ CustLedgerEntry."Customer No." := CustomerNo;
+ CustLedgerEntry."Due Date" := DueDate;
+ CustLedgerEntry.Open := IsOpen;
+ CustLedgerEntry."Closed at Date" := PmtPostingDate;
+ CustLedgerEntry.Amount := LibraryRandom.RandDec(1000, 2);
+ CustLedgerEntry.Insert();
+ end;
+
+ procedure MockCustomerInvoice(CustomerNo: Code[20]; PostingDate: Date; DueDate: Date) InvoiceAmount: Decimal;
+ var
+ CustLedgerEntry: Record "Cust. Ledger Entry";
+ begin
+ MockCustLedgerEntry(CustomerNo, CustLedgerEntry, "Gen. Journal Document Type"::Invoice, PostingDate, DueDate, 0D, true);
+ CustLedgerEntry.CalcFields("Amount (LCY)");
+ InvoiceAmount := CustLedgerEntry."Amount (LCY)";
+ end;
+
+ procedure MockCustomerInvoiceAndPayment(CustomerNo: Code[20]; PostingDate: Date; DueDate: Date; PaymentPostingDate: Date) InvoiceAmount: Decimal;
+ var
+ CustLedgerEntry: Record "Cust. Ledger Entry";
+ begin
+ MockCustLedgerEntry(CustomerNo, CustLedgerEntry, "Gen. Journal Document Type"::Invoice, PostingDate, DueDate, PaymentPostingDate, false);
+ CustLedgerEntry.CalcFields("Amount (LCY)");
+ InvoiceAmount := CustLedgerEntry."Amount (LCY)";
+ end;
+
+ procedure MockCustomerInvoiceAndPaymentInPeriod(CustomerNo: Code[20]; StartingDate: Date; PaidInDays_min: Integer; PaidInDays_max: Integer) InvoiceAmount: Decimal;
+ var
+ PostingDate: Date;
+ DueDate: Date;
+ PaymentPostingDate: Date;
+ begin
+ PostingDate := StartingDate;
+ DueDate := StartingDate + LibraryRandom.RandIntInRange(1, 5);
+ PaymentPostingDate := PostingDate + LibraryRandom.RandIntInRange(PaidInDays_min, PaidInDays_max);
+ InvoiceAmount := MockCustomerInvoiceAndPayment(CustomerNo, PostingDate, DueDate, PaymentPostingDate);
+ end;
+
+
+}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/Test/app.json b/src/Apps/W1/PaymentPractices/Test/app.json
index 0badc2cc17..2267bedd31 100644
--- a/src/Apps/W1/PaymentPractices/Test/app.json
+++ b/src/Apps/W1/PaymentPractices/Test/app.json
@@ -18,6 +18,12 @@
"publisher": "Microsoft",
"version": "29.0.0.0"
},
+ {
+ "id": "cc329ed7-8840-45f6-860b-3eb99c408998",
+ "name": "Payment Practices Test Library",
+ "publisher": "Microsoft",
+ "version": "29.0.0.0"
+ },
{
"id": "5d86850b-0d76-4eca-bd7b-951ad998e997",
"name": "Tests-TestLibraries",
@@ -41,7 +47,7 @@
"platform": "29.0.0.0",
"idRanges": [
{
- "from": 134196,
+ "from": 134197,
"to": 134197
}
],
diff --git a/src/Apps/W1/PaymentPractices/Test/src/PaymentPracticesLibrary.Codeunit.al b/src/Apps/W1/PaymentPractices/Test/src/PaymentPracticesLibrary.Codeunit.al
deleted file mode 100644
index 384fef369a..0000000000
--- a/src/Apps/W1/PaymentPractices/Test/src/PaymentPracticesLibrary.Codeunit.al
+++ /dev/null
@@ -1,165 +0,0 @@
-// ------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-// ------------------------------------------------------------------------------------------------
-namespace Microsoft.Test.Finance.Analysis;
-
-using Microsoft.Finance.Analysis;
-using Microsoft.Purchases.Vendor;
-using Microsoft.Sales.Customer;
-
-codeunit 134196 "Payment Practices Library"
-{
- Subtype = Test;
-
- var
- Assert: Codeunit Assert;
- LibraryUtility: Codeunit "Library - Utility";
- LibraryPurchase: Codeunit "Library - Purchase";
-
- procedure CreatePaymentPracticeHeader(var PaymentPracticeHeader: Record "Payment Practice Header"; HeaderType: Enum "Paym. Prac. Header Type"; AggregationType: Enum "Paym. Prac. Aggregation Type"; StartingDate: Date; EndingDate: Date)
- begin
- PaymentPracticeHeader.Init();
- PaymentPracticeHeader."Header Type" := HeaderType;
- PaymentPracticeHeader."Aggregation Type" := AggregationType;
- PaymentPracticeHeader."Starting Date" := StartingDate;
- PaymentPracticeHeader."Ending Date" := EndingDate;
- PaymentPracticeHeader.Insert();
- end;
-
- procedure CreatePaymentPracticeHeaderSimple(var PaymentPracticeHeader: Record "Payment Practice Header")
- begin
- CreatePaymentPracticeHeader(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::"Company Size", WorkDate() - 180, WorkDate() + 180);
- end;
-
- procedure CreatePaymentPracticeHeaderSimple(var PaymentPracticeHeader: Record "Payment Practice Header"; HeaderType: Enum "Paym. Prac. Header Type"; AggregationType: Enum "Paym. Prac. Aggregation Type")
- begin
- CreatePaymentPracticeHeader(PaymentPracticeHeader, HeaderType, AggregationType, WorkDate() - 180, WorkDate() + 180);
- end;
-
- procedure CreateCompanySizeCode(): Code[20]
- var
- CompanySize: Record "Company Size";
- begin
- CompanySize.Init();
- CompanySize.Code := LibraryUtility.GenerateGUID();
- CompanySize.Description := CompanySize.Code;
- CompanySize.Insert();
- exit(CompanySize.Code);
- end;
-
- procedure CreatePaymentPeriod(DaysFrom: Integer; DaysTo: Integer)
- var
- PaymentPeriod: Record "Payment Period";
- begin
- PaymentPeriod.Init();
- PaymentPeriod."Days From" := DaysFrom;
- PaymentPeriod."Days To" := DaysTo;
- PaymentPeriod.Insert();
- end;
-
- procedure CreateVendorNoWithSizeAndExcl(CompanySizeCode: Code[20]; ExclFromPaymentPractice: Boolean) VendorNo: Code[20]
- var
- Vendor: Record Vendor;
- begin
- LibraryPurchase.CreateVendor(Vendor);
- SetCompanySize(Vendor, CompanySizeCode);
- SetExcludeFromPaymentPractices(Vendor, ExclFromPaymentPractice);
- exit(Vendor."No.");
- end;
-
- procedure InitializeCompanySizes(var CompanySizeCodes: array[3] of Code[20])
- var
- i: Integer;
- begin
- for i := 1 to ArrayLen(CompanySizeCodes) do
- CompanySizeCodes[i] := CreateCompanySizeCode();
- end;
-
- procedure InitializePaymentPeriods(var PaymentPeriods: array[3] of Record "Payment Period")
- var
- PaymentPeriod: Record "Payment Period";
- i: Integer;
- begin
- PaymentPeriod.SetupDefaults();
- PaymentPeriod.SetCurrentKey("Days From");
- PaymentPeriod.FindSet();
- for i := 1 to ArrayLen(PaymentPeriods) do begin
- PaymentPeriods[i] := PaymentPeriod;
- PaymentPeriod.Next();
- end;
- end;
-
- procedure InitAndGetLastPaymentPeriod(var PaymentPeriod: Record "Payment Period")
- begin
- PaymentPeriod.SetupDefaults();
- PaymentPeriod.SetRange("Days To", 0);
- PaymentPeriod.FindLast();
- end;
-
- procedure SetCompanySize(var Vendor: Record Vendor; CompanySizeCode: Code[20])
- begin
- Vendor."Company Size Code" := CompanySizeCode;
- Vendor.Modify();
- end;
-
- procedure SetExcludeFromPaymentPractices(var Vendor: Record Vendor; NewExcludeFromPaymentPractice: Boolean)
- begin
- Vendor."Exclude from Pmt. Practices" := NewExcludeFromPaymentPractice;
- Vendor.Modify();
- end;
-
- procedure SetExcludeFromPaymentPractices(var Customer: Record Customer; NewExcludeFromPaymentPractice: Boolean)
- begin
- Customer."Exclude from Pmt. Practices" := NewExcludeFromPaymentPractice;
- Customer.Modify();
- end;
-
- procedure SetExcludeFromPaymentPracticesOnAllVendorsAndCustomers()
- var
- Vendor: Record Vendor;
- Customer: Record Customer;
- begin
- if Vendor.FindSet(true) then
- repeat
- SetExcludeFromPaymentPractices(Vendor, true);
- until Vendor.Next() = 0;
- if Customer.FindSet(true) then
- repeat
- SetExcludeFromPaymentPractices(Customer, true);
- until Customer.Next() = 0;
- end;
-
- procedure VerifyLinesCount(PaymentPracticeHeader: Record "Payment Practice Header"; NumberOfLines: Integer)
- var
- PaymentPracticeLine: Record "Payment Practice Line";
- begin
- PaymentPracticeLine.SetRange("Header No.", PaymentPracticeHeader."No.");
- Assert.RecordCount(PaymentPracticeLine, NumberOfLines);
- end;
-
- procedure VerifyPeriodLine(PaymentPracticeHeaderNo: Integer; SourceType: Enum "Paym. Prac. Header Type"; PaymentPeriodCode: Code[20]; PctInPeriodExpected: Decimal; PctInPeriodAmountExpected: Decimal)
- var
- PaymentPracticeLine: Record "Payment Practice Line";
- begin
- PaymentPracticeLine.SetRange("Header No.", PaymentPracticeHeaderNo);
-#pragma warning disable AA0210
- PaymentPracticeLine.SetRange("Payment Period Code", PaymentPeriodCode);
- PaymentPracticeLine.SetRange("Source Type", SourceType);
-#pragma warning restore AA0210
- PaymentPracticeLine.FindFirst();
- Assert.AreNearlyEqual(PctInPeriodExpected, PaymentPracticeLine."Pct Paid in Period", 0.1, '"Pct Paid in Period" is not as expected');
- Assert.AreNearlyEqual(PctInPeriodAmountExpected, PaymentPracticeLine."Pct Paid in Period (Amount)", 0.1, '"Pct Paid in Period (Amount)" is not as expected');
- end;
-
-
- procedure VerifyBufferCount(PaymentPracticeHeader: Record "Payment Practice Header"; NumberOfLines: Integer; SourceType: Enum "Paym. Prac. Header Type")
- var
- PaymentPracticeData: Record "Payment Practice Data";
- begin
- PaymentPracticeData.SetRange("Header No.", PaymentPracticeHeader."No.");
- PaymentPracticeData.SetRange("Source Type", SourceType);
- Assert.RecordCount(PaymentPracticeData, NumberOfLines);
- end;
-
-}
\ No newline at end of file
diff --git a/src/Apps/W1/PaymentPractices/Test/src/PaymentPracticesUT.Codeunit.al b/src/Apps/W1/PaymentPractices/Test/src/PaymentPracticesUT.Codeunit.al
index bd9c6ef507..f570262fff 100644
--- a/src/Apps/W1/PaymentPractices/Test/src/PaymentPracticesUT.Codeunit.al
+++ b/src/Apps/W1/PaymentPractices/Test/src/PaymentPracticesUT.Codeunit.al
@@ -3,13 +3,13 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
+#pragma warning disable AA0210 // table does not contain key with field A
namespace Microsoft.Test.Finance.Analysis;
using Microsoft.Finance.Analysis;
-using Microsoft.Finance.GeneralLedger.Journal;
-using Microsoft.Purchases.Payables;
+using Microsoft.Purchases.Vendor;
using Microsoft.Sales.Customer;
-using Microsoft.Sales.Receivables;
+using System.Environment;
codeunit 134197 "Payment Practices UT"
{
@@ -28,7 +28,6 @@ codeunit 134197 "Payment Practices UT"
PaymentPracticesLibrary: Codeunit "Payment Practices Library";
PaymentPractices: Codeunit "Payment Practices";
LibraryPurchase: Codeunit "Library - Purchase";
- LibraryUtility: Codeunit "Library - Utility";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
LibraryRandom: Codeunit "Library - Random";
LibrarySales: Codeunit "Library - Sales";
@@ -69,11 +68,11 @@ codeunit 134197 "Payment Practices UT"
// [GIVEN] Vendor with company size and an entry in the period
VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCodes[1], false);
- MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
+ PaymentPracticesLibrary.MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
// [GIVEN]Vendor with company size and an entry in the period, but with Excl. from Payment Practice = true
VendorExcludedNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCodes[2], true);
- MockVendorInvoice(VendorExcludedNo, WorkDate(), WorkDate());
+ PaymentPracticesLibrary.MockVendorInvoice(VendorExcludedNo, WorkDate(), WorkDate());
// [WHEN] Generate payment practices for vendors by size
PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader);
@@ -95,12 +94,12 @@ codeunit 134197 "Payment Practices UT"
// [GIVEN] Customer with an entry in the period
LibrarySales.CreateCustomer(Customer);
- MockCustomerInvoice(Customer."No.", WorkDate(), WorkDate());
+ PaymentPracticesLibrary.MockCustomerInvoice(Customer."No.", WorkDate(), WorkDate());
// [GIVEN] Customer with an entry in the period, but with Excl. from Payment Practice = true
LibrarySales.CreateCustomer(CustomerExcluded);
PaymentPracticesLibrary.SetExcludeFromPaymentPractices(CustomerExcluded, true);
- MockCustomerInvoice(CustomerExcluded."No.", WorkDate(), WorkDate());
+ PaymentPracticesLibrary.MockCustomerInvoice(CustomerExcluded."No.", WorkDate(), WorkDate());
// [WHEN] Generate payment practices for cust+vendors
PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::"Vendor+Customer", "Paym. Prac. Aggregation Type"::Period);
@@ -111,7 +110,7 @@ codeunit 134197 "Payment Practices UT"
end;
[Test]
- [HandlerFunctions('ConfirmHandler_Yes')]
+ [HandlerFunctions('ConfirmHandlerYes')]
procedure ConfirmToCleanUpOnAggrValidation_Yes()
var
PaymentPracticeHeader: Record "Payment Practice Header";
@@ -122,7 +121,7 @@ codeunit 134197 "Payment Practices UT"
// [GIVEN] Vendor with company size and an entry in the period
VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCodes[1], false);
- MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
+ PaymentPracticesLibrary.MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
// [GIVEN] Lines were generated for Header
PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader);
@@ -137,7 +136,7 @@ codeunit 134197 "Payment Practices UT"
end;
[Test]
- [HandlerFunctions('ConfirmHandler_No')]
+ [HandlerFunctions('ConfirmHandlerNo')]
procedure ConfirmToCleanUpOnAggrValidation_No()
var
PaymentPracticeHeader: Record "Payment Practice Header";
@@ -148,7 +147,7 @@ codeunit 134197 "Payment Practices UT"
// [GIVEN] Vendor with company size and an entry in the period
VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCodes[1], false);
- MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
+ PaymentPracticesLibrary.MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
// [GIVEN] Lines were generated for Header
PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader);
@@ -164,7 +163,7 @@ codeunit 134197 "Payment Practices UT"
end;
[Test]
- [HandlerFunctions('ConfirmHandler_Yes')]
+ [HandlerFunctions('ConfirmHandlerYes')]
procedure ConfirmToCleanUpOnTypeValidation()
var
PaymentPracticeHeader: Record "Payment Practice Header";
@@ -175,7 +174,7 @@ codeunit 134197 "Payment Practices UT"
// [GIVEN] Vendor with company size and an entry in the period
VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCodes[1], false);
- MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
+ PaymentPracticesLibrary.MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
// [GIVEN] Lines were generated for Header
PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
@@ -218,7 +217,7 @@ codeunit 134197 "Payment Practices UT"
for j := 1 to LibraryRandom.RandInt(10) do begin
PeriodCounts[i] += 1;
TotalCount += 1;
- Amount := MockVendorInvoiceAndPaymentInPeriod(VendorNo, WorkDate(), PaymentPeriods[i]."Days From", PaymentPeriods[i]."Days To");
+ Amount := PaymentPracticesLibrary.MockVendorInvoiceAndPaymentInPeriod(VendorNo, WorkDate(), PaymentPeriods[i]."Days From", PaymentPeriods[i]."Days To");
PeriodAmounts[i] += Amount;
TotalAmount += Amount;
end;
@@ -228,9 +227,9 @@ codeunit 134197 "Payment Practices UT"
// [THEN] Check that report dataset contains correct percentages for each period
PrepareExpectedPeriodPcts(ExpectedPeriodPcts, ExpectedPeriodAmountPcts, PeriodCounts, TotalCount, PeriodAmounts, TotalAmount);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[1].Code, ExpectedPeriodPcts[1], ExpectedPeriodAmountPcts[1]);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[2].Code, ExpectedPeriodPcts[2], ExpectedPeriodAmountPcts[2]);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[3].Code, ExpectedPeriodPcts[3], ExpectedPeriodAmountPcts[3]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[1].Description, ExpectedPeriodPcts[1], ExpectedPeriodAmountPcts[1]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[2].Description, ExpectedPeriodPcts[2], ExpectedPeriodAmountPcts[2]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[3].Description, ExpectedPeriodPcts[3], ExpectedPeriodAmountPcts[3]);
end;
[Test]
@@ -262,7 +261,7 @@ codeunit 134197 "Payment Practices UT"
for j := 1 to LibraryRandom.RandInt(10) do begin
PeriodCounts[i] += 1;
TotalCount += 1;
- Amount := MockCustomerInvoiceAndPaymentInPeriod(CustomerNo, WorkDate(), PaymentPeriods[i]."Days From", PaymentPeriods[i]."Days To");
+ Amount := PaymentPracticesLibrary.MockCustomerInvoiceAndPaymentInPeriod(CustomerNo, WorkDate(), PaymentPeriods[i]."Days From", PaymentPeriods[i]."Days To");
PeriodAmounts[i] += Amount;
TotalAmount += Amount;
end;
@@ -272,9 +271,9 @@ codeunit 134197 "Payment Practices UT"
// [THEN] Check that report dataset contains correct percentages for each period
PrepareExpectedPeriodPcts(ExpectedPeriodPcts, ExpectedPeriodAmountPcts, PeriodCounts, TotalCount, PeriodAmounts, TotalAmount);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[1].Code, ExpectedPeriodPcts[1], ExpectedPeriodAmountPcts[1]);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[2].Code, ExpectedPeriodPcts[2], ExpectedPeriodAmountPcts[2]);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[3].Code, ExpectedPeriodPcts[3], ExpectedPeriodAmountPcts[3]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[1].Description, ExpectedPeriodPcts[1], ExpectedPeriodAmountPcts[1]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[2].Description, ExpectedPeriodPcts[2], ExpectedPeriodAmountPcts[2]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[3].Description, ExpectedPeriodPcts[3], ExpectedPeriodAmountPcts[3]);
end;
[Test]
@@ -316,7 +315,7 @@ codeunit 134197 "Payment Practices UT"
for j := 1 to LibraryRandom.RandInt(10) do begin
Vendor_PeriodCounts[i] += 1;
Vendor_TotalCount += 1;
- Amount := MockVendorInvoiceAndPaymentInPeriod(VendorNo, WorkDate(), PaymentPeriods[i]."Days From", PaymentPeriods[i]."Days To");
+ Amount := PaymentPracticesLibrary.MockVendorInvoiceAndPaymentInPeriod(VendorNo, WorkDate(), PaymentPeriods[i]."Days From", PaymentPeriods[i]."Days To");
Vendor_PeriodAmounts[i] += Amount;
Vendor_TotalAmount += Amount;
end;
@@ -326,7 +325,7 @@ codeunit 134197 "Payment Practices UT"
for j := 1 to LibraryRandom.RandInt(10) do begin
Customer_PeriodCounts[i] += 1;
Customer_TotalCount += 1;
- Amount := MockCustomerInvoiceAndPaymentInPeriod(CustomerNo, WorkDate(), PaymentPeriods[i]."Days From", PaymentPeriods[i]."Days To");
+ Amount := PaymentPracticesLibrary.MockCustomerInvoiceAndPaymentInPeriod(CustomerNo, WorkDate(), PaymentPeriods[i]."Days From", PaymentPeriods[i]."Days To");
Customer_PeriodAmounts[i] += Amount;
Customer_TotalAmount += Amount;
end;
@@ -336,15 +335,15 @@ codeunit 134197 "Payment Practices UT"
// [THEN] Check that report dataset contains correct percentages for each period for vendors
PrepareExpectedPeriodPcts(Vendor_ExpectedPeriodPcts, Vendor_ExpectedPeriodAmountPcts, Vendor_PeriodCounts, Vendor_TotalCount, Vendor_PeriodAmounts, Vendor_TotalAmount);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[1].Code, Vendor_ExpectedPeriodPcts[1], Vendor_ExpectedPeriodAmountPcts[1]);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[2].Code, Vendor_ExpectedPeriodPcts[2], Vendor_ExpectedPeriodAmountPcts[2]);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[3].Code, Vendor_ExpectedPeriodPcts[3], Vendor_ExpectedPeriodAmountPcts[3]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[1].Description, Vendor_ExpectedPeriodPcts[1], Vendor_ExpectedPeriodAmountPcts[1]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[2].Description, Vendor_ExpectedPeriodPcts[2], Vendor_ExpectedPeriodAmountPcts[2]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriods[3].Description, Vendor_ExpectedPeriodPcts[3], Vendor_ExpectedPeriodAmountPcts[3]);
// [THEN] Check that report dataset contains correct percentages for each period for customers
PrepareExpectedPeriodPcts(Customer_ExpectedPeriodPcts, Customer_ExpectedPeriodAmountPcts, Customer_PeriodCounts, Customer_TotalCount, Customer_PeriodAmounts, Customer_TotalAmount);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[1].Code, Customer_ExpectedPeriodPcts[1], Customer_ExpectedPeriodAmountPcts[1]);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[2].Code, Customer_ExpectedPeriodPcts[2], Customer_ExpectedPeriodAmountPcts[2]);
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[3].Code, Customer_ExpectedPeriodPcts[3], Customer_ExpectedPeriodAmountPcts[3]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[1].Description, Customer_ExpectedPeriodPcts[1], Customer_ExpectedPeriodAmountPcts[1]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[2].Description, Customer_ExpectedPeriodPcts[2], Customer_ExpectedPeriodAmountPcts[2]);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Customer, PaymentPeriods[3].Description, Customer_ExpectedPeriodPcts[3], Customer_ExpectedPeriodAmountPcts[3]);
end;
[Test]
@@ -370,21 +369,21 @@ codeunit 134197 "Payment Practices UT"
// [GIVEN] Post several entries paid on time, this will affect total entries considered and total entries paid on time.
PaidOnTimeCount := LibraryRandom.RandInt(20);
for i := 1 to PaidOnTimeCount do
- MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate() + LibraryRandom.RandInt(10), WorkDate());
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate() + LibraryRandom.RandInt(10), WorkDate());
// [GIVEN] Post several entries paid late, this will affect total entries considered.
PaidLateCount := LibraryRandom.RandInt(20);
for i := 1 to PaidLateCount do
- MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + LibraryRandom.RandInt(10));
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + LibraryRandom.RandInt(10));
// [GIVEN] Post several entries unpaid overdue, this will affect total entries considered.
UnpaidOverdueCount := LibraryRandom.RandInt(20);
for i := 1 to UnpaidOverdueCount do
- MockVendorInvoice(VendorNo, WorkDate() - 50, WorkDate() - LibraryRandom.RandInt(40));
+ PaymentPracticesLibrary.MockVendorInvoice(VendorNo, WorkDate() - 50, WorkDate() - LibraryRandom.RandInt(40));
// [GIVEN] Post several entries unpaid not overdue, these will not affect count
for i := 1 to LibraryRandom.RandInt(20) do
- MockVendorInvoice(VendorNo, WorkDate(), WorkDate() + LibraryRandom.RandInt(10));
+ PaymentPracticesLibrary.MockVendorInvoice(VendorNo, WorkDate(), WorkDate() + LibraryRandom.RandInt(10));
// [WHEN] Lines were generated for Header
PaymentPractices.Generate(PaymentPracticeHeader);
@@ -419,7 +418,7 @@ codeunit 134197 "Payment Practices UT"
TotalEntries := LibraryRandom.RandInt(100);
for i := 1 to TotalEntries do begin
ActualPaymentTime := LibraryRandom.RandInt(30);
- MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + ActualPaymentTime);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + ActualPaymentTime);
ActualPaymentTimeSum += ActualPaymentTime;
end;
@@ -457,7 +456,7 @@ codeunit 134197 "Payment Practices UT"
TotalPaidEntries := LibraryRandom.RandInt(100);
for i := 1 to TotalPaidEntries do begin
AgreedPaymentTime := LibraryRandom.RandInt(30);
- MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate() + AgreedPaymentTime, WorkDate() + AgreedPaymentTime);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate() + AgreedPaymentTime, WorkDate() + AgreedPaymentTime);
AgreedPaymentTimeSum += AgreedPaymentTime;
end;
@@ -465,7 +464,7 @@ codeunit 134197 "Payment Practices UT"
TotalUnpaidEntries += LibraryRandom.RandInt(100);
for i := 1 to TotalUnpaidEntries do begin
AgreedPaymentTime := LibraryRandom.RandInt(30);
- MockVendorInvoice(VendorNo, WorkDate(), WorkDate() + AgreedPaymentTime);
+ PaymentPracticesLibrary.MockVendorInvoice(VendorNo, WorkDate(), WorkDate() + AgreedPaymentTime);
AgreedPaymentTimeSum += AgreedPaymentTime;
end;
// [WHEN] Lines were generated for Header
@@ -498,13 +497,13 @@ codeunit 134197 "Payment Practices UT"
PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
// [GIVEN] Post an entry for the vendor in the period
- MockVendorInvoiceAndPaymentInPeriod(VendorNo, WorkDate(), PaymentPeriod."Days From", PaymentPeriod."Days To");
+ PaymentPracticesLibrary.MockVendorInvoiceAndPaymentInPeriod(VendorNo, WorkDate(), PaymentPeriod."Days From", PaymentPeriod."Days To");
// [WHEN] Lines were generated for Header
PaymentPractices.Generate(PaymentPracticeHeader);
// [THEN] Check that report dataset contains the line for the period correcly
- PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriod.Code, 100, 0);
+ PaymentPracticesLibrary.VerifyPeriodLine(PaymentPracticeHeader."No.", "Paym. Prac. Header Type"::Vendor, PaymentPeriod.Description, 100, 0);
end;
[Test]
@@ -584,7 +583,7 @@ codeunit 134197 "Payment Practices UT"
// [GIVEN] Vendor "V" with company size and an entry in the period
VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCodes[1], false);
- MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
+ PaymentPracticesLibrary.MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
// [GIVEN] A payment practice header "PPH"
PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader);
@@ -605,116 +604,740 @@ codeunit 134197 "Payment Practices UT"
PaymentPracticeCard.Close();
end;
- local procedure Initialize()
+ [Test]
+ procedure StandardSchemeGenerateProducesSameResults()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ VendorNo: Code[20];
begin
- LibraryTestInitialize.OnTestInitialize(Codeunit::"Payment Practices UT");
+ // [FEATURE] [AI test 0.3]
+ // [SCENARIO 597313] Generating payment practices with Standard reporting scheme produces correct data for vendor entries
+ Initialize();
- // This is so demodata and previous tests doesn't influence the tests
- PaymentPracticesLibrary.SetExcludeFromPaymentPracticesOnAllVendorsAndCustomers();
+ // [GIVEN] Vendor with entry
+ VendorNo := LibraryPurchase.CreateVendorNo();
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate() + 30, WorkDate() + 10);
+
+ // [WHEN] Generate with Standard scheme
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderWithScheme(
+ PaymentPracticeHeader,
+ "Paym. Prac. Header Type"::Vendor,
+ "Paym. Prac. Aggregation Type"::Period,
+ "Paym. Prac. Reporting Scheme"::Standard);
+ PaymentPractices.Generate(PaymentPracticeHeader);
- if Initialized then
- exit;
+ // [THEN] Data is generated correctly
+ PaymentPracticesLibrary.VerifyBufferCount(PaymentPracticeHeader, 1, "Paym. Prac. Header Type"::Vendor);
+ end;
- LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Payment Practices UT");
+ [Test]
+ procedure ReportingSchemeAutoDetectionOnInsert()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ EnvironmentInformation: Codeunit "Environment Information";
+ ExpectedScheme: Enum "Paym. Prac. Reporting Scheme";
+ begin
+ // [FEATURE] [AI test 0.3]
+ // [SCENARIO 629871] Inserting a Payment Practice Header auto-detects the Reporting Scheme based on environment
+ Initialize();
- PaymentPracticesLibrary.InitializeCompanySizes(CompanySizeCodes);
- PaymentPracticesLibrary.InitializePaymentPeriods(PaymentPeriods);
- Initialized := true;
+ // [WHEN] Insert a new Payment Practice Header via Insert(true)
+ PaymentPracticeHeader.Init();
+ PaymentPracticeHeader.Insert(true);
+
+ // [THEN] Reporting Scheme is auto-detected based on environment application family
+ case EnvironmentInformation.GetApplicationFamily() of
+ 'GB':
+ ExpectedScheme := "Paym. Prac. Reporting Scheme"::"Dispute & Retention";
+ 'AU', 'NZ':
+ ExpectedScheme := "Paym. Prac. Reporting Scheme"::"Small Business";
+ else
+ ExpectedScheme := "Paym. Prac. Reporting Scheme"::Standard;
+ end;
+ Assert.AreEqual(
+ ExpectedScheme,
+ PaymentPracticeHeader."Reporting Scheme",
+ 'Reporting Scheme should be auto-detected based on environment application family.');
+ end;
- LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Payment Practices UT");
+ [Test]
+ procedure DisputeRetCalcHeaderTotalsAllPaidOnTime()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ PaymentPracticeData: Record "Payment Practice Data";
+ InvoiceAmount1: Decimal;
+ InvoiceAmount2: Decimal;
+ begin
+ // [FEATURE] [AI test 0.3]
+ // [SCENARIO 597313] CalculateHeaderTotals counts all closed invoices paid on time with zero overdue totals
+ Initialize();
+
+ // [GIVEN] Payment Practice Header "PPH" with Dispute and Retention scheme
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderWithScheme(
+ PaymentPracticeHeader,
+ "Paym. Prac. Header Type"::Vendor,
+ "Paym. Prac. Aggregation Type"::Period,
+ "Paym. Prac. Reporting Scheme"::"Dispute & Retention");
+
+ // [GIVEN] Two closed invoices paid on time (Actual <= Agreed)
+ InvoiceAmount1 := 500;
+ MockPaymentPracticeData(PaymentPracticeHeader."No.", 1, false, 10, 30, InvoiceAmount1, false);
+ InvoiceAmount2 := 300;
+ MockPaymentPracticeData(PaymentPracticeHeader."No.", 2, false, 20, 30, InvoiceAmount2, false);
+
+ // [WHEN] CalculateHeaderTotals is called
+ PaymentPracticeData.SetRange("Header No.", PaymentPracticeHeader."No.");
+ PaymentPracticesLibrary.DisputeRetCalcHeaderTotals(PaymentPracticeHeader, PaymentPracticeData);
+
+ // [THEN] Total payments = 2, total amount = sum of both, overdue = 0, dispute pct = 0
+ Assert.AreEqual(2, PaymentPracticeHeader."Total Number of Payments", 'Total Number of Payments');
+ Assert.AreEqual(InvoiceAmount1 + InvoiceAmount2, PaymentPracticeHeader."Total Amount of Payments", 'Total Amount of Payments');
+ Assert.AreEqual(0, PaymentPracticeHeader."Total Amt. of Overdue Payments", 'Total Amt. of Overdue Payments');
+ Assert.AreEqual(0, PaymentPracticeHeader."Pct Overdue Due to Dispute", 'Pct Overdue Due to Dispute');
+ end;
+
+ [Test]
+ procedure DisputeRetCalcHeaderTotalsOverdueNoDispute()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ PaymentPracticeData: Record "Payment Practice Data";
+ InvoiceAmount: Decimal;
+ begin
+ // [FEATURE] [AI test 0.3]
+ // [SCENARIO 597313] CalculateHeaderTotals calculates overdue amounts correctly when no invoices are disputed
+ Initialize();
+
+ // [GIVEN] Payment Practice Header "PPH" with Dispute and Retention scheme
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderWithScheme(
+ PaymentPracticeHeader,
+ "Paym. Prac. Header Type"::Vendor,
+ "Paym. Prac. Aggregation Type"::Period,
+ "Paym. Prac. Reporting Scheme"::"Dispute & Retention");
+
+ // [GIVEN] Closed overdue invoice without dispute (Actual > Agreed, Dispute = false)
+ InvoiceAmount := 1000;
+ MockPaymentPracticeData(PaymentPracticeHeader."No.", 1, false, 45, 30, InvoiceAmount, false);
+
+ // [WHEN] CalculateHeaderTotals is called
+ PaymentPracticeData.SetRange("Header No.", PaymentPracticeHeader."No.");
+ PaymentPracticesLibrary.DisputeRetCalcHeaderTotals(PaymentPracticeHeader, PaymentPracticeData);
+
+ // [THEN] Total payments = 1, overdue amount = invoice amount, dispute pct = 0
+ Assert.AreEqual(1, PaymentPracticeHeader."Total Number of Payments", 'Total Number of Payments');
+ Assert.AreEqual(InvoiceAmount, PaymentPracticeHeader."Total Amt. of Overdue Payments", 'Total Amt. of Overdue Payments');
+ Assert.AreEqual(0, PaymentPracticeHeader."Pct Overdue Due to Dispute", 'Pct Overdue Due to Dispute');
+
+ // [THEN] Data record has "Overdue Due to Dispute" = false
+ PaymentPracticeData.FindFirst();
+ Assert.IsFalse(PaymentPracticeData."Overdue Due to Dispute", 'Overdue Due to Dispute should be false');
+ end;
+
+ [Test]
+ procedure DisputeRetCalcHeaderTotalsMixedOverdueDispute()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ PaymentPracticeData: Record "Payment Practice Data";
+ OverdueAmount1: Decimal;
+ OverdueAmount2: Decimal;
+ OverdueAmount3: Decimal;
+ begin
+ // [FEATURE] [AI test 0.3]
+ // [SCENARIO 597313] CalculateHeaderTotals calculates correct dispute percentage when some overdue invoices are disputed
+ Initialize();
+
+ // [GIVEN] Payment Practice Header "PPH" with Dispute and Retention scheme
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderWithScheme(
+ PaymentPracticeHeader,
+ "Paym. Prac. Header Type"::Vendor,
+ "Paym. Prac. Aggregation Type"::Period,
+ "Paym. Prac. Reporting Scheme"::"Dispute & Retention");
+
+ // [GIVEN] Three overdue invoices: one disputed, two not
+ OverdueAmount1 := 100;
+ MockPaymentPracticeData(PaymentPracticeHeader."No.", 1, false, 45, 30, OverdueAmount1, true);
+ OverdueAmount2 := 200;
+ MockPaymentPracticeData(PaymentPracticeHeader."No.", 2, false, 50, 30, OverdueAmount2, false);
+ OverdueAmount3 := 300;
+ MockPaymentPracticeData(PaymentPracticeHeader."No.", 3, false, 60, 30, OverdueAmount3, false);
+
+ // [WHEN] CalculateHeaderTotals is called
+ PaymentPracticeData.SetRange("Header No.", PaymentPracticeHeader."No.");
+ PaymentPracticesLibrary.DisputeRetCalcHeaderTotals(PaymentPracticeHeader, PaymentPracticeData);
+
+ // [THEN] Dispute pct = 1/3 * 100 ≈ 33.33
+ Assert.AreEqual(3, PaymentPracticeHeader."Total Number of Payments", 'Total Number of Payments');
+ Assert.AreEqual(OverdueAmount1 + OverdueAmount2 + OverdueAmount3, PaymentPracticeHeader."Total Amt. of Overdue Payments", 'Total Amt. of Overdue Payments');
+ Assert.AreNearlyEqual(33.33, PaymentPracticeHeader."Pct Overdue Due to Dispute", 0.01, 'Pct Overdue Due to Dispute');
+ end;
+
+ [Test]
+ procedure DisputeRetCalcHeaderTotalsMixOnTimeAndOverdue()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ PaymentPracticeData: Record "Payment Practice Data";
+ OnTimeAmount: Decimal;
+ OverdueDisputedAmount: Decimal;
+ OverdueNotDisputedAmount: Decimal;
+ OpenAmount: Decimal;
+ begin
+ // [FEATURE] [AI test 0.3]
+ // [SCENARIO 597313] CalculateHeaderTotals correctly handles a mix of on-time, overdue, and open invoices
+ Initialize();
+
+ // [GIVEN] Payment Practice Header "PPH" with Dispute and Retention scheme
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderWithScheme(
+ PaymentPracticeHeader,
+ "Paym. Prac. Header Type"::Vendor,
+ "Paym. Prac. Aggregation Type"::Period,
+ "Paym. Prac. Reporting Scheme"::"Dispute & Retention");
+
+ // [GIVEN] One on-time closed invoice
+ OnTimeAmount := 400;
+ MockPaymentPracticeData(PaymentPracticeHeader."No.", 1, false, 10, 30, OnTimeAmount, false);
+
+ // [GIVEN] One overdue disputed invoice
+ OverdueDisputedAmount := 600;
+ MockPaymentPracticeData(PaymentPracticeHeader."No.", 2, false, 45, 30, OverdueDisputedAmount, true);
+
+ // [GIVEN] One overdue non-disputed invoice
+ OverdueNotDisputedAmount := 200;
+ MockPaymentPracticeData(PaymentPracticeHeader."No.", 3, false, 50, 30, OverdueNotDisputedAmount, false);
+
+ // [GIVEN] One open invoice (should be skipped)
+ OpenAmount := 999;
+ MockPaymentPracticeData(PaymentPracticeHeader."No.", 4, true, 0, 30, OpenAmount, false);
+
+ // [WHEN] CalculateHeaderTotals is called
+ PaymentPracticeData.SetRange("Header No.", PaymentPracticeHeader."No.");
+ PaymentPracticesLibrary.DisputeRetCalcHeaderTotals(PaymentPracticeHeader, PaymentPracticeData);
+
+ // [THEN] Total payments = 3 (open skipped), total amount = on-time + both overdue
+ Assert.AreEqual(3, PaymentPracticeHeader."Total Number of Payments", 'Total Number of Payments');
+ Assert.AreEqual(OnTimeAmount + OverdueDisputedAmount + OverdueNotDisputedAmount, PaymentPracticeHeader."Total Amount of Payments", 'Total Amount of Payments');
+
+ // [THEN] Overdue amount = only overdue invoices
+ Assert.AreEqual(OverdueDisputedAmount + OverdueNotDisputedAmount, PaymentPracticeHeader."Total Amt. of Overdue Payments", 'Total Amt. of Overdue Payments');
+
+ // [THEN] Dispute pct = 1/2 * 100 = 50 (1 disputed out of 2 overdue)
+ Assert.AreEqual(50, PaymentPracticeHeader."Pct Overdue Due to Dispute", 'Pct Overdue Due to Dispute');
+ end;
+
+ [Test]
+ procedure CompanySizeGenerationSucceedsWithBlankPeriodCode()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ VendorNo: Code[20];
+ begin
+ // [FEATURE] [AI test 0.3]
+ // [SCENARIO 597313] Company Size aggregation succeeds
+ Initialize();
+
+ // [GIVEN] Vendor "V" with company size and an entry in the period
+ VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCodes[1], false);
+ PaymentPracticesLibrary.MockVendorInvoice(VendorNo, WorkDate(), WorkDate());
+
+ // [GIVEN] Header with Company Size aggregation
+ PaymentPracticesLibrary.CreatePaymentPracticeHeader(
+ PaymentPracticeHeader,
+ "Paym. Prac. Header Type"::Vendor,
+ "Paym. Prac. Aggregation Type"::"Company Size",
+ WorkDate() - 180, WorkDate() + 180);
+
+ // [WHEN] Generate payment practices
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] Lines are created (one per company size code)
+ PaymentPracticesLibrary.VerifyLinesCount(PaymentPracticeHeader, 3);
+
+ // [THEN] Data rows include the vendor invoice
+ PaymentPracticesLibrary.VerifyBufferCount(PaymentPracticeHeader, 1, "Paym. Prac. Header Type"::Vendor);
+ end;
+
+ [Test]
+ procedure CompanySizeStandardLeavesInvoiceCountAndValueZero()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ VendorNo: Code[20];
+ begin
+ // [FEATURE] [AI test 0.3]
+ // [SCENARIO 597313] Standard scheme with Company Size aggregation leaves Invoice Count and Invoice Value at zero
+ Initialize();
+
+ // [GIVEN] Vendor "V" with company size and a closed invoice in the period
+ VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCodes[1], false);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 10);
+
+ // [GIVEN] Header with Standard scheme and Company Size aggregation
+ PaymentPracticesLibrary.CreatePaymentPracticeHeader(
+ PaymentPracticeHeader,
+ "Paym. Prac. Header Type"::Vendor,
+ "Paym. Prac. Aggregation Type"::"Company Size",
+ "Paym. Prac. Reporting Scheme"::Standard,
+ WorkDate() - 180, WorkDate() + 180);
+
+ // [WHEN] Generate payment practices
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] Lines exist
+ PaymentPracticesLibrary.VerifyLinesCount(PaymentPracticeHeader, 3);
+
+ // [THEN] All lines have Invoice Count = 0 and Invoice Value = 0
+ VerifyAllLinesInvoiceCountAndValueZero(PaymentPracticeHeader."No.");
+ end;
+
+ [Test]
+ procedure ModePaymentTimeCalculation()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ VendorNo: Code[20];
+ CompanySizeCode: Code[20];
+ begin
+ // [SCENARIO 568642] Check mode payment time calculation in header
+ Initialize();
+
+ // [GIVEN] Create a vendor
+ CompanySizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+ VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+
+ // [GIVEN] Create a payment practice header with Extra Fields enabled
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [GIVEN] Post entries with payment times: 5, 5, 5, 10, 10, 15 (mode = 5)
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 5);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 5);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 5);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 10);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 10);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 15);
+
+ // [WHEN] Lines were generated for Header
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] Mode Payment Time = 5
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+ Assert.AreEqual(5, PaymentPracticeHeader."Mode Payment Time", 'Mode Payment Time is not equal to expected.');
+ end;
+
+ [Test]
+ procedure ModePaymentTimeMinCalculation()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ CompanySizeCode: Code[20];
+ VendorNo1: Code[20];
+ VendorNo2: Code[20];
+ begin
+ // [SCENARIO 568642] Check mode payment time min is the minimum of per-vendor modes
+ Initialize();
+
+ // [GIVEN] Create two vendors with small business company size
+ CompanySizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+ VendorNo1 := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+ VendorNo2 := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+
+ // [GIVEN] Create a payment practice header with Extra Fields enabled
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [GIVEN] Vendor 1: payment times 5, 5, 10 (mode = 5)
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo1, WorkDate(), WorkDate(), WorkDate() + 5);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo1, WorkDate(), WorkDate(), WorkDate() + 5);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo1, WorkDate(), WorkDate(), WorkDate() + 10);
+
+ // [GIVEN] Vendor 2: payment times 8, 8, 12 (mode = 8)
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo2, WorkDate(), WorkDate(), WorkDate() + 8);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo2, WorkDate(), WorkDate(), WorkDate() + 8);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo2, WorkDate(), WorkDate(), WorkDate() + 12);
+
+ // [WHEN] Lines were generated for Header
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] Mode Payment Time Min = 5 (minimum of per-vendor modes 5 and 8)
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+ Assert.AreEqual(5, PaymentPracticeHeader."Mode Payment Time Min.", 'Mode Payment Time Min. is not equal to expected.');
+ end;
+
+ [Test]
+ procedure ModePaymentTimeMaxCalculation()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ CompanySizeCode: Code[20];
+ VendorNo1: Code[20];
+ VendorNo2: Code[20];
+ begin
+ // [SCENARIO 568642] Check mode payment time max is the maximum of per-vendor modes
+ Initialize();
+
+ // [GIVEN] Create two vendors with small business company size
+ CompanySizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+ VendorNo1 := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+ VendorNo2 := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+
+ // [GIVEN] Create a payment practice header with Extra Fields enabled
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [GIVEN] Vendor 1: payment times 5, 5, 10 (mode = 5)
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo1, WorkDate(), WorkDate(), WorkDate() + 5);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo1, WorkDate(), WorkDate(), WorkDate() + 5);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo1, WorkDate(), WorkDate(), WorkDate() + 10);
+
+ // [GIVEN] Vendor 2: payment times 8, 8, 12 (mode = 8)
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo2, WorkDate(), WorkDate(), WorkDate() + 8);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo2, WorkDate(), WorkDate(), WorkDate() + 8);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo2, WorkDate(), WorkDate(), WorkDate() + 12);
+
+ // [WHEN] Lines were generated for Header
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] Mode Payment Time Max = 8 (maximum of per-vendor modes 5 and 8)
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+ Assert.AreEqual(8, PaymentPracticeHeader."Mode Payment Time Max.", 'Mode Payment Time Max. is not equal to expected.');
end;
- local procedure MockVendLedgerEntry(VendorNo: Code[20]; var VendorLedgerEntry: Record "Vendor Ledger Entry"; DocType: Enum "Gen. Journal Document Type"; PostingDate: Date; DueDate: Date; PmtPostingDate: Date; IsOpen: Boolean)
+ [Test]
+ procedure MedianPaymentTimeCalculation()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ CompanySizeCode: Code[20];
+ VendorNo: Code[20];
begin
- VendorLedgerEntry.Init();
- VendorLedgerEntry."Entry No." := LibraryUtility.GetNewRecNo(VendorLedgerEntry, VendorLedgerEntry.FieldNo("Entry No."));
- VendorLedgerEntry."Document Type" := DocType;
- VendorLedgerEntry."Posting Date" := PostingDate;
- VendorLedgerEntry."Document Date" := PostingDate;
- VendorLedgerEntry."Vendor No." := VendorNo;
- VendorLedgerEntry."Due Date" := DueDate;
- VendorLedgerEntry.Open := IsOpen;
- VendorLedgerEntry."Closed at Date" := PmtPostingDate;
- VendorLedgerEntry.Amount := LibraryRandom.RandDec(1000, 2);
- VendorLedgerEntry.Insert();
+ // [SCENARIO 568642] Check median payment time calculation with odd number of entries
+ Initialize();
+
+ // [GIVEN] Create a vendor with small business company size
+ CompanySizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+ VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+
+ // [GIVEN] Create a payment practice header with Extra Fields enabled
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [GIVEN] Post entries with payment times: 3, 7, 5, 11, 9 (sorted: 3, 5, 7, 9, 11; median = 7)
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 3);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 7);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 5);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 11);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 9);
+
+ // [WHEN] Lines were generated for Header
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] Median Payment Time = 7
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+ Assert.AreEqual(7, PaymentPracticeHeader."Median Payment Time", 'Median Payment Time is not equal to expected.');
end;
- local procedure MockVendorInvoice(VendorNo: Code[20]; PostingDate: Date; DueDate: Date) InvoiceAmount: Decimal;
+ [Test]
+ procedure MedianPaymentTimeCalculation_EvenCount()
var
- VendorLedgerEntry: Record "Vendor Ledger Entry";
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ CompanySizeCode: Code[20];
+ VendorNo: Code[20];
begin
- MockVendLedgerEntry(VendorNo, VendorLedgerEntry, "Gen. Journal Document Type"::Invoice, PostingDate, DueDate, 0D, true);
- VendorLedgerEntry.CalcFields("Amount (LCY)");
- InvoiceAmount := VendorLedgerEntry."Amount (LCY)";
+ // [SCENARIO 568642] Check median payment time calculation with even number of entries
+ Initialize();
+
+ // [GIVEN] Create a vendor with small business company size
+ CompanySizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+ VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+
+ // [GIVEN] Create a payment practice header with Extra Fields enabled
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [GIVEN] Post entries with payment times: 4, 10, 2, 8 (sorted: 2, 4, 8, 10; median = (4 + 8) / 2 = 6)
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 4);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 10);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 2);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + 8);
+
+ // [WHEN] Lines were generated for Header
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] Median Payment Time = 6 (average of two middle values)
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+ Assert.AreEqual(6, PaymentPracticeHeader."Median Payment Time", 'Median Payment Time is not equal to expected.');
end;
- local procedure MockVendorInvoiceAndPayment(VendorNo: Code[20]; PostingDate: Date; DueDate: Date; PaymentPostingDate: Date) InvoiceAmount: Decimal;
+ [Test]
+ procedure Percentile80thPaymentTimeCalculation()
var
- VendorLedgerEntry: Record "Vendor Ledger Entry";
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ CompanySizeCode: Code[20];
+ VendorNo: Code[20];
+ i: Integer;
begin
- MockVendLedgerEntry(VendorNo, VendorLedgerEntry, "Gen. Journal Document Type"::Invoice, PostingDate, DueDate, PaymentPostingDate, false);
- VendorLedgerEntry.CalcFields("Amount (LCY)");
- InvoiceAmount := VendorLedgerEntry."Amount (LCY)";
+ // [SCENARIO 568642] Check 80th percentile payment time calculation
+ Initialize();
+
+ // [GIVEN] Create a vendor with small business company size
+ CompanySizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+ VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+
+ // [GIVEN] Create a payment practice header with Extra Fields enabled
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [GIVEN] Post 10 entries with payment times 1 through 10
+ for i := 1 to 10 do
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + i);
+
+ // [WHEN] Lines were generated for Header
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] 80th percentile = 8 (index = 10 * 80 div 100 = 8)
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+ Assert.AreEqual(8, PaymentPracticeHeader."80th Percentile Payment Time", '80th Percentile Payment Time is not equal to expected.');
end;
- local procedure MockVendorInvoiceAndPaymentInPeriod(VendorNo: Code[20]; StartingDate: Date; PaidInDays_min: Integer; PaidInDays_max: Integer) InvoiceAmount: Decimal;
+ [Test]
+ procedure Percentile95thPaymentTimeCalculation()
var
- PostingDate: Date;
- DueDate: Date;
- PaymentPostingDate: Date;
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ CompanySizeCode: Code[20];
+ VendorNo: Code[20];
+ i: Integer;
begin
- PostingDate := StartingDate;
- DueDate := StartingDate;
- if PaidInDays_max <> 0 then
- PaymentPostingDate := PostingDate + LibraryRandom.RandIntInRange(PaidInDays_min, PaidInDays_max)
- else
- PaymentPostingDate := PostingDate + PaidInDays_min + LibraryRandom.RandInt(10);
- InvoiceAmount := MockVendorInvoiceAndPayment(VendorNo, PostingDate, DueDate, PaymentPostingDate);
+ // [SCENARIO 568642] Check 95th percentile payment time calculation
+ Initialize();
+
+ // [GIVEN] Create a vendor with small business company size
+ CompanySizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+ VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+
+ // [GIVEN] Create a payment practice header with Extra Fields enabled
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [GIVEN] Post 20 entries with payment times 1 through 20
+ for i := 1 to 20 do
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + i);
+
+ // [WHEN] Lines were generated for Header
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] 95th percentile = 19 (index = 20 * 95 div 100 = 19)
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+ Assert.AreEqual(19, PaymentPracticeHeader."95th Percentile Payment Time", '95th Percentile Payment Time is not equal to expected.');
end;
- local procedure MockCustLedgerEntry(CustomerNo: Code[20]; var CustLedgerEntry: Record "Cust. Ledger Entry"; DocType: Enum "Gen. Journal Document Type"; PostingDate: Date; DueDate: Date; PmtPostingDate: Date; IsOpen: Boolean)
+ [Test]
+ procedure Percentile80thPaymentTime_FractionalIndex()
+ var
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ CompanySizeCode: Code[20];
+ VendorNo: Code[20];
+ i: Integer;
begin
- CustLedgerEntry.Init();
- CustLedgerEntry."Entry No." := LibraryUtility.GetNewRecNo(CustLedgerEntry, CustLedgerEntry.FieldNo("Entry No."));
- CustLedgerEntry."Document Type" := DocType;
- CustLedgerEntry."Posting Date" := PostingDate;
- CustLedgerEntry."Document Date" := PostingDate;
- CustLedgerEntry."Customer No." := CustomerNo;
- CustLedgerEntry."Due Date" := DueDate;
- CustLedgerEntry.Open := IsOpen;
- CustLedgerEntry."Closed at Date" := PmtPostingDate;
- CustLedgerEntry.Amount := LibraryRandom.RandDec(1000, 2);
- CustLedgerEntry.Insert();
+ // [SCENARIO 568642] Check 80th percentile when index is not a whole number (7 * 80 / 100 = 5.6, truncated to 5)
+ Initialize();
+
+ // [GIVEN] Create a vendor with small business company size
+ CompanySizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+ VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+
+ // [GIVEN] Create a payment practice header with Extra Fields enabled
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [GIVEN] Post 7 entries with payment times 1 through 7
+ for i := 1 to 7 do
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + i);
+
+ // [WHEN] Lines were generated for Header
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] 80th percentile = 5 (index = 7 * 80 div 100 = 5, no interpolation)
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+ Assert.AreEqual(5, PaymentPracticeHeader."80th Percentile Payment Time", '80th Percentile Payment Time is not equal to expected.');
end;
- local procedure MockCustomerInvoice(CustomerNo: Code[20]; PostingDate: Date; DueDate: Date) InvoiceAmount: Decimal;
+ [Test]
+ procedure Percentile95thPaymentTime_FractionalIndex()
var
- CustLedgerEntry: Record "Cust. Ledger Entry";
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ CompanySizeCode: Code[20];
+ VendorNo: Code[20];
+ i: Integer;
begin
- MockCustLedgerEntry(CustomerNo, CustLedgerEntry, "Gen. Journal Document Type"::Invoice, PostingDate, DueDate, 0D, true);
- CustLedgerEntry.CalcFields("Amount (LCY)");
- InvoiceAmount := CustLedgerEntry."Amount (LCY)";
+ // [SCENARIO 568642] Check 95th percentile when index is not a whole number (13 * 95 / 100 = 12.35, truncated to 12)
+ Initialize();
+
+ // [GIVEN] Create a vendor with small business company size
+ CompanySizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+ VendorNo := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+
+ // [GIVEN] Create a payment practice header with Extra Fields enabled
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [GIVEN] Post 13 entries with payment times 1 through 13
+ for i := 1 to 13 do
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorNo, WorkDate(), WorkDate(), WorkDate() + i);
+
+ // [WHEN] Lines were generated for Header
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] 95th percentile = 12 (index = 13 * 95 div 100 = 12, no interpolation)
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+ Assert.AreEqual(12, PaymentPracticeHeader."95th Percentile Payment Time", '95th Percentile Payment Time is not equal to expected.');
end;
- local procedure MockCustomerInvoiceAndPayment(CustomerNo: Code[20]; PostingDate: Date; DueDate: Date; PaymentPostingDate: Date) InvoiceAmount: Decimal;
+ [Test]
+ procedure PctPeppolEnabledCalculation()
var
- CustLedgerEntry: Record "Cust. Ledger Entry";
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ Vendor: Record Vendor;
+ CompanySizeCode: Code[20];
+ VendorWithGLN: Code[20];
+ VendorWithoutGLN: Code[20];
+ PeppolInvoiceCount: Integer;
+ NonPeppolInvoiceCount: Integer;
+ ExpectedPctPeppol: Decimal;
+ i: Integer;
begin
- MockCustLedgerEntry(CustomerNo, CustLedgerEntry, "Gen. Journal Document Type"::Invoice, PostingDate, DueDate, PaymentPostingDate, false);
- CustLedgerEntry.CalcFields("Amount (LCY)");
- InvoiceAmount := CustLedgerEntry."Amount (LCY)";
+ // [SCENARIO 568642] Check Pct Peppol Enabled calculation with one vendor that has GLN and one that does not.
+ Initialize();
+
+ // [GIVEN] Create a company size marked as small business
+ CompanySizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+
+ // [GIVEN] Create a vendor with a GLN value (Peppol enabled) and small business company size
+ VendorWithGLN := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+ Vendor.Get(VendorWithGLN);
+ Vendor.GLN := '1234567890123';
+ Vendor.Modify();
+
+ // [GIVEN] Create a vendor without a GLN value (not Peppol enabled) with small business company size
+ VendorWithoutGLN := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(CompanySizeCode, false);
+
+ // [GIVEN] Create a payment practice header with Extra Fields enabled
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [GIVEN] Post 3 paid invoices for the GLN vendor
+ PeppolInvoiceCount := 3;
+ for i := 1 to PeppolInvoiceCount do
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorWithGLN, WorkDate(), WorkDate(), WorkDate() + 5);
+
+ // [GIVEN] Post 2 paid invoices for the non-GLN vendor
+ NonPeppolInvoiceCount := 2;
+ for i := 1 to NonPeppolInvoiceCount do
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(VendorWithoutGLN, WorkDate(), WorkDate(), WorkDate() + 10);
+
+ // [WHEN] Generate payment practices
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] Pct Peppol Enabled = 3 / 5 * 100 = 60 (percentage of invoices from vendors with GLN)
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+ ExpectedPctPeppol := PeppolInvoiceCount / (PeppolInvoiceCount + NonPeppolInvoiceCount) * 100;
+ Assert.AreNearlyEqual(ExpectedPctPeppol, PaymentPracticeHeader."Pct Peppol Enabled", 0.01, 'Pct Peppol Enabled is not equal to expected.');
end;
- local procedure MockCustomerInvoiceAndPaymentInPeriod(CustomerNo: Code[20]; StartingDate: Date; PaidInDays_min: Integer; PaidInDays_max: Integer) InvoiceAmount: Decimal;
+ [Test]
+ procedure OnlySmallBusinesses_StatisticsAndPctSmallBusinessPayments()
var
- PostingDate: Date;
- DueDate: Date;
- PaymentPostingDate: Date;
+ PaymentPracticeHeader: Record "Payment Practice Header";
+ SmallBizSizeCode: Code[20];
+ NonSmallBizSizeCode: Code[20];
+ SmallBizVendor1: Code[20];
+ SmallBizVendor2: Code[20];
+ NonSmallBizVendor1: Code[20];
+ NonSmallBizVendor2: Code[20];
+ begin
+ // [SCENARIO 568642] Generate payment practices with "Only Small Businesses" enabled. Only small business vendors should be included in the statistics (median, mode, percentiles) .
+ Initialize();
+
+ // [GIVEN] Create a company size marked as "Small Business" and one that is not
+ SmallBizSizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(true);
+ NonSmallBizSizeCode := PaymentPracticesLibrary.CreateCompanySizeCode(false);
+
+ // [GIVEN] Create 2 vendors with the small business company size
+ SmallBizVendor1 := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(SmallBizSizeCode, false);
+ SmallBizVendor2 := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(SmallBizSizeCode, false);
+
+ // [GIVEN] Create 2 vendors with the non-small business company size
+ NonSmallBizVendor1 := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(NonSmallBizSizeCode, false);
+ NonSmallBizVendor2 := PaymentPracticesLibrary.CreateVendorNoWithSizeAndExcl(NonSmallBizSizeCode, false);
+
+ // [GIVEN] Post paid invoices for small business vendor 1 with payment times 5, 5, 10
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(SmallBizVendor1, WorkDate(), WorkDate(), WorkDate() + 5);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(SmallBizVendor1, WorkDate(), WorkDate(), WorkDate() + 5);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(SmallBizVendor1, WorkDate(), WorkDate(), WorkDate() + 10);
+
+ // [GIVEN] Post paid invoices for small business vendor 2 with payment times 8, 8
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(SmallBizVendor2, WorkDate(), WorkDate(), WorkDate() + 8);
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(SmallBizVendor2, WorkDate(), WorkDate(), WorkDate() + 8);
+
+ // [GIVEN] Post paid invoices for non-small business vendor 1 with payment time 20
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(NonSmallBizVendor1, WorkDate(), WorkDate(), WorkDate() + 20);
+
+ // [GIVEN] Post paid invoices for non-small business vendor 2 with payment time 30
+ PaymentPracticesLibrary.MockVendorInvoiceAndPayment(NonSmallBizVendor2, WorkDate(), WorkDate(), WorkDate() + 30);
+
+ // [GIVEN] Create a payment practice header with "Small Businesses" reporting scheme
+ PaymentPracticesLibrary.CreatePaymentPracticeHeaderSimple(PaymentPracticeHeader, "Paym. Prac. Header Type"::Vendor, "Paym. Prac. Aggregation Type"::Period);
+ PaymentPracticeHeader."Reporting Scheme" := PaymentPracticeHeader."Reporting Scheme"::"Small Business";
+ PaymentPracticeHeader.Modify();
+
+ // [WHEN] Generate payment practices
+ PaymentPractices.Generate(PaymentPracticeHeader);
+
+ // [THEN] Only 5 entries should be in the buffer (only small business vendors)
+ PaymentPracticesLibrary.VerifyBufferCount(PaymentPracticeHeader, 5, "Paym. Prac. Header Type"::Vendor);
+
+ // [THEN] Check header statistics - computed only from small business vendor data
+ // Payment times: 5, 5, 8, 8, 10 (sorted)
+ PaymentPracticeHeader.Get(PaymentPracticeHeader."No.");
+
+ // Mode = 5 (most frequent across all data: 5 appears twice, 8 appears twice - tie broken by smallest)
+ Assert.AreEqual(5, PaymentPracticeHeader."Mode Payment Time", 'Mode Payment Time is not equal to expected.');
+
+ // Mode Min = minimum of per-vendor modes: vendor1 mode = 5, vendor2 mode = 8 → min = 5
+ Assert.AreEqual(5, PaymentPracticeHeader."Mode Payment Time Min.", 'Mode Payment Time Min. is not equal to expected.');
+
+ // Mode Max = maximum of per-vendor modes: vendor1 mode = 5, vendor2 mode = 8 → max = 8
+ Assert.AreEqual(8, PaymentPracticeHeader."Mode Payment Time Max.", 'Mode Payment Time Max. is not equal to expected.');
+
+ // Median of 5, 5, 8, 8, 10 (odd count = 5) → middle value = 8
+ Assert.AreEqual(8, PaymentPracticeHeader."Median Payment Time", 'Median Payment Time is not equal to expected.');
+
+ // 80th percentile: index = 5 * 80 div 100 = 4 → sorted[4] = 8
+ Assert.AreEqual(8, PaymentPracticeHeader."80th Percentile Payment Time", '80th Percentile Payment Time is not equal to expected.');
+
+ // 95th percentile: index = 5 * 95 div 100 = 4 → sorted[4] = 8
+ Assert.AreEqual(8, PaymentPracticeHeader."95th Percentile Payment Time", '95th Percentile Payment Time is not equal to expected.');
+ end;
+
+ local procedure Initialize()
begin
- PostingDate := StartingDate;
- DueDate := StartingDate + LibraryRandom.RandIntInRange(1, 5);
- PaymentPostingDate := PostingDate + LibraryRandom.RandIntInRange(PaidInDays_min, PaidInDays_max);
- InvoiceAmount := MockCustomerInvoiceAndPayment(CustomerNo, PostingDate, DueDate, PaymentPostingDate);
+ LibraryTestInitialize.OnTestInitialize(Codeunit::"Payment Practices UT");
+
+ // This is so demodata and previous tests doesn't influence the tests
+ PaymentPracticesLibrary.SetExcludeFromPaymentPracticesOnAllVendorsAndCustomers();
+
+ if Initialized then
+ exit;
+
+ LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Payment Practices UT");
+
+ PaymentPracticesLibrary.InitializeCompanySizes(CompanySizeCodes);
+ PaymentPracticesLibrary.CreateDefaultPaymentPeriodTemplates();
+ PaymentPracticesLibrary.InitializePaymentPeriods(PaymentPeriods);
+ Initialized := true;
+
+ LibraryTestInitialize.OnAfterTestSuiteInitialize(Codeunit::"Payment Practices UT");
end;
local procedure PrepareExpectedPeriodPcts(var ExpectedPeriodPcts: array[3] of Decimal; var ExpectedPeriodAmountPcts: array[3] of Decimal; PeriodCounts: array[3] of Integer; TotalCount: Integer; PeriodAmounts: array[3] of Decimal; TotalAmount: Decimal)
@@ -729,15 +1352,51 @@ codeunit 134197 "Payment Practices UT"
end;
end;
+ local procedure MockPaymentPracticeData(HeaderNo: Integer; EntryNo: Integer; IsOpen: Boolean; ActualPaymentDays: Integer; AgreedPaymentDays: Integer; InvoiceAmount: Decimal; IsDisputed: Boolean)
+ var
+ PaymentPracticeData: Record "Payment Practice Data";
+ begin
+ PaymentPracticeData.Init();
+ PaymentPracticeData."Header No." := HeaderNo;
+ PaymentPracticeData."Invoice Entry No." := EntryNo;
+ PaymentPracticeData."Source Type" := "Paym. Prac. Header Type"::Vendor;
+ PaymentPracticeData."Invoice Is Open" := IsOpen;
+ PaymentPracticeData."Actual Payment Days" := ActualPaymentDays;
+ PaymentPracticeData."Agreed Payment Days" := AgreedPaymentDays;
+ PaymentPracticeData."Invoice Amount" := InvoiceAmount;
+ if IsDisputed then
+ PaymentPracticeData."Dispute Status" := 'DISPUTED';
+ if (not IsOpen) and (ActualPaymentDays > AgreedPaymentDays) then
+ PaymentPracticeData."Overdue Due to Dispute" := IsDisputed;
+ PaymentPracticeData.Insert();
+ end;
+
+ local procedure VerifyAllLinesInvoiceCountAndValueZero(HeaderNo: Integer)
+ var
+ PaymentPracticeLine: Record "Payment Practice Line";
+ begin
+ PaymentPracticeLine.SetRange("Header No.", HeaderNo);
+ PaymentPracticeLine.FindSet();
+ repeat
+ Assert.AreEqual(0, PaymentPracticeLine."Invoice Count", 'Invoice Count should be 0 for Standard scheme');
+ Assert.AreEqual(0, PaymentPracticeLine."Invoice Value", 'Invoice Value should be 0 for Standard scheme');
+ until PaymentPracticeLine.Next() = 0;
+ end;
+
[ConfirmHandler]
- procedure ConfirmHandler_Yes(Question: Text[1024]; var Reply: Boolean)
+ procedure ConfirmHandlerYes(Question: Text[1024]; var Reply: Boolean)
begin
Reply := true;
end;
[ConfirmHandler]
- procedure ConfirmHandler_No(Question: Text[1024]; var Reply: Boolean)
+ procedure ConfirmHandlerNo(Question: Text[1024]; var Reply: Boolean)
begin
Reply := false;
end;
+
+ [MessageHandler]
+ procedure MessageHandler(Message: Text[1024])
+ begin
+ end;
}