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
82 changes: 82 additions & 0 deletions src/Apps/W1/PaymentPractices/App/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Payment Practices

Regulatory reporting app that calculates and reports payment time metrics
for vendors and customers. Required by jurisdictions like Sweden (250+
employees), UK, Australia (AUD 100M+ turnover) to protect smaller
businesses from late payments by measuring and disclosing how quickly
companies pay their invoices.

## Quick reference

- **32 objects:** 8 codeunits, 4 tables, 2 interfaces, 2 enums, 5 pages, 1 report (with 2 Word layouts), 8 permission objects
- **Object ID range:** 685-694
- **No dependencies** on other apps
- **Key metrics:** Average Agreed Payment Period, Average Actual Payment Period, % Paid on Time
- **On-time definition:** Payment Posting Date <= Due Date for closed invoices. Open invoices with past due count toward total but not toward actual payment time.
- **Exclusions:** Vendors/Customers can be excluded via "Exclude from Pmt. Practices" flag on master record

## How it works

The app uses a **strategy pattern** with two extensible dimensions:

1. **Header Type** (Vendor/Customer/Vendor+Customer) implements `PaymentPracticeDataGenerator` interface -- controls which ledger entries to extract
2. **Aggregation Type** (Period/Company Size) implements `PaymentPracticeLinesAggregator` interface -- controls how results are grouped

**Data pipeline:**

1. Create Header (report configuration + region-aware defaults)
2. Generate -- extract raw data from vendor/customer ledger entries
3. Calculate totals using `PaymentPracticeMath` (pure math codeunit, no side effects)
4. Aggregate into lines by Period or Size
5. Print report using Word layout

**Period aggregation** groups by Payment Period ranges (e.g., 0-30 days,
31-60 days). **Size aggregation** groups by vendor Company Size Code --
only works for vendor data, not customer.

## Structure

```
src/
Core/
PaymentPracticeHeader.Codeunit.al -- orchestration
PaymentPracticeMath.Codeunit.al -- pure math (averages, %)
VendorDataGenerator.Codeunit.al -- vendor ledger extraction
CustomerDataGenerator.Codeunit.al -- customer ledger extraction
VendorAndCustDataGenerator.Codeunit.al -- combined extraction
PeriodLinesAggregator.Codeunit.al -- group by period range
SizeLinesAggregator.Codeunit.al -- group by company size
InstallPaymentPractices.Codeunit.al -- setup default periods
PaymentPracticeDataGenerator.Interface.al
PaymentPracticeLinesAggregator.Interface.al
PaymentPracticeHeaderType.Enum.al -- extensible
PaymentPracticeAggregationType.Enum.al -- extensible
Permissions/ -- 8 permission objects
Tables/
PaymentPeriod.Table.al -- config, region defaults
PaymentPracticeHeader.Table.al -- report config + summary
PaymentPracticeLine.Table.al -- aggregated results
PaymentPracticeData.Table.al -- raw ledger data
Pages/ -- 5 pages
Reports/
PaymentPractices.Report.al -- 2 Word layouts
```

## Documentation

Business logic and UI are straightforward -- the app is a reporting tool
with minimal configuration. See `InstallPaymentPractices.Codeunit.al` for
default Payment Period setup per region (GB, FR, AU/NZ, generic).

`OnBeforeSetupDefaults` integration event allows custom period setup for
new regions or business requirements.

## Things to know

- **Strategy pattern extensibility:** Add new Header Types or Aggregation Types by implementing the interfaces and extending the enums
- **Math isolation:** `PaymentPracticeMath` is pure functions -- no database access, no side effects. Easy to test and reuse.
- **Region-aware defaults:** Install codeunit creates different Payment Period ranges for GB, FR, AU/NZ, and a generic fallback
- **Two report layouts:** Period-based (default) and Vendor Size-based. Layout selection must match the Aggregation Type set on the header.
- **Size aggregation limits:** Company Size Code only exists on Vendor master records, so Size aggregation is not meaningful for Customer or Vendor+Customer reports
- **Open invoice handling:** Open invoices with Due Date in the past count toward total invoices but do not contribute to Average Actual Payment Period (no payment date yet)
- **Ledger entry filters:** Excludes prepayments, credit memos, finance charge memos, and reminders. Vendor/Customer exclusion flag is respected during data extraction.
66 changes: 66 additions & 0 deletions src/Apps/W1/PaymentPractices/App/docs/business-logic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Payment Practices Business Logic

This document describes the business logic and processing pipeline for generating payment practice reports.

## Report Generation Pipeline

The report generation process follows a multi-stage pipeline that validates configuration, extracts data, performs calculations, and produces aggregated results.

```mermaid
flowchart TD
Start([PaymentPractices.Generate]) --> Validate[Validate date range]
Validate --> Delete[Delete existing data and lines]
Delete --> Extract[Extract ledger data via DataGenerator]
Extract --> Totals[Calculate header totals via PaymentPracticeMath]
Totals --> Aggregate[Generate aggregated lines via Aggregator]
Aggregate --> Timestamp[Update generation timestamp and user]
Timestamp --> End([Complete])
```

