Files
lmnp-organizer/docs/adr/0011-rental-aggregate-boundaries.md
T

5.4 KiB

Keep rental aggregates small

Status: accepted

Within Rental Management, Lease owns its contractual lifecycle, dates, rent terms, and lease-party relationships while referencing independent Apartment and Tenant aggregates by ID. Activation freezes the landlord identity and address, apartment address, tenant identities and addresses, guarantees, and financial terms; later changes are effective-dated immutable LeaseAmendment records that preserve both contract versions and may only recalculate future unpaid dues. A LeaseGuarantee is an internal lease entity for one physical guarantor and exactly one tenant already named by that lease; it is never expanded into every guarantor-tenant combination. RentDue and RentPayment are independent aggregates; a positive payment owns allocations whose sum cannot exceed the amount received, each allocation cannot exceed its due's remaining balance, and one payment may cover multiple dues only within the same lease. Any surplus stays unallocated. LeaseGuarantee remains internal to Lease until it gains an independent lifecycle or must be shared across leases.

Apartment owns stable PrivateArea values covering rooms and annexes under the tenant's exclusive use. A lease snapshot freezes those identifiers and descriptions; furniture-inventory lines, property-condition observations, and photo evidence all reference them, while the regulatory main-room count is derived rather than stored independently.

FurnitureInventory is also an independent aggregate referenced by LeaseId, not a growing collection inside Lease. It remains editable with the lease draft and receives every explicitly reconciled entry discrepancy before a LeaseDocumentRevision freezes its inventory snapshot; changing the inventory then obsoletes that revision, and lease finalization freezes the aggregate.

PropertyConditionReport is an independent aggregate identified by its own ID and uniquely constrained by (LandlordId, LeaseId, ENTRY|EXIT). It owns its observations, photo references, finalization evidence, and accepted supplement requests. A request accepted before signature changes the draft and invalidates the affected confirmations; one accepted after finalization is append-only. Lease does not embed that growing inspection history.

Creating the EXIT report snapshots the effective finalized ENTRY report, including accepted supplements, while preserving the stable room, zone, inventory-line, and entry-photo references needed for deterministic comparison. The exit draft then evolves independently and presents entry photos as area references without requiring one-to-one photo pairing.

A new Lease is always a mutable, permanently deletable DRAFT; draft overlap is allowed and creates no dues. Explicit activation reserves the period in the apartment's versioned ApartmentOccupancy aggregate, which retains active and archived lease periods and rejects inclusive overlap, then creates one RentDue per landlord, lease, and occupied calendar month under a unique database constraint. Making concurrent activations contend on the same occupancy document prevents write skew that a query-only overlap check would allow. Boundary months prorate rent and charges by occupied calendar days. An amendment split combines its before and after portions before performing one monetary rounding step, while settlement and due-date timing remain independent facts on the due.

Lease activation, amendment reconciliation, payment confirmation and later allocation, reversal, and refund confirmation run in Mongo transactions whenever they change more than one aggregate document. Versioned aggregate documents reject stale concurrent writes; occupancy reservations, contract snapshots, generated or reconciled dues, payments, reversals, and refunds therefore commit or roll back without a single-process lock or manual database compensation. A payment owns at most one allocation per due, preventing two stale copies of one due from being mutated inside the same command.

Lease PDFs, payment receipts, and rent receipts are rendered from frozen contract versions. A payment receipt is available only for a confirmed payment and reports its complete amount, allocations, refunds, and remaining surplus. A rent receipt is available only for a fully settled due and reports exactly that due's rent and charges; it never treats unallocated payment surplus as rent income.

Lease ending is the deliberate exception to the rule that a settled due is never recalculated. Recording an earlier contractual end preserves each due's allocations but replaces its visible amounts with the exact amount owed through the end date. That final amount is calculated by occupied day from the effective-dated contract and applied IRL timeline, so an amendment or revision after the end contributes nothing. Later months remain persisted as hidden zero-amount tombstones carrying their pre-ending rent and charges; this makes withdrawal or invalidation restore the schedule exactly, including intervening amendments and revisions, without recreating aggregate identities. A tombstone is excluded from schedule responses and cannot produce a rent receipt. If the preserved allocations exceed a recalculated due, correctionRequired is exposed and rent-receipt generation remains blocked until the existing reversal workflow corrects the allocations. Payment recording, later allocation, and reversal remain available for existing dues after the lease is archived.