Note (2026-04-24): After this document was written,
legal_entitywas renamed totenantand the oldtenantwas renamed toorganization. Read references to these terms with the pre-rename meaning.
Date: 2026-03-12 Status: Approved
Utility invoices (and similar recurring-service invoices) bundle multiple financial components into a single payment:
The current system only models (1) and (2). Fee line items like Mahngebühr are extracted as line items but break the validation formula because they sit outside the subtotal→total path. Installments are not captured at all.
The "Zu zahlender Betrag" (actual payment amount) = total - prepayments + fees + installments, but amount-due currently only validates against total - prepayments.
Strom inkl. USt (total): 2.000,23€ ← services rendered
Bezahlte Teilbeträge (prepayments): -450,00€ ← prior installments paid
Restforderung: 1.550,23€
Mahngebühr (fee line item): 6,50€ ← outside total
Erster neuer Teilbetrag: 544,00€ ← advance for next period
Zu zahlender Betrag: 2.100,73€ ← actual bank transfer
Installment type (top-level on InvoiceData)(def Installment
"Future installment payment bundled with this invoice.
Represents an advance payment toward a future billing period."
[:map
[:description :string]
[:amount number?]
[:due-date [:maybe :string]]]) ;; ISO 8601
;; On InvoiceData:
[:installments [:maybe [:vector Installment]]]
Installments are separate from line items because:
:category field on LineItem[:category {:optional true} [:enum :service :fee]]
:service (default when absent) — contributes to subtotal → total:fee — outside the total, additive to payment amount (late fees, dunning charges)Optional field: existing data without a category is implicitly :service.
amount-due semanticsNo schema change. amount-due represents the actual payment amount (Zu zahlender Betrag), not just total - prepayments.
- installments = array of NEW installment payments for FUTURE billing periods bundled into this invoice.
These are advance payments toward future service/consumption, not charges for services on this invoice.
Only extract installments that are included in the total payment amount — not future scheduled
payments shown for informational purposes.
CRITICAL: Do NOT confuse with prepayments (which are PRIOR payments being deducted).
- prepayments = money already paid in the past, subtracted from total
- installments = money being collected now for future periods, added to payment amount
Common in utility bills, subscription services, and recurring service contracts where the
settlement invoice bundles advance payments for the next period.
amount-due definition- amount-due = the actual amount to be paid / transferred.
This is the final payment amount on the invoice.
It includes: total - prepayments + fees + installments.
If there are no prepayments, fees, or installments, amount-due equals total.
category field"category": "service" | "fee" // default "service". Use "fee" ONLY for charges
// that appear AFTER the invoice total — late fees,
// dunning charges, reminder fees. Everything else
// is "service".
check-prepayments → renamed to check-amount-dueNew formula:
amount-due ≈ total - Σ(prepayments) + Σ(fee line items) + Σ(installments)
Uses existing document-level tolerance (0.6%, floor 0.10).
check-subtotalFilter to only :service line items:
subtotal ≈ Σ(line items where category ∈ {:service, nil})
check-total or check-line-itemsFee line items don't affect the total formula. Individual line item math is unaffected by category.
:prepayment → :amount-due**Amount-due error**:
Verify amount-due = total - sum(prepayments) + sum(fee line items) + sum(installments).
- If we extracted wrong prepayment/installment amounts: provide corrections
- If fee line items were miscategorized (should be service or vice versa): provide corrections
- If the document's amount-due is wrong: confirmed=true
Correction paths: prepayments, installments, amount-due, line item category.
Note that only :service line items contribute to subtotal. FVR can correct miscategorized line items.
installment-items (mapv (fn [{:keys [description amount]}]
{:description (or (sanitize-booking-text description)
"Teilbetrag")
:totalGrossAmount amount})
installments)
api-line-items (-> gross-line-items
(into prepayment-items)
(into installment-items))
No accountNumber on installment line items — accountant assigns the correct prepayment asset account in DATEV. See [GH issue TBD] for deferred account assignment decision.
amount-due is now the Zu zahlender Betrag, so existing logic works as-is.
Subtotal → Discount → Shipping → Packaging → Surcharges → Tax → Total
→ Prepayments (negative, green)
→ Fee line items (positive, with description)
→ Installments (positive, with description and due date)
→ Amount Due
Fee line items appear in the payment summary (between Total and Amount Due) in addition to the line items table, so the user understands why amount-due ≠ total - prepayments.
Amount Due row shown when any of: prepayments, fee items, installments exist, or amount-due ≠ total.
Create a GitHub issue to evaluate account assignment strategies post-rollout: