# Project Status

**Last updated:** 2026-05-13
**Current phase:** development

---

## Active Tasks

*(None — reviews-sync async refactor shipped, see Recent Decisions)*

---

## Blockers

*(None)*

---

## Next Up

Hors-scope identifié dans le plan de cleanup V1, à traiter séparément :
- Validation signature webhook `/api/webhook/messaging` (sécurité)
- Refactor `messages-view.tsx` (497 lignes, deux modes mélangés)
- Purge `attachments[].Content` base64 anciens (DB bloat)
- Migration `sync-now` GET → POST (sémantique)
- Modèle OpenAI configurable via env var
- Normaliser `properties.co_host_id` (actuellement ambigu : `users.id` ou `users.rentals_subuser_id`)
- Migration NOT NULL sur `discussions.co_host_id` à déployer après backfill prod réussi (`2026_05_05_120100`)
- Étendre `tryRuSubuserId` aux subuser IDs channel-spécifiques (BookingCom envoie `Contacts[].ID = 9257004` quand le RU subuser principal de Hichem est `734927`).

**Action data-fix prod (post-merge) :** les 4 lignes mal attribuées par la sync du 2026-05-06 (id 133, 146, 147, 149 → toutes Hichem mais `co_host_id=1` = Farouk) doivent être corrigées à la main. `UPDATE discussions SET co_host_id = NULL WHERE id IN (133,146,147,149)` puis `php artisan messagerie:sync-latest` (les 4 threads ont `LastMessageDate=2026-05-06`, donc dans la fenêtre 23h). Avec Fix A déployé, `syncLatest` résoudra correctement à Hichem (id=330).

---

## Recent Decisions

- **2026-05-13** — Sync miroir des réservations vers le serveur TLL (Node, `TLL_SERVER_URL`). Nouveau service `App\Services\Tll\TllReservationSyncService` exposant 2 méthodes (`block`, `release`) qui POSTent vers un endpoint unique `POST /reservation/sync` côté TLL avec `{operation, external_id, property_id, check_in?, check_out?}`. Auth réutilise `TllAuthService::forUser(Auth::user())` avec retry 403 → refreshToken (même pattern que `TllProxyController`). Comportement best-effort : try/catch englobant, log warning sur échec, **ne throw jamais** — TLL est un miroir, RU/manager restent la vérité. Toggle `config('tll.sync_enabled')` (env `TLL_SYNC_ENABLED`, default true) pour kill-switch sans redéploiement. Nouveau `config/tll.php` (`url` + `sync_enabled`). Règle de déclenchement : sync TLL suit **exactement** le même critère que la sync RU (si on parle à RU, on parle à TLL juste après ; si on ne parle pas à RU, on ne touche pas TLL). 6 hooks branchés : `ReservationsController::store` après `saveLocalReservation` (block), `ReservationService::updateReservation` après sync RU + `applyLocalUpdates` (block sur `$reservation->fresh()`), `ReservationExtensionService::create` après `updateReservationAfterExtension` (block), `ReservationExtensionService::delete` après `deleteExtensionAndRollbackCheckout` (block sur nouveau check_out), `ReservationsController::annulerContrat` après `cancelReservation` réussi (release), `ReservationsController::destroy` avant `$reservation->delete()` (release). Anciennes réservations (`type_reservation === 'ancienne_reservation'`) skip TLL automatiquement car le flux ne traverse pas le hook. Fix collatéral : `tests/Feature/Reservation/SaveLocalTest.php` instanciait `new ReservationService()` sans args (déjà cassé avant — `RentalsService` requis) → migration vers `app(ReservationService::class)` pour résolution DI. Contrat côté TLL à implémenter : `POST /reservation/sync` avec auth JWT, upsert par `external_id` pour `block`, DELETE par `external_id` pour `release`. 7 nouveaux tests `Tests\Feature\Tll\TllReservationSyncServiceTest` (payload block/release, kill-switch, token absent, refresh 403, swallow d'échec HTTP, swallow d'exception auth). 207 tests downstream (CRM/EventInvitations/Messaging/Reviews/PropertyArchive/Settings) toujours verts. **Endpoint à créer côté Node TLL** — voir le plan dans la conversation pour le contrat exact.

