Skip to main content

Split Billing — five native mechanisms

Billerang has five orthogonal native split levers built into the invoicing engine. None of them requires a validation script (validation scripts can only reject invoices, not redirect RTs). They are applied in this order:

#MechanismWhere it livesWhen it splits
1. Per-IL splitbillSeparately flag on Subscription / ServiceInstance / ChargeTemplate / ChargeInstancePer entityAuto-detected per BR scope; flagged units land in their own IL set inside the same invoice.
2. Per-IL invoice typeAccountingArticle.invoiceType + AccountingArticle.invoiceTypeElPer articleEach IL routes to the article's InvoiceType. EL evaluated against the IL.
3. Per-BC EL invoice typeBillingCycle.invoiceTypeElPer cycleEL evaluated with billingRun + billingAccount + invoice as parameters → resolves an InvoiceType code per BR/BA combination.
4. Per-BC static invoice typeBillingCycle.invoiceTypePer cycleStatic fallback when EL is empty or returns null.
5. Per-BC split by payment methodBillingCycle.splitPerPaymentMethodPer cycleOne invoice per payment method on the BA (e.g. one DIRECT_DEBIT invoice + one CHECK invoice for the same BA).

Per-run default invoice type

A sixth knob lives directly on the BR: defaultInvoiceTypeCode (maps to BillingRun.invoiceType).

This is a per-run default, not a hard override. The article-level routing in mechanism #2 is evaluated before the BR-level fallback — articles can still produce multiple invoice types within the same run.

To force a single invoice type for the whole run, you would need to clear article-level routing (out of scope of the bill-run API).

Precedence chain in determineInvoiceType

For each IL, the engine picks an InvoiceType in this order:

  1. BillingRun.invoiceType (the defaultInvoiceTypeCode from the request)
  2. Default prepaid / deposit / draft (special cases)
  3. BillingCycle.invoiceTypeEl (per-BC EL)
  4. BillingCycle.invoiceType (per-BC static)
  5. Default commercial invoice type

At the IL level (during aggregation), if AccountingArticle.invoiceTypeEl is set on the article assigned to the IL, that EL wins — short-circuiting the chain.

Worked examples

Example 1 — split by payment method

A BA has two payment methods: DIRECT_DEBIT (for the recurring fee) and CHECK (for usage charges). With BillingCycle.splitPerPaymentMethod=true, a single bill run produces two invoices:

Invoice INV_001234  (DIRECT_DEBIT, recurring fee)   €99.99
Invoice INV_001235 (CHECK, usage charges) €34.50

Both invoices reference the same BA and bill run.

Example 2 — split by article invoice type

A merchant has two article codes:

Article "REIMB_3C"   invoiceTypeEl = "#{il.amount < 0 ? 'REIMBURSEMENT_NOTE' : 'INVOICE'}"
Article "COMMISSION" invoiceTypeEl = "INVOICE"

When the bill run produces ILs for both articles, the engine creates two invoices per BA: one of type REIMBURSEMENT_NOTE (containing the negative-amount REIMB_3C lines) and one of type INVOICE (containing the positive-amount lines).

Example 3 — split by billSeparately

A subscription has three service instances, one of which is marked billSeparately=true. The engine adds serviceInstance.id to the GROUP BY only for that service instance — producing one extra IL set in the invoice for it. All three service instances still land in the same invoice document.

Example 4 — combining split mechanisms

The mechanisms compose:

2 payment methods (DIRECT_DEBIT, CHECK)
× 2 article-resolved invoice types (REIMBURSEMENT_NOTE, INVOICE)
× 1 active billSeparately at SERVICE_INSTANCE level (3 instances)
= up to 12 invoices per BA, with separate IL sets within each

In practice the number is much smaller because not every combination has billable RTs.

What about validation scripts?

Validation scripts run after invoices are created. They can:

  • Pass an invoice (status = VALIDATED)
  • Reject an invoice (status = REJECTED)
  • Flag an invoice as suspect (status = SUSPECT)

They cannot redirect RTs into different invoices — that decision is made earlier, during IL grouping and invoice creation. Use the five native mechanisms above for splitting, not validation scripts.