Finance — Common cases
How to read each case: start with the symptom/question, then read the expected behavior, how to verify it in the UI/API/data/code, the source anchor, and the Real-world example. These cases prioritize running behavior; open the linked spec/code when you need the original contract.
Invoice missing or wrong in the manager
Check in order: the original invoice is active, lock/processing is not blocking, the projection has refreshed, and the search key/booking number sync is not stale.
Real-world example: An accountant searches the invoice by booking number but it does not show in the manager → isolate whether it is data or an unrefreshed projection → the original record is correct but the projection is stale → run reconcile instead of editing the invoice.
Does the Subscription invoice include Enrichment?
No. Check two separate sources: the Subscription invoice takes the base schedule/Add-on from EnrollmentBillingSchedule; Booking reconciliation takes the priced Enrichment TermBookingLine. Do not add TermBookingLine to SubscriptionTermBillingSchedule, EnrollmentInvoiceLog, or the Subscription InvoiceLine.
Real-world example: Step 2 reports 10 Enrichment occurrences at
$25, while the Subscription schedule is$5. → The Subscription invoice remains$5; Booking invoice/reconciliation processes$25. → If the Subscription invoice becomes$30, stop issuing it and check the service boundary.
Billing Details reports missing billing although the schedule is invoiced
Completeness must count active EnrollmentBillingSchedule rows belonging to the exact SubscriptionTermBillingSchedule currently open. Do not use every EnrollmentPeriod for the same Subscription/Term; another period must not block the current schedule.
Real-world example: An Enrollment has period A invoiced and period B uninvoiced. → Open Billing Details for schedule A. → Do not show the
Generate Billing Requiredmodal just because period B is still missing.
Booking Enrichment is charged twice
Compare the semantic key TermBookingId + TermProductId + Date, active Booking lines, and TermBookingInvoiceLog. If the Subscription invoice also contains that amount, this is a duplicate charge path; do not fix it by manually editing the invoice. Fix the boundary/projection and then reconcile.
Real-world example: A worker retry creates the same occurrence twice. → Keep one active Booking line and one invoice log. → The remaining retry converges/skips and does not create an additional Subscription invoice.
Editing a confirmed booking changes money
Preview diff first, confirm persists the real invoice/credit-note. Preview does not create schedulers, finance logs, or real credit notes.
Real-world example: Admin adds 2 sessions to a confirmed booking → the preview shows the diff (no ledger writes) → only Confirm creates the real invoice/credit-note.
The billing state changes after Preview review
If Confirm still carries a PreviewSnapshotToken but the billing state is no longer eligible for Preview, the system returns PREVIEW_BILLING_DIFF_STALE, rolls back the transaction, and requires Preview again. It must not silently switch to direct-confirm after an admin has reviewed a preview.
Real-world example: Admin reviews an invoice increase → the original invoice becomes inactive before Confirm → Confirm is blocked without a write → admin runs Preview again to review the current amount.
Preview with multiple attendees or booking groups
- Only attendees belonging to the orders submitted in the current edit are shown separately. This is the same scope confirmed by the Submit & Confirm popup.
- Another booking group sharing the same
BillingIdis grouped intoOther booking groups; amounts that cannot be tied to an attendee are placed inShared / Unallocated. - The
Net billing impacttotal still covers the whole billing scope and must reconcile to the detail rows. Do not divide this total by the attendee count and call it a per-attendee price.
Real-world example: The form shows
$65/attendee but Preview reports+$68. →$65is the form estimate;+$68is the net change across the whole billing scope after new invoices, credit notes, and scheduled-delete invoices. → Reconcile the breakdown and transaction summary instead of treating both as the same fee type.
Mixed invoice state in Preview
A billing record containing retained and Generated invoices is not blocked solely because the state is mixed. If the retained invoice logs are consistent, Preview reconstructs the context, keeps the retained invoice, and places unpaid Generated invoices in ScheduledToDelete. Only missing context or unverifiable attribution/logs returns a structured error; no guessed amount is shown.
Real-world example: One retained invoice and one Generated invoice share a BillingId → line logs map clearly → Preview shows the retained invoice and the amount to void. If the retained invoice mapping is ambiguous → Preview stops with an error so the data can be fixed; admin cannot Confirm using an estimated amount.
Credit note does not match invoice
Cross-check the original invoice link, allocation, attendee/header sync, and booking number. If the projection is wrong but the original record is correct → refresh/reconcile the projection.
Real-world example: Cancelling a booking → the credit note shows an amount different from the original invoice → determine whether the mismatch is real or only in the projection → allocation + original invoice link match → reconcile the projection, do not manually edit the credit note.
Payment collected but invoice not updated
Distinguish Payment from Finance: the provider transaction may be paid, but the invoice/projection needs a job/event to update state. Read the payment record, invoice status, scheduler/error.
Real-world example: A parent paid via PayNow and the receipt said success, but the invoice still shows "unpaid" → money received (Payment) but the ledger not yet updated (Finance) → provider paid but the event/job has not run → check scheduler/error, do not charge again.
Xero sync fails
The error can be in provider configuration, account/tax mapping, invoice/credit-note data, or retry state. Do not edit the original invoice just to "make Xero pass" when the AIMY document is correct.
Real-world example: An invoice cannot push to Xero because a tax code is not mapped → fix the mapping instead of distorting the document → map account/tax correctly then retry sync, keep the original AIMY invoice unchanged.
Invoice email not sent or no PDF
Check the site's template/channel setting, invoice status (Approved/Paid), and permission. Invoice emails always send without PDF; customers must use the auth-protected View/Download link (INV-INVOICE-EMAIL-003/005).
Real-world example: A customer reports "I received the invoice email but no PDF file" → this is correct behavior: the email has no attachment → guide the customer to open the
link; the system requires login before showing/Downloading the PDF.
Reminder not sent for an owing invoice
Cross-check eligibility at run time: active + AmountDue > 0 but excluded if in the DD collection flow, has LockTypeId != null, or the site disables the email channel/template. Every case has a skip reason; skip does not set Invoice.Sent (INV-INVOICE-REMINDER-003).
Real-world example: Invoice AIN-0031 still owes but did not receive the reminder on due+7 → check the skip reason → the invoice is in the DD collection flow so it is excluded by the rule; not a send error.
Invoice emailed but no Viewed tag
Viewed is only set when an authenticated customer successfully opens View/Download PDF; admin opening details, resending email, or unauthorized requests do not set it (INV-INVOICE-VIEW-002). The tag only reflects real customer access.
Real-world example: An admin asks why an invoice emailed yesterday still has no
Viewedtag → email does not count as viewed → the customer clicks the link and logs in successfully → refresh shows theViewedtag.
Invoice Processing — cannot access Add Payment/Apply Credit
LockTypeId != null → the list shows the Processing tag, details show Payment in processing, Add Payment/Apply Credit disabled but still visible (INV-INV-MGR-LOCK-005). Not a bug — wait for the payment flow to finish.
Real-world example: An accountant wants to add another payment to invoice AIN-0090 but the button is grey → the list shows the Processing tag → a payment is processing so manual actions are locked by the rule; wait until the lock clears.
Empty Booking ID but the invoice still has a booking
Empty FinanceTransaction.BookingNumbers does not prove the invoice has no association — it may be an integrity gap (TermBookingInvoiceLog missing InvoiceId, projection not synced). Cross-check the source active TermBookingInvoiceLog; empty BookingNumbers shows - in the Booking ID column (invoice-manager-booking-id-integrity).
Real-world example: Invoice AIN-0163 shows Booking ID
-even though it still has a real booking → look up the source association and find an activeTermBookingInvoiceLogbut an empty projection → run projection repair/backfill, do not manually edit the invoice.
Invoice_Fully_Paid notification not sent
Not sending is correct when: the invoice is marked Paid with TotalAmount = 0, the invoice is imported/synced already Paid, the site disables the email channel, or the account lacks a primary contact email (INV-INVOICE-PAID-NOTIF-001/003/004).
Real-world example: A parent pays in full but does not receive the confirmation email → check whether the invoice is zero-total or email is disabled at the site → account lacks a primary contact → the system does not throw, records the reason "no recipient"; not a payment error.
Closure Day credit exceeds money collected
Do not use TermBookingLine.TotalAmount as the credit basis. Compare issued invoice-log amount minus active credit-note amount; uninvoiced or already offset lines are skipped and partly credited lines credit only the remainder.
Real-world example: A line totals $120, $80 was issued and $20 credited. → Credit $60, not $120; use tax/currency from the source invoice and keep the converted line at zero.
Invoice Manager cannot open Booking Info
Booking Info opens only from a Booking ID with BillingId and is read-only. Rows without billing keep the existing text/tooltip; requests outside BU or account scope return not-found without billing data.
Real-world example: Click a Booking ID on an invoice covering several programmes. → The popup loads all active bookings for that billing, not only the current invoice; Go to Booking Manager keeps the billing filter.