Skip to content

ADR-0004. Polymorphic AllocationUsage(usageType, usageId) shared by SaleOrder + Reservation

FieldValue
StatusAccepted
Date2026-03-12
Deciderssale-team
Supersedes

Context

  • A dining table (AllocationUnit) can be occupied by either an active SaleOrder (a customer eating now) or a Reservation (a future booking).
  • Both need: the unit, time window, status (ACTIVE/SUCCESS/CANCELLED/EXPIRED), assignee.
  • Two parallel tables (SaleOrderAllocation + ReservationAllocation) duplicate the model and break shared queries like "what's free at 7pm".

Decision

Single AllocationUsage table with polymorphic (usageType, usageId) columns generated via generatePrincipalColumnDefs({ discriminator: 'usage' }):

  • usageType ∈ {SALE_ORDER, RESERVATION}
  • usageId references the parent's id
  • Same lifecycle, same WebSocket rooms, same cancellation cascade

When a Reservation transitions to CHECKED_IN, sale spawns a SaleOrder and the AllocationUsage's usageType is updated to SALE_ORDER + usageId to the new order id (the same usage row continues).

Consequences

ProsCons
Single occupancy modelNo DB FK to parent (polymorphic)
"Free at 7pm" query is one table scanService-layer must validate usageId exists
Cancellation cascade is uniformCasts/discriminants in service code
WebSocket fanout via getAllocationUsageRooms works for bothType-safety relies on TS, not DB

Alternatives Considered

OptionProsConsWhy rejected
Separate tables per usage typeStrong DB FKsDuplicates schema + queries + WS roomsMaintenance pain
AllocationUsage with both saleOrderId + reservationId (nullable, XOR)Single table, FKs preservedXOR constraint complex; queries always filter on typeHalf-rejected pattern

References

  • core/src/models/schemas/allocation/allocation-usage/schema.ts
  • sale/src/services/allocation-usage.service.ts
  • sale/src/services/reservation.service.ts (creates Reservation + AllocationUsage in one TX)

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.