# ARCHITECTURE.md — The Landlord Manager

> High-level system design, tech stack decisions, and architectural patterns.
> Keep this file up to date as the system evolves.

---

## 1. SYSTEM OVERVIEW

**The Landlord Manager** is a multi-tenant Property Management System (PMS) for vacation and seasonal rental businesses. It manages the full property lifecycle: onboarding, reservations, check-in, contracts, financial tracking, team communication, and external channel synchronization.

**Primary users:**
- **Admin / Manager (gérant)** — Full system access
- **Co-Host** — Access to specific properties they manage
- **Sub-User** — Limited access (e.g., cleaning staff, accountant)
- **Owner** — External access to property contracts (no system login)
- **Guest** — External access to questionnaires, contracts, check-in (no system login)

---

## 2. TECH STACK

### Backend

| Component | Technology | Version |
|-----------|-----------|---------|
| Framework | Laravel | 12.x |
| Language | PHP | 8.2+ |
| ORM | Eloquent | — |
| Authorization | Spatie Laravel Permission | 6.18 |
| PDF Generation | barryvdh/laravel-dompdf | 3.1 |
| Image Processing | Intervention Image | 3.11 |
| SMS/Voice | Twilio SDK | 8.11 |
| External Sync | Rentals United API (XML) | — |
| Routing (Full-Stack) | Ziggy | 2.4 |
| Queue | Laravel Queue (database driver) | — |

### Frontend

| Component | Technology | Version |
|-----------|-----------|---------|
| Framework | React | 19.0.0-rc.1 |
| Language | TypeScript | 5.7 |
| Full-Stack Bridge | Inertia.js | 2.0 |
| Build Tool | Vite | 6.x |
| CSS Framework | Tailwind CSS | 4.x |
| UI Primitives | Radix UI / shadcn/ui | — |
| Forms | React Hook Form + Zod | 7.56 / 3.24 |
| Tables | TanStack Table | 8.21 |
| Calendar | FullCalendar | 6.1 |
| Charts | Recharts | 2.15 |
| Maps | Leaflet + React Leaflet | 1.9 / 5.0-rc |
| Rich Text Editor | TinyMCE React | 6.2 |
| Drag & Drop | react-beautiful-dnd | 10.0 |
| Notifications (toast) | Sonner | 2.0 |
| Signature | react-signature-canvas | 1.1-alpha |
| PDF Export (client) | jsPDF + html2canvas-pro | 3.0 / 1.5 |
| Excel Export | xlsx | 0.18 |
| Theme | next-themes | 0.4 |

### Database

| Environment | Driver |
|-------------|--------|
| Production | MySQL 8.x |
| Testing | SQLite (in-memory) |

### Infrastructure

| Component | Technology |
|-----------|-----------|
| Dev Server | php artisan serve + Vite dev |
| Production Assets | Vite build → `public/build/` |
| Queue Worker | `php artisan queue:work` |
| Scheduled Tasks | Laravel Scheduler + cron |
| PWA | Vite PWA Plugin + Service Worker |

---

## 3. ARCHITECTURAL PATTERNS

### 3.1 Service-Oriented Architecture

Business logic lives exclusively in `app/Services/`. Controllers are thin orchestrators.

```
HTTP Request → Controller (validate, authorize) → Service (business logic) → Model (data) → Inertia Response
```

**Service classes:**
- `PropertyService` — Property CRUD, image management
- `ReservationService` — Booking logic, availability checks
- `RentalsUnitedApiService` — External API sync (45KB, most complex)
- `RentalsPayloadBuilder` — XML payload construction for Rentals United
- `RentalsService` — Rentals business operations
- `SendMailService` — Email dispatch orchestration
- `FolderService` — Workflow folder operations (shared by onboarding, tickets, reservation workflows)
- `DashboardService` — KPI calculations and dashboard data
- `QuizService` — Survey creation and distribution
- `RecoveryService` — Payment tracking
- `ClientService` — Client operations
- `PropertyContractService` — Contract generation and signature management
- `LocationsService` — Geographic hierarchy management
- `PropertyDraftService` — Draft property management
- `ReservationExtensionService` — Stay extension calculations
- `IdEncoderService` — Encrypted ID encoding/decoding for public links
- `Messaging\CoHostResolver` — Centralized resolution of the co-host that owns a Rentals United thread/message. Five ordered strategies (ReservationProperty, RuSubuserId, ContactName, InboxOwner with anti-leak guard, PrimaryFallback). Used by `MessagingHistorySyncService` and `MessagingWebhookController`.

### 3.2 Inertia.js Full-Stack Rendering

There is no separate REST API for the React frontend. Data flows through Inertia:

