# TelecomCash mobile (Flutter — Android / iOS)

Application de caisse pour téléphone, branchée sur l'API REST `/api/v1` de l'application web Laravel
(même comptes, mêmes permissions, mêmes règles métier). L'application **ne recalcule aucune règle métier** :
chiffre d'affaires, bénéfice, imputation des encaissements, soldes clients et de caisse viennent du serveur.
Le total affiché dans le panier est une estimation au prix catalogue ; le reçu affiche le montant renvoyé par le serveur.

## Fonctionnalités

| Écran | Contenu |
|---|---|
| Connexion | identifiant **ou** e-mail, mot de passe, adresse du serveur modifiable ; jeton Sanctum en stockage sécurisé |
| Verrouillage | code PIN (4 chiffres) et/ou empreinte / Face ID, après 2 min en arrière-plan |
| **Accueil** | indicateurs du jour / semaine / mois, solde de caisse, ventilation par opérateur, ventes sur 14 jours |
| **Vendre** | gros boutons produits aux couleurs de l'opérateur, « les plus vendus » en tête, recherche, panier +/−, client (recherche + création rapide), « Payé intégralement » activé par défaut, montant reçu → monnaie à rendre, vibration, écran de succès « Nouvelle vente » / « Partager le reçu » (WhatsApp, partage) |
| **Clients** | cartes (bordure à la couleur de la zone, badges zone / localisé, solde dû), filtre par zone (puces Tous / zones / Sans zone) combiné à la recherche, menu ⋮ Voir / Modifier / Supprimer, actions Vendre / Encaisser / Appeler / WhatsApp |
| Fiche client | solde dû, Encaisser, **« Enregistrer ma position chez le client »** (GPS haute précision, aperçu et précision ± m avant validation, hors ligne possible), position enregistrée (mini-carte, date, précision), Itinéraire, Mettre à jour, Supprimer |
| **Carte** | OpenStreetMap (sans clé) : zones en cercles colorés, clients aux couleurs de leur zone avec initiales, filtre par zone + recherche, « ma position », fiche résumée (zone, téléphone, solde dû) → Fiche complète / Appeler / Itinéraire |
| **Zones** | cartes (couleur, ville, clients, localisés), détail = carte de la zone + cartes de ses clients ; création / modification / suppression avec `zones.manage` |
| **Mouvements** | onglets Entrées / Sorties en cartes, saisie rapide avec puces de catégorie, Voir / Supprimer |
| **Plus** | rapports (résumé texte et PDF à partager), caisse du jour et clôture avec écart, ventes en onglets Non payées / Partielles / Payées / Annulées (Voir, Encaisser, Annuler, Partager), Carte, Zones, synchronisation, paramètres (PIN, biométrie, thème clair / sombre) |

| **Achats** (Plus, permission `suppliers.manage`) | achats fournisseurs regroupés Non payés / Partiels / Payés, création, modification, suppression avec motif, TVA déductible |

Thème vif aligné sur le back-office web : dégradé indigo `#4f46e5` → violet `#7c3aed`, accent fuchsia `#d946ef`, succès
émeraude → sarcelle, danger rose → rose foncé, alerte ambre → orange, TVA violet ; indicateurs du tableau de bord en tuiles
dégradées avec variation ▲/▼ ; onglet central « Vendre » en dégradé fuchsia → indigo ; montants : bénéfice vert, dette
orange, sortie rouge, TVA violet.

### Modifier / supprimer

- **Vente** (`sales.edit`, ventes validées) : produits, quantités, prix, client, date, note, TVA. Stock réajusté et
  encaissements conservés par le serveur ; trop-perçu remboursé automatiquement. Une vente sans client doit rester payée
  intégralement (message du serveur affiché).
- **Suppression** de vente (`sales.delete`) ou d'achat (`suppliers.manage`) : motif obligatoire, confirmation, trace d'audit.
- **Mouvement** (`transactions.edit`) : montant, type du même sens, catégorie, mode, description, TVA ; un changement
  entrée ↔ sortie est refusé par le serveur (message affiché).
- Actions masquées sans la permission correspondante. Modifications et suppressions nécessitent une connexion (message clair
  hors ligne) ; les créations restent possibles hors ligne.

### TVA facultative

- Réglages lus dans `/references` (`vat.enabled`, taux par défaut, taux proposés 7 / 13 / 19 %, prix TTC ou HT).
- Vente, achat : « Appliquer la TVA », puces de taux ou saisie libre, « Prix saisis TTC / HT », aperçu HT / TVA / TTC en
  direct (même règle que `App\Support\Vat`, ligne par ligne). Sans taux, rien n'est calculé. `vat_rate` est toujours envoyé
  explicitement (null = pas de TVA) pour ne pas déclencher le taux par défaut du serveur.
- Dépense / recette : « TVA incluse » + taux → HT et TVA affichés.
- Les montants affichés après enregistrement (fiches, reçus, partages WhatsApp) sont ceux du serveur.
- Tableau de bord : chiffre d'affaires HT, tuile « TVA à reverser » (ou crédit de TVA) et bloc TVA collectée / déductible.

Vente en **2 appuis** : un appui sur le produit, un appui sur « Valider » (espèces, payé intégralement).
« Voir le panier » donne accès aux quantités, au client, au paiement partiel et à la monnaie à rendre.

Permissions : les onglets et actions suivent les permissions renvoyées à la connexion
(`sales.create`, `clients.view`, `transactions.view`, `reports.view`, `cash.close`…). Sans `finance.costs`,
coûts, marges et bénéfices ne sont jamais affichés (et le résumé partagé est recomposé sans eux).