- **2026-05-12** — Refonte du bouton « Synchroniser » sur `/reviews` en mode background, pour contourner le timeout dur FastCGI d'OVH mutualisé qui renvoyait un 500 Apache au bout de quelques minutes (le PHP-FPM continuait en background, prouvé empiriquement, mais le frontend voyait un 500 — fragile et non observable). Nouvelle table `review_sync_runs` (pending/running/completed/failed + stats JSON + co_host_id scoping + source manual/scheduled). Nouveau job `App\Jobs\SyncReviewsJob` (queue database, `$tries=1`, `$timeout=3600`) qui transitionne le run et écrit dans `storage/logs/reviews_sync.log`. `ReviewController::sync()` dispatch le job + retourne instantanément avec flash info ; garde-fou anti-doublon : refuse de dispatcher si un run actif existe déjà pour le même co-host. Nouvelle route `GET /reviews/sync/status` (JSON) pollée toutes les 5s par le frontend (`filters-card.tsx`), avec reprise d'état au mount si une sync était déjà en cours avant un refresh. Toast de succès/erreur déclenché sur la transition active→terminal + `router.reload({ only: ['items', 'stats', 'sources'] })` pour rafraîchir la table. Worker queue planifié via `Schedule::command('queue:work --once --stop-when-empty --max-time=55 --tries=1')->everyMinute()->withoutOverlapping()->runInBackground()` dans `routes/console.php`. Sync quotidienne automatique : nouvelle option `--days-back=N` sur la commande artisan `reviews:sync` (compute la from-date relative à aujourd'hui), schedulée `Schedule::command('reviews:sync --days-back=2 --log-file=logs/reviews_sync.log')->dailyAt('03:00')`. La commande artisan reste utilisable en SSH pour les gros backfills (`--from-date=2020-01-01`). 9 nouveaux tests `Tests\Feature\Reviews\ReviewsSyncTest` (38 tests reviews au total, tous verts). Note déploiement OVH : le cron `php artisan schedule:run` doit être actif dans le panel OVH (il l'est déjà puisque 4 commandes scheduled tournent — events:notify-today, drafts:cleanup, ai:build-property-profiles, tokens:cleanup). Fréquence cron limitée selon le plan d'hébergement (Personal=horaire, Pro=10min, Performance=1min) — détermine le délai entre clic et démarrage du job.

- **2026-05-06** — Bouton « Traduire en FR » sur les messages reçus de la messagerie. Petit pill `🌐 FR` discret en bas-droite des bulles entrantes (`is_incoming=true`). Au clic : appel API maison `POST translation.thelandlord.tn/translate_to_fr` avec `{"text": ...}`, lecture de `translated_text` dans la réponse, écrasement de `messagerie_chats.body`. Service isolé `App\Services\Messaging\MessageTranslator` (mockable, ~30 lignes). Nouvelle route `POST /guest-communication/chats/{chat}/translate` (groupe `messagerie.create`). Config : `messaging.translation_api_url` + env `TRANSLATION_API_URL`. 6 tests (happy path, cross-tenant 403, outgoing 422, body vide 422, API failure 500, admin override).

- **2026-05-06** — `messagerie:sync-history` refondu : nouveau param `--from-date=YYYY-MM-DD` (default `2025-01-01`). Bascule de `MessagingHistorySyncService::sync()` (table-driven, exigeait `reservations` peuplée) vers `syncFromApi()` (mois-par-mois SOAP via `listReservationsByDate`, indépendant de la DB). Suppression de la méthode `sync()` morte (175 lignes), du helper `logUnexpectedPayload`, et de 2 imports inutilisés. 4 nouveaux tests pour la commande.

- **2026-05-06** — Suppression définitive des 3 commandes post-mortem du bug Farouk (`messaging:backfill-cohost`, `messagerie:reclassify-discussions`, `messagerie:detach-orphan-chats`) + leur unique fichier de test. Aucune n'était schedulée ni référencée dans les cron-*.php. Le `CoHostResolver` reste la solution durable. Nettoyage `.claude/INTEGRATIONS.md` + `STATUS.md`.

