Skip to main content

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.id
  • offerTemplate
  • accountingArticle.id
  • tax.id, taxPercent, taxInclusive
  • seller.id

The eleven knobs

KnobTypeDefaultEffect
disableAggregationbooleanfalseWhen true, no aggregation at all — one IL per RT.
dateAggregationenumMONTH_OF_USAGE_DATEBucket RTs by usage date.
discountAggregationenumFULL_AGGREGATIONCollapse discount RTs or keep them separate.
useAccountingArticleLabelbooleanfalse (UI defaults to TRUE)IL description from article label vs. RT description.
aggregateUnitAmountsbooleanfalseQuantity-weighted average for ILs with different unit prices.
ignoreSubscriptionsbooleantrueAggregate across subscriptions.
ignoreOrdersbooleantrue (UI defaults to OFF)Aggregate across orders.
ignoreUserAccountsbooleantrueAggregate across consumers.
ignoreServiceInstancesbooleantrueAggregate across service instances.
incrementalInvoiceLinesbooleanfalseAppend IL data instead of rebuilding from scratch.
additionalAggregationFieldsstring[][]Extra GROUP BY columns (JPA paths).

dateAggregation — four values

ValueEffect
NO_DATE_AGGREGATIONEach unique usageDate keeps its own IL.
DAY_OF_USAGE_DATEGroup by calendar day.
WEEK_OF_USAGE_DATEGroup by ISO week.
MONTH_OF_USAGE_DATEGroup by calendar month (default).

discountAggregation — two values

ValueEffect
FULL_AGGREGATIONAll RTs (including discount RTs) collapse together.
NO_AGGREGATIONEach 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
FlagResult
OFF (default)Three ILs — unit price is part of the GROUP BY.
ONOne 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.

FlagIL description
OFFRT description (e.g. "MEAL_3C usage 2026-04-15 14:32")
ONArticle label (e.g. "Meal vouchers reimbursement")

ignoreOrders — worked example

Suppose 38 RTs across 5 distinct orders for the same (BA × article × month × parameter5 × parameter6).

ignoreOrdersILs produced
OFF (UI default)5 ILs — one per order.
ON1 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):

SuggestionEffect
parameter1parameter6Group by RT parameter slots.
orderNumberGroup by order number (independent of ignoreOrders).
subscription.codeGroup by subscription.
serviceInstance.codeGroup by service instance.
userAccount.codeGroup by consumer.
seller.codeGroup 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:

LevelCarried on
SUBSCRIPTIONSubscription.billSeparately
SERVICE_INSTANCEServiceInstance.billSeparately
CHARGE_TEMPLATEChargeTemplate.billSeparately
CHARGE_INSTANCEChargeInstance.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.