141 lines
7.2 KiB
Markdown
141 lines
7.2 KiB
Markdown
# 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 <DEVICE_ID> 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.
|