# CLAUDE.md — App Fidélité (tablette magasin) Guide pour travailler sur ce projet. À lire avant toute modification. ## Vue d'ensemble Application **Flutter** de **programme de fidélité** pour un magasin, sur **tablette Android en portrait** (caisse). Chaque client a une carte = un **QR code** ; le staff scanne, ajoute des **factures** (montant € → **points**), et le client échange ses points contre des **récompenses**. Fait partie d'un écosystème de **deux apps** partageant le même backend : - **`app_fideliter`** (CE repo) : app **tablette / staff**. - **`app_fideliter_client`** (repo séparé) : app **client** (compte, points, QR). App sœur `gestion_prix_produit` (autre projet) : même stack et mêmes conventions, reprises ici. Thème identique mais **accent vert émeraude** (au lieu du bleu) pour distinguer les apps sur une même tablette. ## Stack technique - **Flutter** (Material 3), Dart. Cible : Android tablette, portrait verrouillé. - **PocketBase** comme backend (auto-hébergé sur un **Raspberry Pi**) : auth + base SQLite + API REST + **temps réel (SSE)**. - Packages clés : - `pocketbase` — client backend (auth, CRUD, réaltime) - `shared_preferences` — persistance de la session (rester connecté) - `mobile_scanner` — scan du QR client (caméra ; scanette externe à venir) - `qr_flutter` — affichage/génération du QR d'un client - `intl` — formatage € et dates (locale `fr_FR`) - Icône via `flutter_launcher_icons` (source : `assets/icon/`). ## Backend PocketBase - **URL serveur** : `https://db.tailb756e1.ts.net` (définie dans [`lib/pocketbase_config.dart`](../lib/pocketbase_config.dart)). Le Raspberry est exposé **publiquement en HTTPS via Tailscale Funnel** (gratuit) → accessible de partout (4G, autre WiFi…), pas seulement sur le LAN. En local, le serveur reste aussi joignable en `http://192.168.1.32:8090`. Tailscale est installé **sur le Pi uniquement** ; les clients n'installent que l'app. Le Pi doit rester allumé + connecté à Internet. - **Compte staff** : `mathew.simon2004@gmail.com` (mot de passe défini au setup). Un compte est « staff » si son champ `is_staff = true` dans la collection `users`. - **Superuser** (admin PocketBase) : géré via l'UI `http://192.168.1.32:8090/_/`. ### Collections | Collection | Type | Champs principaux | Accès (règles) | |---|---|---|---| | `users` | auth | `is_staff` (bool) + email/password | chacun voit son compte ; `is_staff` non modifiable par l'utilisateur | | `clients` | base | `user` (relation, option.), `code` (unique), `nom`, `prenom`, `telephone`, `points` | staff = tout ; client = sa fiche uniquement | | `mouvements` | base | `client` (relation), `type` (facture/recompense/ajustement), `montant_euros`, `points`, `libelle`, `created` | staff écrit ; client lit les siens (anti-triche) | | `recompenses` | base | `nom`, `cout_points`, `actif` | tout connecté lit ; staff modifie | | `reglages` | base | `euros_par_point`, `nom_magasin` (1 seule ligne) | tout connecté lit ; staff modifie | ### Points de vigilance backend - **Pas de trigger PocketBase** : le solde `clients.points` est maintenu **par l'app** (`ClientRepository._ecrireSolde`), qui crée le mouvement PUIS met à jour le solde. Comme seul le staff écrit, pas de souci de concurrence. - **Code client** (`FID-XXXXXX`) généré **côté app** (`ClientRepository._genererCode`), unicité garantie par un index unique + réessais. - Créer/modifier des collections : voir l'admin UI, ou l'API superuser (`POST /api/collections/_superusers/auth-with-password` puis `/api/collections`). ## Architecture de l'app - **Singletons** `.instance` pour les services/repositories. - **Repositories** `ChangeNotifier` = source de vérité, écoutés via `AnimatedBuilder`. - **Temps réel** : chaque repository s'abonne (`pb.collection(...).subscribe`) et met à jour sa liste en mémoire → l'UI se synchronise entre tous les appareils. - **Auth** : `AuthGate` écoute `pb.authStore.onChange`. Session persistée via `AsyncAuthStore` (SharedPreferences) → on reste connecté. ### Structure ```text lib/ ├── main.dart # init (locale fr, PocketBase) + thème ├── config.dart # constantes (ratio défaut, préfixe code) ├── pocketbase_config.dart # URL serveur, client `pb`, helper `estStaff` ├── theme.dart # thème noir & blanc + accent vert émeraude ├── models/ # client, mouvement, recompense (fromMap PocketBase) ├── services/ │ ├── reglages.dart # ratio €/point + nom magasin (+ temps réel) │ ├── client_repository.dart # clients + factures + récompenses + points (+ temps réel) │ └── recompense_repository.dart# catalogue (+ temps réel) ├── screens/ │ ├── auth_gate.dart # login staff ↔ app selon la session │ ├── staff_login_screen.dart │ ├── home_shell.dart # 4 onglets │ ├── clients_screen.dart # liste + recherche + création │ ├── client_edit_screen.dart # Nom / Prénom / Téléphone │ ├── client_detail_screen.dart # solde, facture, récompense, historique (édit/suppr, temps réel) │ ├── scan_screen.dart # scan QR → fiche client (_ouvrirParCode) │ ├── recompenses_screen.dart │ └── reglages_screen.dart # ratio, nom magasin, état serveur, déconnexion ├── widgets/ # pastille_points, qr_client └── utils/format.dart # euro(), points(), dateHeure() (fr_FR) ``` ## Règles métier - **Points gagnés = montant facture ÷ `euros_par_point`** (fractionnaires, ex. 12 € à 10 €/pt = 1,2 pt). - Changer le ratio n'affecte **que les nouvelles factures** (pas de recalcul rétroactif — volontaire). - Éditer une facture recalcule ses points au **ratio courant** et réajuste le solde. - Récompenses : gagner **et** dépenser des points (débit à l'utilisation). ## Conventions de code - **Tout en français** : noms de variables, méthodes, commentaires, libellés UI. - Reprendre le style existant (singletons, `ChangeNotifier`, `copyWith`, `fromMap`). - Modèles : `fromMap(record.toJson())` où `record` est un `RecordModel` PocketBase. Les relations sont des **id** (string) ; `created` est une date ISO (string). ## Commandes ```bash flutter pub get flutter analyze # doit rester à 0 problème flutter test flutter build apk --release # Installer en gardant les données (session conservée) : adb -s install -r build/app/outputs/flutter-apk/app-release.apk adb devices # lister les appareils branchés ``` Icône : `dart run flutter_launcher_icons` après modif de `assets/icon/`. ## Roadmap / à faire - **Scanette USB externe** : se comporte comme un clavier → brancher un champ caché qui appelle `scan_screen.dart` → `_ouvrirParCode` (déjà factorisé). - **Accès externe** au serveur (hors LAN) : IP/domaine public + **HTTPS** (puis retirer `usesCleartextTraffic`). - Impression du QR (carte physique), bonus de bienvenue / paliers, statistiques. - App client (`app_fideliter_client`) : à aligner sur ce backend PocketBase.