```php
// Controller
return Inertia::render('reservations/index', [
    'reservations' => $reservations,
    'filters' => $request->only(['search', 'status']),
]);
```

```tsx
// React Page (resources/js/pages/reservations/index.tsx)
export default function Index({ reservations, filters }: PageProps) {
    // ...
}
```

**Shared props** (available on every page via `usePage().props`):
- `auth.user` — Authenticated user with roles/permissions
- `locale` — Current locale (`fr` / `en`)
- Defined in `app/Http/Middleware/HandleInertiaRequests.php`

### 3.3 Multi-Tenancy

Every user who manages properties has a `co_host_id` (or `id` for top-level accounts). ALL data queries must scope to this ID.

```php
// Correct pattern
$properties = Property::where('co_host_id', auth()->user()->coHostUser()->id)->get();

// WRONG — leaks data across tenants
$properties = Property::all();
```

The `coHostUser()` method on the `User` model returns the correct scoping user (either the user themselves or their co-host parent).

**Rentals United dual-ID system:**
- `rentals_subuser_id` — ID in Rentals United's system
- `co_host_id` — ID in The Landlord Manager's system
- These are NOT interchangeable. Always check which one a function expects.

### 3.4 Permission System

Uses Spatie Laravel Permission with a custom middleware (`PermissionPrefix`).

```php
// Middleware adds the action suffix automatically
Route::middleware(['permission_prefix:reservation.'])->group(function () {
    Route::get('/reservations', ...)        // checks: reservation.view
        ->name('reservations.index');
    Route::post('/reservations', ...)       // checks: reservation.create
        ->name('reservations.store');
    Route::put('/reservations/{id}', ...)   // checks: reservation.update
        ->name('reservations.update');
    Route::delete('/reservations/{id}', ...)// checks: reservation.delete
        ->name('reservations.delete');
});
```

Permission domains: `reservation`, `property`, `owner`, `client`, `user`, `caisse`, `paiement`, `depense`, `reclamation`, `calendar`, `planification`, `question`, `newsletter`, `messagerie`, `revenue`, `channel_manager`

### 3.5 Event-Driven Side Effects

**Observers** handle automatic side effects:
- `OnboardingFolderObserver` — Fires on onboarding folder state changes
- `FolderEventObserver` — Creates audit events when folder operations occur

**Jobs** handle async work:
- `SendReservationEmailsJob` — Batch reservation emails
- `ProcessReservationsJob` — Post-reservation processing
- `SyncPropertyJob` — Async Rentals United property sync
- `SendNewsletterEmailJob` — Newsletter distribution
- `SendQuizEmailJob` — Survey distribution

### 3.6 Public (Unauthenticated) Features

Several features are accessible without login via encrypted IDs:

| Feature | Route | Service |
|---------|-------|---------|
| Guest questionnaire | `/questionnaires/show/{encryptedId}` | `QuizService` |
| Reservation contract signing | `/reservations/contract/{encryptedId}` | `ReservationContractController` |
| Payment receipt | `/paiement/recu/{encryptedId}` | `RecoveryController` |
| Property contract (owner) | `/property/contract/{code}` | `PropertyContractController` |

Use `IdEncoderService` to generate and decode these encrypted IDs.

---

## 4. DATABASE DESIGN

### Schema Principles

1. All tables have `timestamps` (created_at, updated_at).
2. Multi-tenant tables include `co_host_id` foreign key referencing `users.id`.
3. Audit trails use `*_histories` tables (e.g., `reservation_histories`).
4. Workflow states use `*_statuses` tables with user-customizable statuses.
5. Polymorphic relations used in `folder_events` for cross-workflow events.

### Core Entity Relationships

```
User (co_host_id) ──┬── Property ──┬── Reservation ──┬── Recovery
                    │              │                  ├── ReservationHistory
                    │              │                  ├── ReservationContract
                    │              │                  └── OnlineCheckin
                    │              ├── PropertyBlock
                    │              ├── PropertyContract (→ Owner)
                    │              ├── Planning ──── PlanningAssignment
                    │              └── OnboardingFolder ──── FolderEvent
                    ├── Client ── Reservation
                    ├── Reclamation
                    └── CompanyDetail
```

### Financial Data Flow

```
Reservation ──┬── Recovery (payments received)
              └── Expens (costs)

Caisse = sum(Recovery) - sum(Expens) for a period/property
```

---

## 5. FILE STRUCTURE