The Generate method in the PaymentPractices codeunit orchestrates the entire pipeline. It begins by validating the date range configuration on the header record. Next, it deletes any existing data and lines from previous runs to ensure a clean slate. The system then invokes the data generator interface implementation based on the header type. After data extraction completes, PaymentPracticeMath calculates summary metrics for the header. The aggregator interface implementation then produces detail lines. Finally, the system updates generation metadata including timestamp and user.

## Data Extraction

PaymentPracticeBuilders codeunit handles the extraction of raw invoice and payment data from ledger entries.

For vendor transactions, the system queries Vendor Ledger Entry records filtered to document type Invoice within the specified date range. It excludes vendors marked for exclusion in the configuration. For each qualifying invoice, it calls CopyFromInvoiceVendLedgEntry on the PaymentPracticeData table, which copies core fields and calculates both agreed payment days (based on due date) and actual payment days (based on payment posting date).

Customer transaction extraction mirrors the vendor process, querying Customer Ledger Entry records and using CopyFromInvoiceCustLedgEntry for data population.

The combined customer-vendor generator invokes both extraction processes sequentially to populate data for both transaction types.

## Payment Metrics Calculation

PaymentPracticeMath codeunit provides the core mathematical operations for payment performance metrics.

GetPercentOfOnTimePayments calculates the on-time payment rate by comparing payment posting date to due date for closed invoices. An invoice is considered on time when the payment posting date is less than or equal to the due date. Open invoices with past due dates count toward the total denominator but not the numerator, reducing the on-time percentage. The method returns both a count-based percentage and an amount-based percentage.

GetAverageActualPaymentTime computes the mean actual payment days across all closed invoices in the dataset. Open invoices are excluded from this calculation since they have no payment date.

GetAverageAgreedPaymentTime computes the mean agreed payment days across all invoices in the dataset, including both open and closed invoices. This represents the contracted or expected payment terms.

## Aggregation Strategies

The system supports two primary aggregation strategies implemented through the PaymentPracticeLinesAggregator interface.

### Period Aggregation

The period aggregator iterates through all records in the Payment Period master table ordered by Days From. For each period, it applies a filter to PaymentPracticeData where Actual Payment Days falls within the period's range defined by Days From and Days To.

For each filtered subset, it calculates the percentage of invoices paid within that period by both count and amount. When Days To equals zero, the period is treated as open-ended and includes all payments with actual days greater than or equal to Days From.

### Company Size Aggregation

The company size aggregator iterates through all records in the Company Size master table. For each size category, it applies a filter to PaymentPracticeData matching the company size code and invokes PaymentPracticeMath to calculate average agreed days, average actual days, and on-time percentage.

This aggregation strategy is only valid for header type Vendor. If invoked for Customer or Both header types, the ValidateHeader method throws an error preventing report generation.

## Validation and Error Handling

The ValidateHeader method in each aggregator implementation enforces business rules before generation begins. Period aggregation accepts all header types. Company size aggregation validates that the header type is Vendor and raises an error for other types.

Date range validation ensures the ending date is greater than or equal to the starting date. The system prevents generation when validation fails, preserving data integrity and ensuring meaningful results.

## Manual Modifications

After generation completes, users can manually modify line values. The OnModify trigger on PaymentPracticeLine sets the Modified Manually flag on the parent header, creating an audit trail that distinguishes calculated reports from adjusted reports. This flag persists through subsequent views and exports, providing transparency about data provenance.
69 changes: 69 additions & 0 deletions src/Apps/W1/PaymentPractices/App/docs/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Payment Practices Data Model

This document describes the data model for the Payment Practices feature, which tracks and reports on payment performance metrics for vendors and customers.

## Entity Relationship Overview

```mermaid
erDiagram
PaymentPracticeHeader ||--o{ PaymentPracticeLine : "aggregates into"
PaymentPracticeHeader ||--o{ PaymentPracticeData : "contains raw data"
PaymentPracticeLine }o--|| PaymentPeriod : "references"
PaymentPracticeData }o--|| CompanySize : "references"
```

## Core Tables

### PaymentPeriod (Table 685)

The master configuration table defining payment period buckets for aggregation. Each period represents a range of days used to categorize payment timing.

Primary key is a Code field. Supports region-aware defaults for GB, FR, AU, and NZ markets. Open-ended periods are represented by setting Days To to zero. Includes an OnBeforeSetupDefaults integration event for customization.

### PaymentPracticeHeader (Table 687)

The main report configuration and summary table. Each header represents a single payment practice report run.

Uses an auto-increment primary key. Contains date range parameters, aggregation type selection, and header type selection. Stores calculated summary metrics including average agreed payment period, average actual payment period, and percentage paid on time. Tracks generation metadata such as timestamp and user. Includes a FlowField to indicate whether detail lines exist.

Supports cascade deletion of related lines and raw data. The Modified Manually field is set when lines are edited after generation.

### PaymentPracticeLine (Table 688)

Contains aggregated results for each segment within a report. Lines represent either payment period buckets or company size groupings depending on the aggregation type.

