Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/Apps/W1/PaymentPractices/App/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"platform": "28.0.0.0",
"idRanges": [
{
"from": 685,
"to": 694
"from": 680,
"to": 698
}
],
"resourceExposurePolicy": {
Expand All @@ -29,5 +29,12 @@
"target": "Cloud",
"features": [
"TranslationFile"
],
"internalsVisibleTo": [
{
"id": "cc329ed7-8840-45f6-860b-3eb99c408998",
"name": "Payment Practices Test Library",
"publisher": "Microsoft"
}
]
}
Original file line number Diff line number Diff line change
@@ -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";
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,25 @@ 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
PaymentPracticeData.SetRange("Source Type", SourceType);
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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
Expand All @@ -45,15 +48,20 @@ 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;

procedure ValidateHeader(var PaymentPracticeHeader: Record "Payment Practice Header")
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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Validates the Payment Practice Header before data generation.
/// </summary>
/// <param name="PaymentPracticeHeader">The header to validate.</param>
procedure ValidateHeader(var PaymentPracticeHeader: Record "Payment Practice Header")

/// <summary>
/// Enriches or filters a Payment Practice Data row before insertion.
/// Returns true to include the row, false to skip it.
/// </summary>
/// <param name="PaymentPracticeData">The data row to enrich/filter.</param>
/// <returns>True to include the row, false to skip.</returns>
procedure UpdatePaymentPracData(var PaymentPracticeData: Record "Payment Practice Data"): Boolean

/// <summary>
/// Calculates scheme-specific header totals after standard totals are generated.
/// </summary>
/// <param name="PaymentPracticeHeader">The header to update with totals.</param>
/// <param name="PaymentPracticeData">The data to aggregate from.</param>
procedure CalculateHeaderTotals(var PaymentPracticeHeader: Record "Payment Practice Header"; var PaymentPracticeData: Record "Payment Practice Data")

/// <summary>
/// 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.
/// </summary>
/// <param name="PaymentPracticeLine">The line to update with totals.</param>
/// <param name="PaymentPracticeData">The data to aggregate from. Filters set by the caller define the slice.</param>
procedure CalculateLineTotals(var PaymentPracticeLine: Record "Payment Practice Line"; var PaymentPracticeData: Record "Payment Practice Data")
}
Loading
Loading