```
project-root/
├── CLAUDE.md              # AI agent instructions (read every session)
├── ARCHITECTURE.md        # This file
├── STATUS.md              # Living project status
├── errors.md              # Append-only error log
├── .tasks/                # Task briefs
├── app/
│   ├── Console/Commands/  # 9 Artisan commands
│   ├── Constants/         # WhatsAppTemplates
│   ├── DTOs/              # Data Transfer Objects (Dashboard/)
│   ├── Helpers/           # DateHelper, XmlToArray, ArrayToXml
│   ├── Http/
│   │   ├── Controllers/   # 31 controllers (thin, delegate to Services)
│   │   │   ├── Api/       # Webhook handlers
│   │   │   ├── Auth/      # 8 auth controllers
│   │   │   └── Settings/  # Profile, Password
│   │   ├── Middleware/    # SetLocale, PermissionPrefix, HandleInertia, HandleAppearance
│   │   ├── Requests/      # 29 form request validation classes
│   │   └── Resources/     # 3 API resource transformers
│   ├── Jobs/              # 9 queued jobs
│   ├── Mail/              # 7 Mailable classes
│   ├── Models/            # 48 Eloquent models
│   ├── Observers/         # OnboardingFolderObserver, FolderEventObserver
│   ├── Providers/         # AppServiceProvider
│   └── Services/          # 17 service classes (core business logic)
├── config/                # 31 config files
├── database/
│   ├── migrations/        # 39 migration files
│   ├── seeders/           # RolesAndPermissionsSeeder, DatabaseSeeder
│   └── factories/         # 4 model factories
├── resources/
│   ├── css/               # Tailwind entry
│   ├── js/
│   │   ├── app.tsx        # React + Inertia entry point
│   │   ├── ssr.tsx        # SSR entry point
│   │   ├── components/    # 30+ reusable components
│   │   │   └── ui/        # 44 shadcn/ui components
│   │   ├── hooks/         # Custom React hooks
│   │   ├── layouts/       # auth, guest, admin layouts
│   │   ├── lib/           # Utilities
│   │   ├── pages/         # 25+ Inertia page components
│   │   └── types/         # TypeScript type definitions
│   └── views/
│       ├── app.blade.php  # Main Inertia template
│       ├── emails/        # Modern email Blade templates
│       ├── mail/          # Legacy email templates
│       ├── pdf/           # PDF Blade templates
│       └── newsletter/
├── routes/
│   ├── web.php            # 356 web routes
│   ├── api.php            # API / webhook routes
│   ├── auth.php           # Auth routes
│   └── settings.php       # Settings routes
├── tests/
│   ├── Feature/           # Controller/integration tests
│   └── Unit/              # Service unit tests
└── public/
    ├── sw.js              # PWA Service Worker
    └── build/             # Vite compiled assets
```

---

## 6. KEY ARCHITECTURAL DECISIONS

| Decision | Choice | Rationale |
|----------|--------|-----------|
| Full-stack rendering | Inertia.js (not REST API) | Avoids maintaining a separate API layer, type-safe props via TypeScript |
| Authorization | Spatie Permission (not Gate/Policy only) | Granular role-based access needed for multi-tenant SaaS |
| Frontend components | shadcn/ui + Radix | Accessible, composable, no opinionated styling lock-in |
| Form validation | Zod + React Hook Form | Type inference from schema, eliminates duplicate type definitions |
| External sync | Cron pull + webhook push | Rentals United requires both for real-time and batch sync |
| PDF generation | DomPDF (server) | Contracts need server-side generation for legal consistency |
| ID obfuscation | Custom IdEncoderService | Public contract/quiz links need encrypted IDs, not sequential integers |
| Multi-tenancy | co_host_id column pattern | Simpler than schema-per-tenant for this use case, adequate for scale |

---

## 7. SECURITY MODEL

1. **Authentication:** Session-based (Laravel Sanctum for API tokens).
2. **Authorization:** Spatie permission middleware on all protected routes.
3. **Multi-tenancy isolation:** `co_host_id` scoping in every data query.
4. **Public links:** Encrypted IDs via `IdEncoderService` — IDs are never exposed in plain form.
5. **Input validation:** All mutations go through Form Request classes.
6. **CSRF protection:** Laravel's built-in CSRF middleware on all web routes.
7. **File uploads:** Images processed through Intervention Image before storage.

---

## 8. PERFORMANCE CONSIDERATIONS

1. **Eager loading:** Always eager-load relationships to avoid N+1 queries.
   ```php
   Reservation::with(['property', 'client', 'recoveries'])->where(...)
   ```
2. **Pagination:** Large datasets (reservations, properties) must be paginated.
3. **Queue:** Email sending, newsletter distribution, and sync tasks run in background jobs.
4. **Cache:** Config and routes cached in production (`php artisan optimize`).
5. **Vite build:** Production assets are minified and versioned.
6. **Virtual rendering:** Large lists use `react-window` for virtualization.

---

*Last updated: 2026-04-13*