Montants en TND à 3 décimales, format `1 234,500 TND` (identique à l'helper `money()` du serveur).

## Mode hors ligne

- Chaque client, vente ou mouvement reçoit un **UUID généré sur l'appareil**.
- Si le serveur est injoignable, l'opération est stockée dans une file locale SQLite (drift) et l'écran l'indique.
- Pastille **En ligne / Hors ligne · n** en haut de chaque écran (appui = synchroniser / voir la file).
- À la reconnexion (changement de réseau, retour au premier plan, toutes les 30 s) :
  1. `POST /sync/push` des clients, puis des ventes et mouvements (une vente sur un client créé hors ligne
     référence `client_uuid`) — idempotent, un renvoi ne crée pas de doublon ;
  2. `GET /references` puis `GET /sync/pull?since=<server_time précédent>` pour rafraîchir le cache.
- Opérations refusées par le serveur : visibles dans **Plus › Synchronisation** (réessayer / abandonner).
- Le catalogue, les clients, les zones, la dernière carte et les derniers chiffres du tableau de bord restent disponibles hors ligne.
- Une position GPS saisie hors ligne part dans `locations[]` de `/sync/push` (avec `client_uuid` si le client a lui aussi
  été créé hors ligne) ; le serveur rattache alors le client à la zone qui contient le point.

## Localisation (GPS)

- Android : `ACCESS_FINE_LOCATION` / `ACCESS_COARSE_LOCATION` ; iOS : `NSLocationWhenInUseUsageDescription`.
- GPS coupé → « Activez la localisation » + bouton vers les réglages ; accès refusé définitivement → bouton
  « Réglages de l'application ». Au-delà de ± 50 m, l'application signale une position approximative.
- Bouton masqué sans la permission `clients.locate` ; carte et zones masquées sans `zones.view`.
- Fond de carte : serveurs publics OpenStreetMap (attribution affichée). Pour un usage intensif, prévoir un
  fournisseur de tuiles dédié (politique d'usage OSM) en changeant `urlTemplate` dans `lib/widgets/map_widgets.dart`.

## Installation

Prérequis : [Flutter](https://docs.flutter.dev/get-started/install) 3.47 ou plus (Dart 3.13),
Android Studio / SDK Android pour Android, Xcode (macOS) pour iOS.

```bash
cd mobile
flutter pub get
dart run build_runner build     # régénère lib/data/local_db.g.dart après modification des tables
```

## Configuration du serveur

L'adresse du serveur se saisit sur l'écran de connexion (lien « Serveur : … »), sans `/api` :

| Contexte | Adresse |
|---|---|
| Émulateur Android, serveur sur le PC | `http://10.0.2.2:8000` (valeur par défaut) |
| Téléphone sur le même Wi-Fi | `http://<IP du PC>:8000` — lancer `php artisan serve --host=0.0.0.0 --port=8000` |
| Production | `https://votre-domaine.tn` (HTTPS recommandé) |

Le HTTP en clair est autorisé (Android `usesCleartextTraffic`, iOS ATS) pour les serveurs locaux.

Comptes de démonstration : `caissier` / `Demo@2026`, `admin` / `Admin@2026`.

## Lancer et construire

```bash
flutter run                          # appareil / émulateur connecté
flutter build apk --release          # Android (APK)
flutter build appbundle --release    # Android (Play Store)
flutter build ipa                    # iOS (sur macOS)
flutter build web --release          # version navigateur (aperçu / dépannage)
```

Avant une publication : renseigner la signature Android (`android/key.properties`) et l'identifiant
d'application (`tn.telecomcash.telecomcash`) si nécessaire, ainsi que l'icône.

Web : `web/sqlite3.wasm` et `web/drift_worker.js` (versions de `sqlite3` 3.5.2 et `drift` 2.35.3) servent à la base
locale du navigateur ; à remplacer si ces paquets changent de version.

## Tests

```bash
flutter analyze
flutter test                                          # montants, panier, zones, GPS, TVA, actions selon les droits
php artisan serve --port=8000                         # dans le dossier Laravel
flutter test test/server_sync_test.dart --dart-define=SERVER=http://127.0.0.1:8000
```

Le test d'intégration se connecte avec `caissier` / `Demo@2026` (surchargeable par `--dart-define=LOGIN=…`
et `PASSWORD=…`), crée une vente en ligne, puis un client et une vente hors ligne, synchronise et vérifie
sur le serveur (client résolu par `client_uuid`, idempotence). Il vérifie aussi le filtre par zone, la carte
et l'enregistrement d'une position (GPS simulé, en ligne puis hors ligne).
`test/server_edit_test.dart` (même commande) se connecte en `gestionnaire` et `caissier` : vente HT à 19 % (totaux du serveur =
aperçu), modification, suppression, achat créé / modifié / supprimé, dépense TVA incluse modifiée puis supprimée, droits
refusés au caissier. **Ces tests écrivent de vraies données** dans la base (le second supprime ce qu'il crée) :
à lancer sur une base de test ou de démonstration.

## Structure

```
lib/
  core/      api_client.dart (HTTP + erreurs), money.dart (format TND), session.dart (connexion, PIN, thème)
  data/      models.dart, local_db.dart (drift : cache, file d'attente, produits les plus vendus),
             sync_service.dart (en ligne / hors ligne, push / pull)
  features/  auth, dashboard, sell (panier, encaissement, reçu), clients, movements, more (rapports, caisse…)
  widgets/   composants communs (pastille de synchronisation, champ montant, gros boutons)
test/        money_test.dart, cart_test.dart, server_sync_test.dart
```
