Contracts
Contract entities represent rental agreements as business records. A contract is not just a PDF; the PDF is a document linked to the contract.
contracts
Purpose
Central business entity representing a rental contract.
Suggested attributes
| Field | Description |
|---|---|
id | Primary identifier. |
property_id | Property covered by the contract. |
contract_type | RESIDENTIAL, COMMERCIAL, TEMPORARY, STUDENT, TOURIST, or OTHER. |
contract_date | Signature or registration date. |
start_date | Contract start date. |
end_date | Contract end date. |
original_end_date | First legal end date captured before any auto-renewal catch-up. |
renewal_type | Renewal logic or description. |
auto_renewal_enabled | User-confirmed auto-renewal flag. Existing contracts remain false after migration. |
auto_renewal_period_months | Renewal period length in months. |
auto_renewal_notice_days | Days before end date by which notice must be given. |
auto_renewal_notice_deadline | Computed deadline for notice. |
auto_renewal_next_date | Next end date after the latest applied or proposed renewal. |
auto_renewal_status | NONE, ACTIVE, CANCELLED_BY_NOTICE, DISABLED, or ERROR. |
rent_amount | Main recurring rent amount. |
rent_frequency | MONTHLY, QUARTERLY, YEARLY, or CUSTOM. |
deposit_amount | Security deposit amount. |
payment_due_day | Day of month or period when rent is due. |
notice_period_months | Notice period in months. |
tax_regime | Contract tax regime. |
cedolare_secca_flag | Whether cedolare secca applies. |
bank_account_id | Default logical bank account for collection. |
status | DRAFT, ACTIVE, EXPIRED, or TERMINATED. |
conditions | Structured or free-form contract conditions. |
notes | Operational notes. |
created_at | Creation timestamp. |
updated_at | Last update timestamp. |
Relationships
| Relationship | Description |
|---|---|
contracts.property_id to properties.id | Each contract belongs to one property. |
contracts to contract_units | A contract may cover one or more units. |
contracts to contract_parties | A contract has role-based parties. |
contracts to rent_installments | A contract generates expected rent installments. |
contracts.bank_account_id to bank_accounts.id | Optional default collection account. |
contracts to documents | Contract files are documents linked to the contract. |
contracts to contract_renewal_events | Auto-renewal audit trail, reminders, cancellations, and scheduler actions. |
Notes and design rationale
The contract stores business facts used by rent scheduling, tax reporting, alerts, and reconciliation. Legal files are stored in documents.
Contract deletion cleanup
Contract deletion is orchestrated by the dedicated data-service module
backend/data-service/modules/contracts/contractDeleteOrchestrator.js. The
data-service module does not access MySQL directly: it authenticates the user,
checks contract ownership through the normal route guard, receives the cleanup
options selected by the user, deletes document/vector resources through the
owning services, and delegates database preview/cascade work to datahub.
The database-owned part is implemented in
backend/datahub/modules/contracts/contractDeleteCascade.js and exposed through
internal-only datahub endpoints:
POST /internal/contracts/:id/delete-previewPOST /internal/contracts/:id/delete-cascade
Those endpoints require an internal JWT with audience datahub and scope
datahub:contracts:delete. They are not browser APIs and must not contain
business ownership decisions.
POST /contracts/:id/delete-preview returns the counts of connected records and
the default cleanup options. POST /contracts/:id/delete applies the selected
options. DELETE /contracts/:id is kept for compatibility and applies the
default cleanup options.
By default, contract deletion performs a bottom-up cleanup before deleting the contract row:
- Collects rent installments linked to the contract.
- Collects contract-scoped expected expenses.
- Collects bank transactions connected through rent matches, expense matches, or bank transaction splits.
- Deletes rent payment matches.
- Deletes expense payment matches for contract-scoped expected expenses.
- Deletes bank transaction splits that point to the contract, its rent installments, or its expected expenses.
- Deletes contract-scoped expected expenses instead of letting them become orphaned
contract_id=NULLrecords. - Resets impacted bank transactions to unreconciled by clearing reconciliation fields.
- Deletes linked contract documents through
docs-service, which also removes extracted text and vector index content owned by those documents. - If document deletion is disabled but vector cleanup is enabled, it calls
document-indexer-serviceto remove the contract document vectors only. - Detaches manual expenses, linked mail messages, and analytics read models that are not removed by FK cascade.
- Deletes explicit contract entity shares.
- Deletes the contract only after the manual cleanup, letting FK cascades remove
contract_bank_accounts,contract_units,contract_parties,contract_renewal_events,rent_installments, andrent_installment_lines.
Property-level expenses are preserved. If an expense should survive contract deletion, it must be modeled without contract_id.
Auto-renewal migration policy
The auto-renewal migration is intentionally conservative:
- Existing contracts are not mass-enabled.
auto_renewal_enableddefaults tofalse.auto_renewal_statusdefaults toNONE.- No migration backfills renewal assumptions from existing PDFs or extracted text.
- Any auto-renewal activation must be explicitly reviewed and saved by a user.
For historical data, use the read-only assisted script:
cd backend/data-service
node scripts/proposeContractAutoRenewals.js --datahub-url http://localhost:3000 --format json
The script scans document text already linked to contracts and returns proposed candidates with evidence snippets. It never writes to the database and never enables auto-renewal.
Auto-renewal rollout
Rollout is split into controlled releases so each operational risk can be validated independently:
| Release | Scope | Runtime behavior |
|---|---|---|
| 1 | Schema, form, LLM extraction, catch-up calculation. | No scheduler-driven renewal. Users can review extracted fields before saving. |
| 2 | Catch-up rent installment generation. | Missing installments are generated idempotently when a user saves a catch-up renewal. |
| 3 | Scheduler dry-run. | Nightly system job calls the internal dry-run endpoint and logs what would happen. No writes. |
| 4 | Real automatic renewal and notifications. | Scheduler applies due renewals, creates audit events, generates missing installments, and sends notice reminders through alertingService. |
Operational switches:
| Setting | Purpose |
|---|---|
CONTRACT_AUTO_RENEWALS_MODE | dry-run for release 3, run only when release 4 is intentionally enabled. |
CONTRACT_AUTO_RENEWALS_ENABLED | Enables or disables the scheduler system job. |
CONTRACT_AUTO_RENEWAL_NOTIFICATIONS_ENABLED | Enables notification sending for reminders. |
CONTRACT_AUTO_RENEWAL_REMINDER_DAYS | Comma-separated reminder offsets, default 90,30,7. |
CONTRACT_AUTO_RENEWALS_NOTIFICATION_TO | Fallback recipients when no owner/manager email exists. |
contract_renewal_events
Purpose
Audit trail for every auto-renewal decision and side effect.
Suggested attributes
| Field | Description |
|---|---|
id | Primary identifier. |
contract_id | Linked contract. |
event_type | SCHEDULED, APPLIED, APPLIED_ON_CREATE, SKIPPED, CANCELLED, ERROR, or NOTICE_REMINDER_SENT. |
old_end_date | Contract end date before the event. |
new_end_date | Contract end date after the event. |
notice_deadline | Notice deadline involved in the event. |
renewal_date | Date on which the event was applied or evaluated. |
renewal_period_months | Renewal period length used by the event. |
status_before | Auto-renewal status before the event. |
status_after | Auto-renewal status after the event. |
created_by | SYSTEM, USER, or LLM. |
created_by_user_id | User who triggered the event, when applicable. |
reason | Human-readable reason. |
metadata_json | Structured details such as reminder offset or generated installment counts. |
contract_units
Purpose
Links contracts to one or more property units.
Suggested attributes
| Field | Description |
|---|---|
id | Primary identifier. |
contract_id | Linked contract. |
property_unit_id | Linked property unit. |
notes | Free-form notes. |
Relationships
| Relationship | Description |
|---|---|
contract_units.contract_id to contracts.id | Many units can belong to one contract. |
contract_units.property_unit_id to property_units.id | One unit can appear in many historical contracts. |
Notes and design rationale
A contract may cover a whole property, a single room, multiple rooms, parking, storage, or a combination of units.
contract_parties
Purpose
Role-based relationship between parties and contracts.
Suggested attributes
| Field | Description |
|---|---|
id | Primary identifier. |
contract_id | Linked contract. |
party_id | Linked party. |
role | LANDLORD, CO_LANDLORD, TENANT, CO_TENANT, GUARANTOR, AGENCY, or MANAGER. |
percentage | Share or allocation percentage when relevant. |
start_date | Role start date. |
end_date | Role end date. |
notes | Free-form notes. |
Relationships
| Relationship | Description |
|---|---|
contract_parties.contract_id to contracts.id | One contract can have many parties. |
contract_parties.party_id to parties.id | One party can participate in many contracts. |
Notes and design rationale
The model supports multiple landlords, co-landlords, tenants, co-tenants, guarantors, agencies, and managers without schema changes.