- **2026-05-06** — Double fix `CoHostResolver` (post-mortem du bug Farouk-bis sur DB fraîche). **Fix A** : `collectCandidateNames` lit maintenant `Contacts[].FirstName + LastName` (les 2 orientations) en plus de `Contacts[].Name`. Les payloads RU réels (BookingCom/Airbnb) n'envoient jamais `Name`, et inversent souvent first/last. Sans ça `tryContactName` était aveugle → fallback PrimaryFallback systématique. **Fix B** : `MessagingHistorySyncService::syncFromApi` résout désormais par-thread via `resolveFromThreadPayload($thread, null)` (5 stratégies) au lieu d'un seul `resolveFromReservationId($reservationId)` (2 stratégies, blind sur DB vide). Le `co_host_id` résolu par thread est aussi propagé à `Chat::upsertFromApiMessage` au lieu d'utiliser une valeur unique réservation-level. 3 nouveaux tests dont la reproduction exacte du payload BookingCom de prod (id=133). Voir `errors.md` pour le post-mortem détaillé.

- **2026-05-05** — Bouton « Historique complet » dans l'UI guest-communication. Récupère depuis RentalsUnited tous les messages d'une discussion (depuis le tout premier) et les upsert localement. Réutilise `MessagingHistorySyncService::syncForThread($threadId)` (déjà testé). Nouvelle route `POST /guest-communication/discussions/{id}/sync-history` (groupe `messagerie.create`), action `GuestCommunicationController::syncHistory()`. Bouton masqué pour les canaux `tll_internal`, `facebook_comment`, `instagram_comment` (RU n'est pas la source de vérité). 5 nouveaux tests : happy path, cross-tenant 403, canal non supporté 422, échec API 500, admin via credentials du propriétaire. 31 tests `GuestCommunicationControllerTest` passent.

- **2026-05-05** — Refonte complète de la résolution co-host pour la messagerie RU. Création du service `App\Services\Messaging\CoHostResolver` centralisant 5 stratégies ordonnées (ReservationProperty, RuSubuserId, ContactName, InboxOwner, PrimaryFallback). Garde-fou anti-cross-tenant via `Chat::upsertFromApiMessage($msg, $id, ?int $expectedCoHostId)`. `MessagingHistorySyncService::syncLatest()` ne stamp plus arbitrairement l'utilisateur itéré, et détecte les drifts. `MessagingWebhookController` utilise le même resolver. Migration `2026_05_05_120000` : UNIQUE sur `users.rentals_subuser_id` + index composite. Commandes `messagerie:reclassify-discussions` (dry-run par défaut) et `messagerie:detach-orphan-chats` pour le backfill rétroactif. `BackfillDiscussionCoHostCommand` marqué deprecated. 17 nouveaux tests dont reproduction du scénario exact du bug Farouk. Voir `errors.md` pour le post-mortem.

- **2026-04-13** — Added CLAUDE.md, ARCHITECTURE.md, STATUS.md, errors.md, and .tasks/ to project root for AI agent session memory.
- **2026-04-30** — Suppression complète de l'ancienne messagerie V1 (`GuestCommunicationController` original, 6 routes `/thread/*` et `/guest-communication`, dossier `pages/messagerie/` legacy avec 4 fichiers, item sidebar). V1 était un controller pur API → Rentals United, sans persistance, remplacé par la V2.
- **2026-04-30** — Renommage V2 → version principale : `GuestCommunicationV2Controller` → `GuestCommunicationController`, `pages/messagerie-v2/` → `pages/messagerie/`, route prefix `/guest-communication-v2` → `/guest-communication` (10 routes + 14 références frontend). Conservé tel quel : canal Pusher `messagerie.{coHostId}`, commands `messagerie:*`, table `messagerie_chats`, permissions `messagerie.*`, config/messaging.php.
- **2026-04-30** — Ajout de 33 tests (Loi 4) couvrant le webhook (11), le controller V2 (13), AiSuggestController (6), AiSuggestionService (3 + 1 skip MySQL-only). Fix collatéral : la migration FULLTEXT (`2026_04_02_101005`) skip désormais sur SQLite, débloquant la suite de tests entière (200 → 191 passants, sans nouveaux échecs introduits).
