BI-37: Opening Balance Management — All Business Scenarios¶
Key Definitions¶
credit_balance — Money the family has "on their account" with the school. Like a wallet. The school owes them this money. It can be used to reduce a future bill, or refunded back to the family as cash. Lives on the Debtor record (not per-term) — it's always available regardless of which billing cycle is active.
opening_balance — Debt a family carries over from a previous billing term. The family still owes this from before. Lives on the Billing Profile (per debtor per term).
billing_profile — One record per family per billing cycle (term). This is where opening_balance lives. Each term has its own.
credit_note — An accounting document that says "the school owes the family this amount." It is the record for a refund or billing adjustment. Creating a credit note means money is leaving the school — either by reducing what the family owes on an invoice, or by returning cash.
carry-forward — The process of moving unpaid debts from one term to the next. Only opening_balance is carried forward (calculated from unpaid invoices). Credit balance does not need carrying forward because it already lives on the debtor.
How credit_balance changes¶
credit_balance lives on the Debtor record. It is not tied to any specific billing cycle.
| Event | credit_balance goes... | Why |
|---|---|---|
| Family overpays on a payment | UP | Extra money is now sitting on their account |
| A bill is generated that uses their credit | DOWN | Credit was applied as a discount on the invoice |
| Cash refund is processed | DOWN | Money returned to family |
| Credit note not applied to invoice | UP | School acknowledges it owes the family, money is parked on their account |
| Void a payment that caused an overpayment | DOWN | Reversing the overpayment — as if it never happened |
| Admin manually adjusts | UP or DOWN | Manual correction |
How opening_balance changes¶
opening_balance lives on the Billing Profile (per debtor per term).
| Event | opening_balance goes... | Why |
|---|---|---|
| Admin sets it manually or imports via CSV | SET to a value | Admin provided the number |
| Carry-forward calculates it from unpaid invoices | SET to calculated value | System computed it |
| A bill is generated that includes it | TO ZERO | Consumed — added as a line item on the new invoice |
PART 1: PAYMENT SCENARIOS¶
P1: Payment exactly covers the invoices¶
Family has Invoice A with $1,000 outstanding. Admin records a payment of $1,000 and allocates all of it to Invoice A.
- Invoice A: fully paid
- credit_balance: no change
P2: Payment is more than what's allocated (overpayment)¶
Family has Invoice A with $1,000 outstanding. Admin records $1,200 and allocates $1,000 to Invoice A. $200 is left over.
- Invoice A: fully paid
- $200 automatically added to credit_balance on the Debtor record
- Audit log: "credit_balance +$200.00 from payment PAY-FAM001-0005"
No edge case — the debtor record always exists, so the overpayment always has somewhere to land.
P3: Partial payment¶
Family owes $1,000 on Invoice A. Admin records $600, allocates all to Invoice A.
- Invoice A: partially paid, $400 outstanding
- credit_balance: no change
P4: Multiple invoices with overpayment¶
Invoice A ($500) and Invoice B ($300) outstanding. Payment $1,000: $500 to A, $300 to B.
- Both invoices: fully paid
- credit_balance: +$200
P5: Adding allocations to a payment that had an overpayment¶
Payment $1,200: $1,000 allocated to Invoice A, $200 went to credit_balance. Admin now allocates $200 of that payment to Invoice B.
- credit_balance -$200 (the unallocated portion is now allocated — reversed from the debtor)
- Invoice B: reduced by $200
- Payment is now fully allocated ($1,000 to A, $200 to B)
- Audit log: "credit_balance -$200.00 — reallocated from payment PAY-FAM001-0005 to INV-002"
P6: Adding allocations when the credit from that payment was already consumed¶
Payment $1,200: $1,000 to Invoice A, $200 to credit_balance. A bill generation consumed that $200 credit (credit_balance = $0 on the debtor). Admin now tries to allocate $200 from this payment to Invoice B.
- BLOCKED. The $200 unallocated amount from this payment was already consumed as a credit line item on a generated invoice.
- Allowing the allocation would double-spend: Invoice B gets $200 from the payment AND the generated invoice already got $200 from the credit.
- Admin must void the consuming transaction first, or record a new payment.
- Message: "Cannot allocate — the unallocated amount from this payment ($200) was consumed by credit application. Void the consuming transaction first, or record a new payment."
This is the same pattern as VP3 (void blocked when credit consumed) — triggered by adding allocations instead of voiding.
PART 2: REFUND SCENARIOS¶
The rule: When refunding a payment, the system takes from credit_balance first (the unallocated money from THAT specific payment), then reverses allocations if needed.
Important: A refund only reverses what that specific payment did. It does NOT touch credit from other payments.
R1: Full refund — payment had an overpayment¶
Payment was $1,200: $1,000 allocated to Invoice A, $200 went to credit_balance. Admin refunds $1,200.
- credit_balance -$200 (the unallocated portion from this payment)
- Reverse $1,000 from Invoice A → Invoice A back to $1,000 outstanding
- Payment status → refunded
- Credit note created for $1,200
R2: Partial refund — amount ≤ credit_balance from this payment¶
Same payment as above. Admin refunds $150.
- credit_balance -$150 (fully covered by the unallocated portion)
- Invoice A: untouched, still fully paid
- Payment status: still applied, amount_refunded = $150
- Credit note created for $150
- Remaining credit from this payment = $50
R3: Partial refund — amount > credit_balance from this payment¶
Same payment. Admin refunds $500.
- credit_balance -$200 (exhaust the unallocated portion)
- Remaining $300 reversed from Invoice A → Invoice A now $300 outstanding
- Payment status: still applied, amount_refunded = $500
- Credit note created for $500
R4: Full refund — no overpayment (payment was fully allocated)¶
Payment was $1,000, all allocated to Invoice A. credit_balance contribution = $0. Admin refunds $1,000.
- credit_balance: nothing to deduct
- Reverse full $1,000 from Invoice A → back to $1,000 outstanding
- Payment status → refunded
- Credit note created for $1,000
R5: Refund when credit_balance was already consumed by a new invoice¶
Payment was $1,200: $1,000 to Invoice A, $200 to credit_balance. A new invoice consumed that $200 credit (credit_balance = $0). Admin wants to refund $200.
- credit_balance is $0 — the credit from this payment was already used
- $200 reversed from Invoice A allocations → Invoice A now $200 outstanding
- Credit note created for $200
The new invoice that consumed the credit is unaffected.
R6: Second refund on same payment¶
Payment $1,200, first refund of $300 already done. Admin refunds another $200.
- Same logic: check remaining credit from this payment, then reverse allocations
- amount_refunded becomes $500
- Second credit note created for $200
R7: Refund exceeds remaining refundable amount¶
Payment $1,000, already refunded $800. Admin tries to refund $300 (only $200 left).
- Rejected. Cannot refund more than remaining.
R8: Refund a voided payment¶
- Not allowed. Voided payments were already fully reversed.
R9: Refund when credit_balance has credit from OTHER payments too¶
credit_balance = $500 (from multiple overpayments over time). Admin refunds Payment X ($1,000, fully allocated, $0 contribution to credit). Admin refunds $300.
- The $500 credit came from other payments, NOT Payment X
- Only Payment X's allocations are reversed — $300 from Invoice A
- credit_balance: no change (not touched — it belongs to other payments)
- Credit note created for $300
PART 3: VOID PAYMENT SCENARIOS¶
Voiding = "this payment was entered by mistake — it never happened." No credit note. Internal correction only.
VP1: Void a fully allocated payment¶
Payment $1,000 to Invoice A. Admin voids.
- Invoice A back to $1,000 outstanding
- credit_balance: no change
- Payment status → voided
VP2: Void a payment that had an overpayment¶
Payment $1,200: $1,000 to Invoice A, $200 to credit_balance. Admin voids.
- Invoice A back to $1,000 outstanding
- credit_balance -$200 (reverse the overpayment sweep)
- Payment status → voided
VP3: Void when credit_balance from this payment was already consumed¶
Payment $1,200: $1,000 to Invoice A, $200 to credit_balance. New invoice consumed the $200 credit (credit_balance = $0). Admin tries to void.
- BLOCKED. The $200 from this payment has been consumed. Voiding would make credit_balance -$200 which is invalid.
- Admin must first void the invoice that consumed the credit, then come back.
- Message: "Cannot void — credit from this payment was consumed by invoice generation. Void the consuming invoice first."
VP4: Void a partially refunded payment¶
- Not allowed. A refund already happened in reality (money was returned). Cannot pretend the payment never existed.
PART 4: CREDIT NOTE SCENARIOS¶
Credit notes are created in two ways:
- Automatically — as part of a refund (Part 2). The credit note is just the receipt.
- Manually — admin creates one directly for billing adjustments.
CN1: Credit note applied to a specific invoice¶
Invoice A: $2,000 outstanding. School overcharged $300. Admin creates a credit note for $300 applied to Invoice A.
- Credit note created ($300, immediately closed)
- Invoice A outstanding: $2,000 → $1,700
- credit_balance: no change (credit went directly to the invoice)
CN2: Credit note NOT applied to an invoice¶
Family paid everything already. School discovers a $300 overcharge. Admin creates a credit note for $300 with no invoice to apply it to.
- Credit note created ($300, immediately closed)
- credit_balance: +$300 (money is owed to the family, parked on their account)
- That $300 will later be: consumed by next invoice, carried forward, or refunded as cash
Why UP? The credit note acknowledges "we owe you." The money hasn't left yet — it's sitting as credit. It goes DOWN later when we refund it or apply it to an invoice.
CN3: Credit note via the refund flow¶
This is automatic — created by the refund endpoint. The credit_balance changes are handled by the refund logic (Part 2), not by the credit note itself. The credit note here is just the accounting record.
PART 5: SETTING OPENING BALANCES¶
OB1: Manual entry¶
Admin opens Term 2 → Billing Profiles → FAM001 → sets opening_balance = $1,200.
To set credit_balance, admin goes to the Debtor record for FAM001 and sets credit_balance = $50.
- Values saved. Audit logged.
- Lock rule (opening_balance): Only works if no transaction has been generated yet for this profile.
- credit_balance: Can be adjusted on the debtor at any time (audit logged).
OB2: Bulk import (CSV)¶
debtor_code, opening_balance, credit_balance
FAM001, 1200.00, 50.00
FAM002, 0.00, 0.00
FAM003, 850.00, 120.00
System validates all debtor codes. opening_balance is written to the billing profile (lock rule applies). credit_balance is written to the Debtor record. Logs everything.
When would you use this? When migrating from another system (like NetSuite) or when the finance team prepared the numbers in a spreadsheet.
OB3: Carry-forward¶
Admin clicks "Carry Forward from Term 1" on Term 2 and selects Term 1 as the source.
For each family in both terms:
- Sum unpaid invoices from Term 1 → opening_balance on Term 2's billing profile
- Mark Term 1's unpaid invoices as carried_forward (unpayable, closed)
- Log everything
credit_balance is not carried forward — it already lives on the debtor record and persists across terms automatically.
Skipped families: If a family is in Term 1 but not Term 2, they're skipped and reported. Their Term 1 invoices stay as-is.
OB4: Carry-forward overwrites existing values¶
If Term 2 profiles already had manual opening_balance values, carry-forward overwrites them (with a warning). Old values are in the audit log.
PART 6: GENERATING BILLS¶
TG1: include_opening_balance = ON¶
| Line Item | Amount |
|---|---|
| Tuition Term 2 | +$5,000 |
| Sibling Discount | -$500 |
| Opening Balance (Term 1) | +$1,200 |
| Credit Applied | -$300 |
| TOTAL | $5,400 |
After: opening_balance on billing profile → $0 (consumed), credit_balance on debtor → $0 (consumed)
TG2: include_opening_balance = OFF¶
Normal invoice without balance line items. opening_balance and credit_balance stay on the profile untouched.
TG3: Credit > invoice total¶
Fees = $500, debtor's credit_balance = $800. Only $500 of credit applied. Invoice total = $0. Remaining $300 stays on the debtor's credit_balance. We do NOT generate negative invoices.
TG4: Opening balance and credit on same invoice — NOT netted¶
Both appear as separate line items. Opening Balance +$1,200, Credit Applied -$300. Transparent to the family — they see both.
TG5: Void generated transactions and regenerate¶
When voiding generated transactions:
- opening_balance on billing profile restored to pre-generation value
- credit_balance on debtor restored to pre-generation value
- Profile reset to draft, ready for regeneration
PART 7: CARRY-FORWARD SPECIAL SITUATIONS¶
CF1: Reversing a carry-forward¶
Admin carried forward to Term 2 by mistake.
- Term 2: opening_balance → $0 (or to pre-carry-forward values if manually set before)
- Term 1: carried_forward invoices → un-marked (back to sent/overdue/partially_paid)
- credit_balance: not affected (it lives on the debtor, not the profile)
- Only allowed if Term 2 has NOT generated transactions yet
CF2: Family only in source term¶
FAM005 has $800 unpaid in Term 1 but no profile in Term 2. Skipped. Term 1 invoices untouched.
CF3: Mix of paid and unpaid¶
3 paid, 2 unpaid ($600 total). Only the 2 unpaid invoices contribute to opening_balance. Only they get marked carried_forward. Paid invoices are untouched.
CF4: Credit_balance and carry-forward¶
credit_balance lives on the debtor, not on the billing profile. It does not participate in carry-forward at all. Whatever credit a family has accumulated (from overpayments, unapplied credit notes, etc.) is always available on their debtor record regardless of which term is active.
CF5: Can someone pay a carried-forward invoice?¶
No. carried_forward = closed + unpayable. System rejects payment attempts. The debt has moved to the new term.
PART 8: BILLING CYCLE DELETION¶
BD1: Delete a draft cycle that received carry-forward¶
Deleting Term 2 (draft). Term 1 had invoices carried forward to it.
- Term 2 profiles deleted (with their opening_balance values)
- credit_balance on debtors: not affected (it lives on the debtor, not the profile)
- Term 1 carried_forward invoices: un-marked (back to previous status)
- Without this, the debt disappears — it left Term 1 but Term 2 no longer exists
BD2: Delete a cycle that was carried forward FROM¶
Term 1 was the source. Admin wants to delete Term 1.
- Blocked. Deleting would destroy the source of Term 2's opening balances.
- Admin must reverse carry-forward on Term 2 first, then delete Term 1.
BD3: Only draft cycles can be deleted¶
No change to this existing rule.
PART 9: DEBTOR BALANCE VIEW¶
Total owed = Sum of outstanding on all unpaid transactions − credit_balance (from Debtor record)
- If positive: the family owes the school
- If zero or negative: the family is in credit or square
Do NOT add opening_balance on top of outstanding — that would double-count once it's been consumed into an invoice.
credit_balance is always available from the debtor record. No need to look up which billing profile it belongs to.
SUMMARY TABLE¶
| Scenario | opening_balance (profile) | credit_balance (debtor) | Transactions |
|---|---|---|---|
| Overpayment | — | UP | Invoice paid |
| Reallocate overpayment to invoice | — | DOWN | Invoice reduced |
| Reallocate overpayment (credit consumed) | — | BLOCKED | Must void consuming txn first |
| Refund from credit | — | DOWN | Credit note created |
| Refund exceeding credit | — | DOWN to 0 | Credit note + invoices reversed |
| Void payment (overpayment) | — | DOWN | Allocations reversed |
| Void payment (credit consumed) | — | BLOCKED | Must resolve first |
| Credit note → invoice | — | unchanged | Invoice reduced |
| Credit note → no invoice | — | UP | Credit on account |
| Manual balance entry | SET | SET | None |
| CSV import | SET | SET | None |
| Carry-forward | SET | not affected | Source invoices carried_forward |
| Reverse carry-forward | TO ZERO | not affected | Invoices un-marked |
| Bill generated (ON) | TO ZERO | DOWN/ZERO | Balance line items added |
| Bill generated (OFF) | unchanged | unchanged | Normal items only |
| Void generated txns | RESTORED | RESTORED | Voided, profile reset |
| Delete cycle (target of CF) | deleted | not affected | Source invoices un-marked |
TRACKING NOTE¶
The refund logic (R1–R9) requires knowing how much of the current credit_balance came from a specific payment. This is tracked via the balance_audit_log — each credit_balance increase records the source payment ID so the system can determine exactly how much credit from Payment X remains available.