Billing
v1 lite — currently a stub. Only what MVP needs (recording raw Usage Events) is built; charge tracking and invoicing are designed below but not implemented.
Packages: packages/domain/billing, packages/application/billing
Purpose
The Billing context will track the financial relationship between the organisation and its
people. Today it does exactly one thing: records Usage Events (raw hours-flown facts) so that
future charge calculation has an input to work from. Charge Items, Invoice Drafts, and the
invoice lifecycle described below are a design, not a build — packages/application/billing
is currently export {} and db/schema/billing.ts has only the usage_events table.
Key entities
Implemented
Usage event
The raw, unpriced usage fact recorded automatically when a Scheduling Flight completes — hours
flown per meter type, computed as an end-minus-start delta from the Flight’s Checkout/Return
meter readings. Written via the UsageEventRecorder port from returnBooking; Billing does not
call into Scheduling, only receives this write.
Key attributes:
id— unique identifierflightId— the Flight this usage was computed from (FlightId)personId— the person who flew (PersonId)aircraftId— the aircraft used (AircraftId)hobbsHours/tachHours— hours flown per meter type; either may be null if that meter wasn’t recorded, but at least one must be presentoccurredAt— when the Flight completedcreatedAt— when the event was recorded
Persisted (usage_events table, UsageEventRecorderD1) but not yet surfaced anywhere — it’s the
input a future Billing pass will use to compute a Charge Item, not a Charge Item itself. No rate,
price, or amount is attached at this stage.
Planned — not yet implemented
The two entities below have no domain type, no application use case, and no schema yet. They’re recorded here as the intended design for whenever Billing moves past the usage-event stub, not as a description of current behaviour.
Charge item (aggregate root or entity)
A single billable line item against a Person. The atomic unit of billing.
Key attributes:
id— unique identifierpersonId— the person being charged (PersonId)bookingId— optional reference to the Booking that generated the charge (BookingId | null)description— human-readable description (e.g.C172 ZK-ABC — 1.2 hrs at $150/hr)quantity— amount or duration (e.g. hours flown)unitPrice— price per unit in cents (avoids floating-point currency issues)total— computed:quantity × unitPriceinvoiceId— null until added to an Invoice DraftcreatedAt— when the charge was recorded
Invoice draft
An un-issued invoice accumulating Charge Items for a Person.
Key attributes:
id— unique identifierpersonId— the invoice recipient (PersonId)lineItems— list ofChargeItemIdreferencessubtotal— sum of all line item totalsstatus—draft|issued|voidcreatedAt— draft creation timestampissuedAt— null until issued
Domain rules
Implemented:
- A Usage Event’s hours-flown delta must not be negative (an end reading below the start reading is rejected), and at least one meter type must have a complete start/end pair.
Planned (Charge Item / Invoice Draft, not yet implemented):
2. A Charge Item must reference an active Person.
3. Charge Items cannot be modified once added to an issued Invoice.
4. An Invoice Draft may be edited freely before issuance.
5. Once issued, an Invoice transitions to issued and is immutable; corrections require a new invoice or credit note.
6. All monetary values are stored in the smallest currency unit (e.g. cents).
Key use cases
Implemented:
recordUsageEvent— record a raw hours-flown fact, called byreturnBooking.
Planned (not yet implemented):
recordChargeItem— create a Charge Item against a Person (optionally linked to a Booking).createInvoiceDraft— create a new draft Invoice for a Person.addChargeToInvoice— attach a Charge Item to an existing Invoice Draft.issueInvoice— finalise and issue an Invoice Draft.voidInvoice— void a draft or issued invoice.
Cross-context relationships
| Context | Usage |
|---|---|
| People | References PersonId for the charge/invoice recipient |
| Scheduling | Charge Items may reference BookingId to link charges to bookings; receives Usage Events written via the UsageEventRecorder port (Scheduling writes, Billing does not read back) |
| Notifications | Invoice issuance triggers a notification to the Person |