Composite primary key consists of header number and line number. Stores source type, company size code, and payment period code as dimension fields. Contains calculated averages and percentages specific to each segment.

Modifications to lines automatically set the Modified Manually flag on the parent header through the OnModify trigger.

### PaymentPracticeData (Table 686)

Raw extracted ledger data serving as the source for aggregation calculations. Each record represents a single invoice entry with payment information.

Composite primary key includes header number, invoice entry number, and source type. Captures invoice dates, payment dates, amounts, and calculated payment day metrics. Links to customer/vendor number and company size code for filtering.

Provides CopyFromInvoiceVendLedgEntry and CopyFromInvoiceCustLedgEntry methods for data extraction. Includes SetFilterForLine method to apply dimension filters during aggregation.

## Enums and Interfaces

### Aggregation Type Enum

Defines how payment practice lines are grouped, either by payment period or by company size. Extensible to support custom aggregation strategies. Implements the PaymentPracticeLinesAggregator interface.

### Header Type Enum

Specifies the scope of data extraction: vendor transactions, customer transactions, or both. Extensible to support additional transaction sources. Implements the PaymentPracticeDataGenerator interface.

### PaymentPracticeDataGenerator Interface

Contract for data extraction implementations. Defines the GenerateData method that populates PaymentPracticeData records from ledger entries.

### PaymentPracticeLinesAggregator Interface

Contract for aggregation implementations. Defines PrepareLayout for setup, GenerateLines for calculation, and ValidateHeader for configuration validation.

## Relationships and Constraints

The header table serves as the parent for both lines and raw data, with cascade deletion ensuring referential integrity. Lines reference the payment period master table through a code field. Raw data references the company size master table through a code field. All foreign key relationships use standard AL table relations.

The Modified Manually flag provides audit tracking when users override generated values, preserving the distinction between calculated and manually adjusted reports.
81 changes: 81 additions & 0 deletions src/Apps/W1/PaymentPractices/App/docs/extensibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Extensibility

The Payment Practices app is designed as an open framework for localization and ISV extensions. Both core enums are marked `Extensible = true`, and the architecture uses interfaces to support custom implementations.

## Extension points

### Add a new aggregation type

Aggregation types control how payment practice data is grouped and presented in reports. The app ships with two aggregation types: by period and by vendor size.

To add a new aggregation type:

1. Create a codeunit implementing the `PaymentPracticeLinesAggregator` interface. You must implement three methods:
- `PrepareLayout()` -- registers the Word report layout for this aggregation type
- `GenerateLines()` -- produces aggregated lines from header data
- `ValidateHeader()` -- validates header fields before generation

2. Add an enum value to `Paym. Prac. Aggregation Type` and bind it to your codeunit.

3. Create a Word report layout for your aggregation type. Register it in `PrepareLayout()`.

The report will automatically use your codeunit when the user selects your aggregation type.

### Add a new data source

Data sources define where payment practice data originates. The app ships with two sources: vendor ledger entries and customer ledger entries.

To add a new data source:

1. Create a codeunit implementing the `PaymentPracticeDataGenerator` interface. You must implement one method:
- `GenerateData()` -- reads source data and populates payment practice header records

2. Add an enum value to `Paym. Prac. Header Type` and bind it to your codeunit.

The framework will invoke your generator when creating headers of your type.

### Integration events

**OnBeforeSetupDefaults** on the `PaymentPeriod` table allows custom period setup per region. This event uses the `IsHandled` pattern -- set `IsHandled := true` to prevent default period creation.

### External dependencies

The app references fields defined in base app extensions but not included in this app:

- **Exclude from Pmt. Practices** flag on Vendor and Customer tables -- must be defined by base app or localization layer
- **Company Size Code** on Vendor and Customer tables -- must be populated externally before aggregation by size will produce meaningful results

### Layouts

The report has two Word layouts:

- `PaymentPractice_PeriodLayout` -- used by period aggregation
- `PaymentPractice_VendorSizeLayout` -- used by size aggregation

New aggregators must provide their own layouts. Layouts are registered in the `PrepareLayout()` method.

### Access modifiers

All core codeunits are marked `Internal` access. The public API consists of:

- The two extensible enums (`Paym. Prac. Aggregation Type`, `Paym. Prac. Header Type`)
- The two interfaces (`PaymentPracticeLinesAggregator`, `PaymentPracticeDataGenerator`)
- The table objects (Header, Lines, PaymentPeriod)
- The report object

Extensions should interact with the framework through these public contracts, not by calling internal codeunits directly.

### Telemetry

The app emits Feature Telemetry events for key operations:

- `0000KSW` -- Payment Practices list page discovered
- `0000KSV` -- Report printed
- `0000KSU` -- Period aggregation completed
- `0000KSX` -- Size aggregation completed

Extensions can emit their own telemetry using the same FeatureTelemetry codeunit.

## Summary

This is an open framework. Partners can add jurisdiction-specific aggregation types and data sources without modifying core code. Extend the enums, implement the interfaces, and register your layouts -- the framework handles the rest.
Loading