Aggregation Rules
A bill run aggregates RatedTransactions (RTs) into InvoiceLines (ILs) by building a GROUP BY over the relevant columns. This page documents every knob that influences the GROUP BY, with concrete examples.
Always-on grouping keys
Regardless of settings, these columns are always part of the IL grouping key (when aggregation is enabled):
billingAccount.idofferTemplateaccountingArticle.idtax.id,taxPercent,taxInclusiveseller.id
The eleven knobs
| Knob | Type | Default | Effect |
|---|---|---|---|
disableAggregation | boolean | false | When true, no aggregation at all — one IL per RT. |
dateAggregation | enum | MONTH_OF_USAGE_DATE | Bucket RTs by usage date. |
discountAggregation | enum | FULL_AGGREGATION | Collapse discount RTs or keep them separate. |
useAccountingArticleLabel | boolean | false (UI defaults to TRUE) | IL description from article label vs. RT description. |
aggregateUnitAmounts | boolean | false | Quantity-weighted average for ILs with different unit prices. |
ignoreSubscriptions | boolean | true | Aggregate across subscriptions. |
ignoreOrders | boolean | true (UI defaults to OFF) | Aggregate across orders. |
ignoreUserAccounts | boolean | true | Aggregate across consumers. |
ignoreServiceInstances | boolean | true | Aggregate across service instances. |
incrementalInvoiceLines | boolean | false | Append IL data instead of rebuilding from scratch. |
additionalAggregationFields | string[] | [] | Extra GROUP BY columns (JPA paths). |
dateAggregation — four values
| Value | Effect |
|---|---|
NO_DATE_AGGREGATION | Each unique usageDate keeps its own IL. |
DAY_OF_USAGE_DATE | Group by calendar day. |
WEEK_OF_USAGE_DATE | Group by ISO week. |
MONTH_OF_USAGE_DATE | Group by calendar month (default). |
discountAggregation — two values
| Value | Effect |
|---|---|
FULL_AGGREGATION | All RTs (including discount RTs) collapse together. |
NO_AGGREGATION | Each discount RT becomes its own IL. |
Example with 50 RTs and one 10% loyalty discount:
FULL→ 1 discount IL alongside the main IL set.NO→ 50 discount ILs, one per RT.
aggregateUnitAmounts — worked example
Three RTs in the same group, different unit prices:
unit=€19, qty=1, amount=€19
unit=€28.50, qty=1, amount=€28.50
unit=€47.50, qty=1, amount=€47.50
| Flag | Result |
|---|---|
| OFF (default) | Three ILs — unit price is part of the GROUP BY. |
| ON | One IL with qty=3, amount=€95, unit=€31.67 (quantity-weighted average). |
useAccountingArticleLabel — worked example
The UI for the universal bill run defaults this ON because article labels are stable, customer-facing strings while RT descriptions are rating-time technical strings.
| Flag | IL description |
|---|---|
| OFF | RT description (e.g. "MEAL_3C usage 2026-04-15 14:32") |
| ON | Article label (e.g. "Meal vouchers reimbursement") |
ignoreOrders — worked example
Suppose 38 RTs across 5 distinct orders for the same (BA × article × month × parameter5 × parameter6).
ignoreOrders | ILs produced |
|---|---|
| OFF (UI default) | 5 ILs — one per order. |
| ON | 1 IL — orders collapse. |
This relies on a Phase 1.A backend fix (2026-05-13) that removed an unconditional groupBy.add("orderNumber") which had been making the flag a no-op.
additionalAggregationFields — extra GROUP BY columns
Each entry is a JPA path on RatedTransaction injected into the aggregation query's GROUP BY. Not EL — for filtering use filters or applicationEl.
Curated suggestions (autocomplete in the form):
| Suggestion | Effect |
|---|---|
parameter1 … parameter6 | Group by RT parameter slots. |
orderNumber | Group by order number (independent of ignoreOrders). |
subscription.code | Group by subscription. |
serviceInstance.code | Group by service instance. |
userAccount.code | Group by consumer. |
seller.code | Group by seller. |
Free text remains supported — power users can target paths like extraParameter JSON keys when the aggregation handler is wired for them.
billSeparately — automatic per-unit splits
Independent of the knobs above, the engine inspects the billSeparately flag at four entity levels:
| Level | Carried on |
|---|---|
| SUBSCRIPTION | Subscription.billSeparately |
| SERVICE_INSTANCE | ServiceInstance.billSeparately |
| CHARGE_TEMPLATE | ChargeTemplate.billSeparately |
| CHARGE_INSTANCE | ChargeInstance.billSeparately |
When BillSeparatelyDetector finds at least one flagged record in the BR scope, the matching ID column gets added to both SELECT and GROUP BY of the aggregation query — forcing one IL per flagged unit at that level. The form does not toggle this; it is configured per entity.
Computing the expected IL count
The total number of ILs produced ≈ number of unique combinations of:
billing account × accounting article × tax bucket × seller × date bucket × discount columns (if NO) × subscription (if not ignored) × service instance (if not ignored) × user account (if not ignored) × order (if not ignored) × unit amount (if not aggregated) × each additionalAggregationFields value × each active billSeparately level
For high-volume datasets, removing knobs (e.g. setting ignoreSubscriptions=true) significantly cuts the IL count.