# Module — Projection e-commerce

> Phase 4. **Module clé** : synchronisation **central → base client** pour alimenter le futur site e-commerce de chaque boutique. Il s'agit surtout de **mécanique de synchro**, pas de nouvelles entités centrales majeures.
> Références : **[Architecture multi-tenant §4bis](../../portail-manager/docs/ARCHITECTURE_MULTITENANT.md)** · Liens : [README modules](README.md) · [Vision](../README.md) · [Architecture](../01-architecture.md) · [Modèle de données](../02-modele-de-donnees.md) · [Sécurité](../03-securite.md) · [API](../04-api.md) · [Décisions](../notes/decisions.md).

## 1. Objectif

Propager vers la **base de données propre à chaque boutique** un **sous-ensemble en lecture** des données POS, pour que son site e-commerce lise **sa** base sans jamais toucher la base centrale.

- **Admin plateforme** : provisionner/piloter la projection, rejouer/rattraper une boutique.
- **Manager** : publier son catalogue vers son site (indirectement, via les données POS qu'il gère).
- **Site e-commerce (consommateur)** : lit sa base projetée (catalogue, prix, stock) et **remonte ses commandes** au centre.

## 2. Périmètre

**In**
- **Synchronisation central → base client** des données POS publiables.
- **Cible d'écriture** = la `CompanyDatabase` du client, via **`TenantConnectionService`**.
- **Mécanisme recommandé (a)** : **événements de domaine via Symfony Messenger** — à chaque changement POS pertinent, un message met à jour la projection.
- **Idempotence** (upsert par clé métier), **le central gagne**, **cohérence éventuelle**, projection **en lecture seule** côté client.
- **Remontée des commandes** du site (`order`) vers le centre.
- Commande de rattrapage **`app:tenant:project {boutique?}`** (ETL/rejeu).

**Out (v1)**
- Réplication SQL native (option (c), écartée pour une projection partielle et transformée).
- Écriture métier depuis le site (le site n'écrit que panier/comptes visiteurs dans sa base).
- Résolution temps réel de conflits bidirectionnels (le central est autoritaire).

## 3. Entités

**Base centrale** : **pas de nouvelle entité métier majeure**. Ajouts techniques :
- ⬜ **`ProjectionOutbox`** (optionnel, pattern *outbox*) : `company`, `entityType`, `businessKey`, `payload`, `op` (`upsert`|`delete`), `status`, `createdAt` — garantit la fiabilité du dispatch Messenger.
- ✅ **`CompanyDatabase`** (existant, 1:1 `Company`) : cible/connexion chiffrée ; `schemaVersion` pour détecter les bases en retard.

**Schéma de projection (base client, lecture seule)** — tables cibles :
- `product` (clé métier : `product_uuid`), `product_category` (`category_uuid`), `price` (`product_uuid` + contexte), `stock` (`product_uuid` [+ dépôt]), `order` (commandes du site remontées au centre).

Montants en **centimes XOF**. Chaque ligne projetée porte sa **clé métier** (UUID central) pour l'upsert.

## 4. Permissions / commandes d'admin

| Code / commande | Type | Usage |
|---|---|---|
| `projection.manage` | permission (Admin) | Piloter la projection, rejouer, voir l'état de sync |
| `app:tenant:project {boutique?}` | commande CLI | Reconstruit/rafraîchit la projection (ETL, rattrapage) |
| `app:tenant:migrate {boutique?}` | commande CLI (socle §5) | Applique le schéma data-plane à la base client |

Gating admin uniquement (plan de contrôle) ; jamais de connexion tenant sur les routes du control plane.

## 5. API (`/api/v1`)

> La projection est surtout **interne** (Messenger + CLI). API réduite au **pilotage** et à la **remontée des commandes**.

| Méthode | Route | Corps / params | Permission |
|---|---|---|---|
| `GET` | `/api/v1/projection/status?boutiqueId=` | état de sync par boutique (retard, dernier run) | `projection.manage` |
| `POST` | `/api/v1/projection/replay` | `{ boutiqueId, entityType?, since? }` — rejoue la projection | `projection.manage` |
| `POST` | `/api/v1/ecommerce/orders` | commande émise par le site → remontée au centre (`Idempotency-Key`) | (clé site / scope tenant) |

Le site e-commerce **lit sa base directement** (pas d'API centrale pour la lecture catalogue) ; l'API ne sert qu'au pilotage et à la remontée `order`. Enveloppe `ApiResponseFormatter`.

## 6. Écrans

**Web (Twig, admin)** — ⬜
- **Tableau de bord de projection** : par boutique, état (à jour / en retard), dernier run, nombre d'éléments, bouton **Rejouer**.
- Détail des erreurs de sync (dernier échec, payload en cause), lien vers l'outbox.

**PWA** — aucun écran dédié (mécanique serveur).

## 7. Règles métier

- **Déclencheurs (mode a)** : création/modification de `Product`, `ProductCategory`, changement de `Price` ou de `Stock`, statut de `Sale`/commande → **publication d'un message Messenger** qui exécute l'**upsert** dans la `CompanyDatabase` cible.
- **Idempotence** : écriture par **`INSERT … ON DUPLICATE KEY UPDATE`** (ou équivalent) sur la **clé métier** (UUID central). Rejouer un message n'a aucun effet de bord.
- **Le central gagne** : en cas de divergence, la valeur centrale écrase la projection ; la base client n'est **jamais** autoritaire.
- **Cohérence éventuelle** : latence acceptée côté site ; ordre non garanti → chaque message porte un `version`/`updatedAt` pour ignorer les écritures obsolètes (last-writer-wins par timestamp central).
- **Lecture seule client** : la projection n'est modifiée que par la sync ; le site n'écrit que ses propres données (panier, comptes visiteurs), qui ne remontent **pas**, sauf `order`.
- **Remontée `order`** : les commandes du site sont écrites dans la base client puis **remontées au centre** (message/endpoint), où elles deviennent des ventes/commandes autoritaires (et éventuellement facturées, cf. [Facturation](facturation-taxes.md)).
- **Robustesse** : connexion tenant ouverte paresseusement, fermée en fin de traitement ; échecs journalisés `FileLogger` (jamais de mot de passe), retry Messenger ; **outbox** pour ne perdre aucun changement.
- **Sélection du périmètre** : seuls les produits/prix « publiables » sont projetés (filtre de publication à définir — ADR-OPEN-B).

## 8. Projection e-commerce

C'est **l'objet même du module**. Résumé du contrat de projection :

| Table client | Source centrale | Clé métier | Sens |
|---|---|---|---|
| `product` | `Product` (publiable) | `product_uuid` | central → client |
| `product_category` | `ProductCategory` | `category_uuid` | central → client |
| `price` | prix produit/contexte | `product_uuid`(+ctx) | central → client |
| `stock` | `Stock`/mouvements | `product_uuid`(+dépôt) | central → client |
| `order` | commandes du site | `order_uuid` | **client → central** (remontée) |

## 9. Tests

- **Unitaires** : upsert idempotent (rejeu sans doublon), last-writer-wins par timestamp, filtre de publication.
- **Intégration** : `TenantConnectionService` écrit dans une base client de test (fixture `amansa`) ; un changement `Product` central → ligne `product` cliente à jour ; suppression → `op=delete` propagé.
- **Fonctionnels** : `POST /projection/replay` reconstruit la projection ; `GET /projection/status` reflète le retard ; remontée `order` idempotente (`Idempotency-Key`).
- **Résilience** : message rejoué N fois = état stable ; base client indisponible → retry sans perte (outbox).

## 10. Dépendances

- ✅ Socle multi-tenant (phase 1) : `CompanyDatabase`, **`TenantConnectionService`**, `EncryptionService`, `DatabaseBackupService` (généralisable).
- Métier : [Catalogue produits](catalogue-produits.md), [Stock & inventaire](stock-inventaire.md), [POS / Ventes](pos-ventes.md), [Facturation](facturation-taxes.md).
- Infra : Symfony **Messenger** (`MESSENGER_MODE`), `FileLogger`, `ApiResponseFormatter`, [Administration boutiques](administration-boutiques.md) (provisioning des bases).
- Détail complet du modèle et des mécanismes : **[docs/ARCHITECTURE_MULTITENANT.md §4bis](../../portail-manager/docs/ARCHITECTURE_MULTITENANT.md)**.

## 11. Notes / ADR liés

- **ADR-001** — Base centrale autoritaire + projection POS.
- **ADR-OPEN-B** — Périmètre exact de la projection (tables/colonnes publiées).
- **ADR-OPEN-C** — Mécanisme de synchro : **Messenger événementiel recommandé (a)** vs ETL planifié (b).
- **ADR-OPEN-D** — Bases clients : même serveur vs instances dédiées, utilisateur DB dédié (moindre privilège).
- Recommandation : commencer par (b) ETL `app:tenant:project` pour valider le schéma, basculer vers (a) Messenger + outbox pour le temps quasi réel.
