Files
Omika/ANALYSE-OMIKA-APP.md
T
2026-10-23 18:46:50 +02:00

446 lines
28 KiB
Markdown

# Omika — Analyse technique de l'application (mobile offline-first + desktop)
> Source : `D:\Avalonia\Omika` — projets `src/Omika.App`, `src/Omika.App.Local`,
> `src/Omika.App.Desktop`, `src/Omika.App.Android`, `src/Omika.App.iOS`.
> Analyse factuelle destinée à la rédaction d'un site web marketing pour vendre
> le logiciel de gestion d'interventions (field-service management).
---
## Vue d'ensemble de la pile technique
| Élément | Détail | Preuve |
|---|---|---|
| Langage / runtime | C# sur **.NET 10** (`net10.0`) | `src/Omika.App/Omika.App.csproj` |
| UI | **Avalonia UI** (thème Fluent, police Inter), rendu identique sur desktop + mobile | `Omika.App.csproj`, `App.axaml` |
| MVVM | **CommunityToolkit.Mvvm** (`[ObservableProperty]`, `[RelayCommand]`) | `ViewModelBase.cs` |
| Base locale | **SQLite** (`Microsoft.Data.Sqlite`) — fichier `omika.db` | `LocalDatabase.cs` |
| Cartes | **Mapsui.Avalonia12** (vue lieu/intervention) | `Omika.App.csproj` |
| QR | **QRCoder** (génération) + **ZXing.Net** (scan Android) | `Omika.App.csproj`, `Omika.App.Android.csproj` |
| Images | **SkiaSharp** (optimisation poids photos) + **MetadataExtractor** (date EXIF) | `Omika.App.csproj` |
| Hôtes | Desktop (`Avalonia.Desktop`, `WinExe`), Android (`net10.0-android`, min SDK 23, `com.applilogik.omika.app`), iOS (`net10.0-ios`, min 14.0) | les 3 `.csproj` hôtes |
| Injection | **Pas de conteneur DI** — composition racine manuelle unique | `AppServices.cs` |
Point notable : l'application est un **monolithe UI partagé**. `Omika.App` contient
toute la logique d'interface (Vues + ViewModels + Navigation + Services UI) ; les
trois hôtes ne contiennent que le strict minimum natif (lancement + implémentations
plateforme : caméra, QR, téléphone, email, carte, secure storage, connectivité).
---
## 1. Les écrans de l'application
Toutes les vues sont dans `src/Omika.App/Views/*.axaml`, les ViewModels dans
`src/Omika.App/ViewModels/*.cs`, et le routage page → ViewModel dans
`src/Omika.App/Navigation/PageViewModelFactory.cs` (enum `AppPage`).
### 1.1 Activation / licence
- **`LicenseActivationView.axaml` + `LicenseActivationViewModel.cs`** — écran
d'inscription au premier démarrage. Deux entrées vers la même procédure : saisie
manuelle d'un code d'inscription ou **scan d'un QR** (avec aperçu caméra en direct,
annulable). Affiche aussi les états « Connexion requise pour vérifier votre
licence » et « Licence expirée ou révoquée ». Aucune URL codée en dur.
### 1.2 Accueil / Shell
- **`ShellView.axaml` + `ShellViewModel.cs`** — coquille applicative : rail latéral
(desktop/tablette), **barre de navigation basse mobile** (5 accès directs max,
le reste dans un menu « Plus »), topbar avec titre, carte profil, **bouton
flottant de synchronisation avec badge du nombre d'opérations en attente**.
Mode « immersif » plein écran pour l'éditeur d'image.
### 1.3 Agenda / Planning / Rendez-vous
- **`AgendaView.axaml` + `AgendaViewModel.cs`** — agenda personnel : vues
**Jour / Semaine / Mois**, bandeau 7 jours avec compteurs, sélecteur de date.
Lecture 100 % SQLite locale (aucun appel API au chargement).
- **`AgendaEquipeView.axaml` + `AgendaEquipeViewModel.cs`** — agenda d'équipe :
filtres par équipe/collaborateur, modes liste/calendrier.
- **`RendezVousDetailView.axaml` + `RendezVousDetailViewModel.cs`** — fiche
rendez-vous : contacts, **saisie de temps inline** (brouillons), **annulation**
(origine + motif + note), **finalisation** (« Fait » après validation des temps),
accès au **compte rendu**.
### 1.4 Interventions
- **`InterventionsView.axaml` + `InterventionsViewModel.cs`** — recherche
d'interventions (filtres référence/client/site/collaborateur/état/type/relation
aux RDV) avec **onglets « Récentes » (dernière recherche persistée) et
« Épinglées » (favoris)**. Recherche à la demande (API), affichage offline du cache.
- **`InterventionDetailView.axaml` + `InterventionDetailViewModel.cs`** — fiche
intervention : hero (référence, client, site, dossier), **contacts, documents/
images, notes internes, courriels, brouillons de rapport, notes administratives**,
actions **Modifier / Archiver** (offline-first via Outbox).
- **`InterventionEditView.axaml` + `InterventionEditViewModel.cs`** — formulaire
partagé de création/édition d'intervention (offline-first).
- Sous-écrans d'une intervention :
- `InterventionContactsView` + `ContactDetailView` — contacts liés + fiche contact.
- `InterventionDocumentsView` + `PhotoViewerView` + `ImageEditorView` — documents/
photos, carrousel, **annotation d'image plein écran** (édition + signature).
- `InterventionNotesView` + `InterventionNotesAdministrativesView` — notes internes
et administratives (droit `A.NOTE.ADMIN`).
- `InterventionEmailsView` + `NouveauEmailView` — courriels d'intervention +
composition d'email (depuis modèle, rapport ou contact).
- `RapportChampsView` — saisie des champs/rubriques d'un modèle de rapport.
- `InterventionResourcesView` — vue regroupant notes / images / documents.
### 1.5 Dossiers / Clients / Sites
- **`DossiersView` + `DossierDetailView`** — liste (recherche + **épinglage local**)
et fiche dossier.
- **`ClientsView` + `ClientDetailView`** — liste (recherche + épinglage) et fiche client.
- **`SitesView` + `SiteDetailView`** — liste (recherche + épinglage) et fiche site,
avec **carte géographique** (contrôle `Controls/LieuMap.axaml`, Mapsui) et
lancement d'itinéraire.
### 1.6 Équipe / Collaborateurs
- **`EquipesView.axaml` + `EquipesViewModel.cs`** — menu « Équipe » : liste des
équipes de l'utilisateur.
- **`EquipeDetailView` + `EquipeDetailViewModel.cs`** — membres d'une équipe.
- **`CollaborateurDetailView` + `CollaborateurDetailViewModel.cs`** — fiche
collaborateur (dont **attribution/révocation de licence mobile**).
- `CollaborateursView.axaml` / `CollaborateursViewModel.cs` — liste de collaborateurs
existante mais **non câblée** dans la factory de pages (voir §8).
### 1.7 Suivi des temps / Compte rendu
- **`SuiviTempsCreateView.axaml` + `SuiviTempsCreateViewModel.cs`** — création d'une
saisie de temps (offline-first : brouillons SQLite + Outbox). Prise en compte de la
**pause déjeuner** (préférence locale par utilisateur).
- **`CompteRenduCreateView.axaml` + `CompteRenduCreateViewModel.cs`** — saisie du
compte rendu d'un rendez-vous (individuel ou groupe/journée, anti-doublon).
### 1.8 Synchronisation / Paramètres
- **`SynchronisationView.axaml` + `SynchronisationViewModel.cs`** — file de
synchronisation : liste des opérations Outbox en attente (temps, annulations,
photos/documents, interventions…) + lancement manuel.
- **`ParametresView.axaml` + `ParametresViewModel.cs`** — réglages : **thème
clair/sombre/système**, **couleur principale** (presets + curseurs RVB), section
**Compte/Licence** (n° masqué, validité, expiration, société), **limite de poids
des photos** (politique client, lecture seule), **horaires de pause déjeuner**,
**maintenance des données** (purge cache documents, suppression données,
réinitialisation complète), **email au support** (export des logs en pièce jointe),
déconnexion, version.
### 1.9 Composants réutilisables (`Controls/`)
Cartes (`ClientCard`, `SiteCard`, `ContactCard`, `DossierCard`, `InterventionCard`,
`RendezVousCard`, `EquipeCard`), boutons d'action (`CallButton`, `EmailButton`,
`OpenButton`), champs (`DateField`, `TimeField`, `SignaturePad` pour signature,
`ImageEditorControl`), `LieuMap`, aperçu HTML d'email
(`EmailHtmlPreviewControl`), templates d'édition de champs
(`Views/ChampEditionTemplates.axaml`).
---
## 2. Navigation et MVVM
### ViewLocator (`src/Omika.App/ViewLocator.cs`)
`IDataTemplate` standard d'Avalonia : résout la vue à partir du type du ViewModel en
remplaçant le suffixe `ViewModel` par `View` (réflexion). Cas particulier pour
`PlaceholderViewModel` → `PlaceholderPage`. Retour `Match` sur `ViewModelBase`.
### NavigationService (`Navigation/NavigationService.cs`, `INavigationService.cs`)
- Liste d'items `NavigationItem` : Agenda (Home), Interventions, Dossiers, Équipe,
Clients, Sites, Paramètres. Les 5 premiers sont « primaires » sur mobile, les
autres passent par le menu « Plus ».
- **Pile de retour** (`_backStack` de `NavigationEntry`) pour `GoBack`.
- Événements `PageChanged` / `ParameterChanged` (le Shell écoute pour recharger le VM).
- **Blocage en amont par droits** via `PermissionGuard.IsPageAllowed` — impossible de
naviguer vers une page non autorisée (pas seulement masquée).
- Le changement de page est **purement visuel** : aucun appel API.
### ShellViewModel (`ViewModels/ShellViewModel.cs`)
Orchestre : page courante + ViewModel courant, titre, état de synchronisation
(`SyncUiState`), identité utilisateur (nom/initiales), mode immersif, **badge de
synchronisation en attente** (`PendingSyncCount` via Outbox), commande
`SyncNowCommand` (avec verrou anti-double-clic), filtrage du menu par droits,
`RefreshCurrentPage()` après sync.
### RootViewModel (`ViewModels/RootViewModel.cs`)
Décide au démarrage entre **écran d'activation** et **Shell**. Règles :
- rejet terminal serveur (licence inactive/expirée/révoquée/device mismatch) → activation avec QR ;
- session valide (token valide, renouvelé, ou période offline autorisée) → Shell ;
- session présente mais hors grace ET serveur injoignable → activation sans QR (« Connexion requise ») ;
- aucune session → inscription QR.
### PageViewModelFactory (`Navigation/PageViewModelFactory.cs`)
Fabrique les ViewModels métier par page, avec **routage des paramètres** (Guid,
`AgendaItem`, `DetailNavigation`, `InterventionEditNavigation`, `EmailEditionNavigation`,
`NouveauEmailRapportNavigation`, `NouveauEmailContactNavigation`, `ImageEditorNavigation`…).
Réutilise les instances Agenda / Agenda-équipe pour conserver date/filtres/mode au
retour. Câble `SetShell` pour résoudre la dépendance circulaire Shell ↔ Factory.
### CommunityToolkit.Mvvm (`ViewModels/ViewModelBase.cs`)
`ObservableObject` de base, `[ObservableProperty]` (code généré), `[RelayCommand]`,
helpers `LogError`/`TrackFeature` (télémétrie d'usage/erreurs best-effort).
### Droits (`Services/PermissionService.cs`, `Navigation/PermissionGuard.cs`)
Droits centralisés par clés canoniques (`MobilePermissionKeys` : `VoirClients`,
`VoirSites`, `VoirDossiers`, `PeutEnvoyerEmail`, `PeutCreerRapport`,
`PeutVoirAgendaEquipe`, `LicenceMobile`, `A.TEMP.COLB`…). Le menu et les pages sont
filtrés/recalculés dès que le bootstrap (droits serveur) est rafraîchi.
---
## 3. La couche offline-first
### 3.1 Base SQLite (`Sqlite/LocalDatabase.cs`, `Sqlite/SqliteSchema.cs`)
- Fichier **`omika.db`** dans `%LocalAppData%\Omika` (desktop) / dossier app (mobile).
- Schéma versionné (`CurrentVersion = 1`), créé **idempotent** (`CREATE TABLE IF NOT
EXISTS`) + **migrations de colonnes** via `PRAGMA table_info` (ajout de colonnes
sans détruire les lignes).
- Séparation stricte :
- **Tables métier** (remplacées par snapshot) : `Client`, `Personne`, `Site`,
`Intervention`, `Dossier`, `RendezVous`, `Collaborateur`, `DocumentMetadata`,
`SuiviTemps` — chacune = `Id` (PK GUID) + `PayloadJson` (DTO complet) + colonnes
de référence typées (`ClientId`, `SiteId`, `DossierId`, `InterventionId`…).
- **Tables techniques** (jamais purgées) : `Outbox`, `IdentityMapping`, `SyncState`,
`DocumentCache`, `LocalBootstrap`, `MobileEnrollment`, `UiPreferences`,
`PreferencesUtilisateur`, `SuiviTempsDraft`, `RendezVousTempsValidation`,
`RendezVousDraftReference`, `InterventionCache`, `DossierLocalState`,
`DossierInterventionCache`, `ContextInterventionCache(State)`, `RapportBrouillon`,
`NoteInterne`, + caches de dernière recherche (`DossierLastSearch`,
`ClientLastSearch`, `SiteLastSearch`).
- `LocalRepository<TEntity>` (`Repositories/LocalRepository.cs`) : repository
générique `Upsert/GetById/GetAll/Delete/Clear/Count` sur ce schéma JSON + colonnes
de référence (mapper `ReferenceColumnMapper`).
### 3.2 L'Outbox (`Services/OutboxService.cs`, `Models/OutboxOperation.cs`, `Models/OutboxState.cs`, `Repositories/OutboxRepository.cs`)
- Chaque mutation métier locale produit une **opération Outbox** (idempotente).
- `OutboxOperation` porte : `OperationId` (GUID de transaction **immuable**, créé une
fois et conservé à travers les retries), `EntityType`, `OperationType`
(CREATE/UPDATE/DELETE/ARCHIVE), `RequestedEntityGuid` (GUID local temporaire),
`CanonicalEntityGuid` (GUID serveur après réconciliation A→B), `Payload` (JSON),
`DependencyEntityType`/`DependencyRequestedEntityGuid` (ordonnancement), `State`,
`RetryCount`, `LastError`.
- `OutboxState` : `Pending → Sending → Accepted → WaitingResult → Succeeded | Rejected
| StatusNotAvailable` (7 états, récupérables après crash).
### 3.3 Le SyncEngine (`Services/SyncEngine.cs`) — cycle complet
Protégé par `SemaphoreSlim` (une seule sync à la fois). Cycle :
1. **Vérifier la connectivité** (`INetworkConnectivity`) — hors ligne → retour
« Hors ligne. » **sans aucune tentative HTTP**.
2. **Envoyer les opérations prêtes** (`GetReadyAsync`) en différant celles dont la
dépendance (ex. note → intervention créée) est encore active ; marquage `Sending`
avant envoi ; `DeviceId` réel transmis (cohérence serveur token/opération).
Erreur réseau → retour `Pending` + `RetryCount++`.
**Documents** : upload **synchrone** multipart (jamais de Base64) ; résultat
connu immédiatement, sans polling.
3. **Interroger les statuts** (`GET /api/sync/status/{OperationId}`) : `Success`
(réconciliation A→B), `Rejected` (définitif, conservé pour diagnostic),
`StatusNotAvailable` → **ré-émission bornée à 5** puis anomalie visible (jamais
convertie artificiellement en succès).
4. **Appliquer le snapshot atomiquement** (voir ci-dessous).
Résultat structuré `SyncCycleResult` (Executed, SnapshotApplied, OperationsSent/
Succeeded/Rejected/Anomaly, FailureStage, ExceptionType, Message).
### 3.4 SnapshotApplier (`Services/SnapshotApplier.cs`) — transaction atomique
- Le snapshot est **entièrement téléchargé et validé AVANT** toute modification.
- **Transaction SQLite unique** : remplace `Personne`, `Intervention`, `RendezVous`
(DELETE + INSERT), **préserve les rendez-vous ayant une mutation locale en
attente** (annulation non confirmée), puis purge les tables « à la demande »
(Client, Site, Dossier, Collaborateur, DocumentMetadata, SuiviTemps).
- En cas d'erreur : **rollback** — l'ancienne base métier reste intacte.
- Les tables techniques (Outbox, IdentityMapping, DocumentCache, LocalBootstrap,
favoris, brouillons…) **ne sont jamais touchées**.
### 3.5 Réconciliation d'identités (`Services/MobileIdentityReconciliationService.cs`)
GUID mobile temporaire (A) → GUID canonique serveur (B). Idempotente et
**transactionnelle** : enregistre le mapping (`IdentityMapping`), réécrit la PK
logique, réconcilie le cache document, les **références enfants** (foreign keys
locales), l'Outbox en attente, et **réécrit les payloads Outbox** via des
`IOutboxPayloadRewriter` typés (`PayloadRewriters/ClientPayloadRewriter.cs`,
`SitePayloadRewriter.cs`, `DossierPayloadRewriter.cs`, `InterventionPayloadRewriter.cs`,
`RendezVousPayloadRewriter.cs`, `NoteInternePayloadRewriter.cs`,
`NoteAdministrativePayloadRewriter`).
### 3.6 Cache document (`Services/DocumentCacheService.cs`, `Repositories/DocumentCacheRepository.cs`)
- Les **binaires sont dans des fichiers locaux** (`%LocalAppData%\Omika\documents`) ;
SQLite ne contient que les **métadonnées** (`DocumentCache` : LocalPath, NomFichier,
MimeType, Size, State).
- Téléchargement **à la demande**, survit au snapshot, purge possible depuis les
Paramètres.
### 3.7 Détection de connectivité
- Repli portable (`Services/NetworkConnectivity.cs`) : interface `NetworkInterface`
active non loopback.
- **Android** (`AndroidNetworkConnectivity.cs`, branché dans `Application.cs`) :
`ConnectivityManager` — évite les faux positifs « en ligne » (mode avion, hotspot
sans Internet).
---
## 4. Session et activation
### 4.1 MobileSessionService (`Services/MobileSessionService.cs`)
- **Token stocké en secure storage dédié**, jamais en SQLite en clair
(`ISecureTokenStore`) ; le bootstrap (profil/droits/licence) est, lui, en SQLite
(`LocalBootstrapState`, singleton).
- **Période offline autorisée** : une session déjà validée reste accessible hors
connexion pendant `OfflineGraceDays` jours, calculés **strictement** depuis la
dernière confirmation serveur (`LastLicenseValidationUtc`), jamais depuis une
écriture locale.
- **Renouvellement single-flight** (`SemaphoreSlim`) : un seul `license/validate`
à la fois ; mémorisation d'un rejet terminal pour ne pas marteler le serveur.
- `ClearSessionAsync` supprime **seulement** token + bootstrap — **jamais les données
métier** (SQLite, cache, Documents, Outbox).
- `RefreshBootstrapAsync` recharge référentiels/équipes/collaborateurs/droits sans
repousser la grace.
### 4.2 Flux d'activation côté mobile (`Services/MobileEnrollmentService.cs`, `Services/LicenseEnrollmentClient.cs`)
1. Lecture du **QR d'inscription** (`EnrollmentQrCodec.Decode`, **vérification de
signature par `kid`** via `Security/EnrollmentVerificationKey.cs`).
2. **Validation stricte HTTPS non-local** de l'URL du service de licence
(`EndpointValidator`).
3. Appel `POST /api/license/activate` avec `CodeClient`, `NumeroLicence`, `DeviceId`
+ snapshot d'appareil.
4. Validation HTTPS de l'URL d'API Omika retournée par le serveur.
5. Persistance de la configuration (`MobileEnrollment` / `MobileEnrollmentStore`).
6. `ApplyEnrollmentSessionAsync` : token en secure storage + bootstrap en SQLite.
Aucune URL codée en dur — tout provient du QR puis du serveur de licence.
### 4.3 Licence de développement (Debug uniquement)
- `Services/DevelopmentLicenseProvider.cs` + `DevelopmentLicenseConfigurationLoader.cs` :
licence locale valide chargée depuis un fichier **non sensible**
`development-license.json`, **compilée sous `#if DEBUG` uniquement** — en Release,
le provider retourne toujours `null` (le serveur reste l'autorité).
- Permet de tester navigation/SQLite/snapshot/offline sans base de licences HFSQL.
- `EnsureDevelopmentSessionAsync` obtient un **vrai token signé** via
`POST /api/dev/session` pour le flux Desktop de développement.
### 4.4 Secure storage par plateforme
- **Android** : `AndroidSecureTokenStore` — **Keystore AES-GCM**, clé non exportée,
supprimée à la désinstallation (→ réinstallation = écran QR). `LegacyTokenMigrator`
migre l'ancien `session.token` en clair vers le blob chiffré.
- **iOS** : `IosSecureTokenStore` — **Keychain** ; `IosTokenBootstrap` gère le marker
d'installation (purge Keychain à la réinstallation) + migration.
- **Desktop/fallback** : `PlatformSecureTokenStore` (fichier, développement).
---
## 5. Le client HTTP (`Services/MobileApiClient.cs`, ~1135 lignes)
- Client HTTP typé vers l'API mobile Omika, réutilisant les **contrats backend
partagés** (`Omika.Core.Contracts.Mobile.*`).
- `BaseUrl` : **`null` tant que l'appareil n'est pas inscrit**, puis fixée depuis la
configuration d'inscription (jamais de localhost/fallback codé en dur).
`RequireBaseUrl()` lève si aucune URL.
- **Bearer token** : `AccessToken` posé via `Authorization: Bearer` dans
`CreateAuthenticatedRequest` (jamais loggé).
- ~50 endpoints : licence (`activate`/`validate`/`dev/session`), bootstrap, sync
(`operations`, `status/{id}`, `snapshot`), recherche intervention/dossier/client/
site/collaborateur, fiches par GUID, documents (download/upload), notes, emails,
modèles (mail/rapport), génération (`email`/`rapport`), compte rendu, suivi temps.
- **Deux `DelegatingHandler`** branchés dans `AppServices` :
- `TraceIdPropagationHandler` — propagation TraceId/SessionId (corrélation).
- `SessionRefreshHandler` — **renouvellement 401 single-flight** (réémission
transparente), anti-récursion via option `SkipAuthRefresh` sur les appels licence.
---
## 6. Plateformes
### Desktop (`src/Omika.App.Desktop/Program.cs`)
- `WinExe`, `[STAThread]`, `IClassicDesktopStyleApplicationLifetime`.
- Capture globale des exceptions non gérées → `crash.log` + télémétrie
(`ExceptionTelemetry`). Rendu Windows/macOS/Linux via `Avalonia.Desktop`.
### Android (`src/Omika.App.Android/`)
- `MainActivity` (`AvaloniaMainActivity`) : demande de **permission caméra** runtime,
**sélecteur de fichiers SAF** (`ACTION_OPEN_DOCUMENT`, multi-sélection), **capture
photo** (`ACTION_IMAGE_CAPTURE` + FileProvider), gestion des résultats, handlers
d'exceptions natives (`AndroidEnvironment.UnhandledExceptionRaiser`).
- `Application.cs` (`CustomizeAppBuilder`) : installe **tous** les services natifs
AVANT l'init Avalonia — `AndroidEnrollmentQrScanner` (Camera2 + ZXing), launchers
`tel:`/`mailto:`/`geo:`, `AndroidFilePicker`, `AndroidPhotoCapture`,
`AndroidFileLauncher`, `AndroidSecureTokenStore` (Keystore), `AndroidDeviceInfoProvider`,
`AndroidNetworkConnectivity` (ConnectivityManager), migration du token.
### iOS (`src/Omika.App.iOS/`)
- `AppDelegate.cs` (`CustomizeAppBuilder`) : `IosEnrollmentQrScanner`, launchers
`tel:`/`mailto:`/Apple Maps, `IosFilePicker` (UIDocumentPicker + PHPicker photos),
`IosPhotoCapture` (UIImagePickerController), `IosFileLauncher`
(UIDocumentInteractionController), `IosSecureTokenStore` (Keychain) +
`IosTokenBootstrap` (marker d'installation), `IosDeviceInfoProvider`.
- `Main.cs` : entrée `UIApplication.Main`.
### Points spécifiques transverse
- **QR code** : génération (`QrCodeToImageConverter.cs`) + scan natif (aperçu caméra
en direct via `IQrScannerPreviewSink` / `LicenseActivationViewModel.OnFrame`).
- **Caméra/photo** : capture native + **optimisation du poids** (`SkiaImageOptimizer`)
+ **date EXIF** (`MetadataDocumentDateResolver`) avant enregistrement.
- **Téléphone/email/carte** : abstractions `IPhoneLauncher`/`IEmailLauncher`/
`IMapLauncher` avec implémentations natives par plateforme.
- **Secure storage** : Keystore / Keychain / fichier (voir §4.4).
---
## 7. Extraction marketing (fonctionnalités concrètes, avec preuve technique)
| Argument marketing | Réalité technique (fichier) |
|---|---|
| **100 % hors-ligne** | Agenda et fiches lisent SQLite locale sans appel API (`AgendaViewModel.cs` « Ne déclenche aucun appel API », `InterventionDetailViewModel.cs`). Les mutations sont écrites localement puis envoyées plus tard (Outbox). |
| **Synchronisation automatique** | Sync au lancement (`App.axaml.cs` `StartInitialSyncAsync`) + après inscription + manuelle (`ShellViewModel.SyncNowAsync`). |
| **Aucune perte de données** | Snapshot atomique avec rollback (`SnapshotApplier.cs`), préservation des RDV en attente, réconciliation d'identités idempotente (`MobileIdentityReconciliationService.cs`). |
| **File d'attente hors-ligne fiable** | Outbox avec états récupérables après crash, ré-émission bornée, jamais de faux succès (`SyncEngine.cs`, `OutboxState.cs`). |
| **Multi-plateforme, une seule base de code** | ~165 fichiers UI partagés (`Omika.App`), 3 hôtes fins (Desktop/Android/iOS). |
| **Interface native** | Avalonia Fluent + Inter, adaptative (rail desktop vs barre basse mobile, `ShellView.axaml`), thème clair/sombre/système + couleur personnalisable (`ThemeService`, `ParametresViewModel`). |
| **Activation sécurisée par QR** | QR signé (`kid`), validation HTTPS stricte, token en Keystore/Keychain (`MobileEnrollmentService`, `AndroidSecureTokenStore`, `IosSecureTokenStore`). |
| **Photos/documents optimisés** | Compression poids (SkiaSharp), date réelle EXIF, cache fichier, upload multipart sans Base64 (`DocumentLocalService`, `SkiaImageOptimizer`, `MetadataDocumentDateResolver`). |
| **Signature + annotation d'image** | `Controls/SignaturePad.axaml`, `ImageEditorView`/`ImageEditorControl` + `SignatureStrokeTracker`. |
| **Emails & rapports depuis modèles** | `NouveauEmailView`, `RapportChampsView`, génération serveur (`/generation/email`, `/generation/rapport`). |
| **Suivi des temps intelligent** | Pré-remplissage, pause déjeuner, saisie pour autrui (`A.TEMP.COLB`), brouillons offline (`RendezVousDetailViewModel`, `SuiviTempsLocalService`). |
| **Sécurité par droits** | Menu/pages filtrés par droits serveur, rechargés à chaque sync (`PermissionGuard`, `PermissionService`). |
| **Diagnostic & support intégrés** | Télémétrie locale (JSONL), export logs en email au support, capture de crash (`AppServices`, `SupportLogExporter`, `ExceptionTelemetry`). |
| **Gestion de licence & licences collaborateurs** | Activation/validation/grace offline, attribution/révocation de licence mobile (`MobileSessionService`, `MobileApiClient.AssignMobileLicenceAsync/ReleaseMobileLicenceAsync`). |
| **Cartographie des interventions** | `Controls/LieuMap.axaml` (Mapsui) + itinéraire (`IMapLauncher`). |
---
## 8. Limites / risques
1. **Journalisation de diagnostic « temporaire » abondante** : de nombreux
`Console.WriteLine` marqués « à retirer après diagnostic » subsistent dans le
code de production (`[TOKEN-DIAG]`, `[INTERVENTION-HTTP]`, `[API-CONFIG]`,
`[STARTUP]`, `REFRESH START/SUCCESS`, `SQLITE IMPORT END`) — `MobileSessionService.cs`,
`MobileApiClient.cs`, `AppServices.cs`, `SyncEngine.cs`. Aucun secret n'est loggé,
mais c'est du bruit à nettoyer avant une démo publique.
2. **Licence de développement (Debug)** : bypass local `#if DEBUG` — correctement
compilé hors Release, mais à ne jamais laisser fuir en production
(`DevelopmentLicenseProvider.cs`).
3. **Commentaires d'énumération obsolètes** : `AppPage.cs` qualifie encore Agenda/
Interventions/Dossiers/Clients/Sites de « placeholder pour ce lot » alors qu'ils
sont **entièrement implémentés** (`PageViewModelFactory.cs` route tout vers de
vrais ViewModels). Risque de confusion lors de la relecture.
4. **Écran orphelin** : `CollaborateursView.axaml` + `CollaborateursViewModel.cs`
existent mais ne sont **pas référencés** par la navigation (le menu « Équipe »
passe par `EquipesView`/`EquipeDetail`).
5. **Composition racine manuelle** : `AppServices.cs` (~840 lignes) câble tout sans
conteneur DI — explicite mais long à maintenir.
6. **Snapshot réduit** : le snapshot ne fournit plus que Personne/Intervention/
RendezVous ; Client/Site/Dossier/Collaborateur/DocumentMetadata/SuiviTemps sont
« à la demande » et **purgés à chaque snapshot** (`SnapshotApplier.cs`). La
première consultation hors ligne de ces fiches dépend donc des **caches/épinglages**
locaux (`DossierLocalState`, `ClientLocalState`, `SiteLocalState`,
`InterventionCache`, `ContextInterventionCache`) — comportement à bien expliquer
(les favoris et la dernière recherche restent disponibles offline).
7. **Connectivité portable naïve** : le repli `NetworkConnectivity` (interface
active ≠ internet) est corrigé sur Android par `ConnectivityManager`, mais iOS et
desktop utilisent le repli.
8. **Stockage du token Desktop** : le repli fichier (`PlatformSecureTokenStore`) est
moins robuste que Keystore/Keychain (documenté « développement/desktop »).
9. **Grace offline pilotée serveur** : la durée hors-ligne autorisée dépend de
`OfflineGraceDays` renvoyé par la licence ; en mode Dev elle est fixée à la durée
d'expiration (`DevelopmentLicenseProvider.cs`).
---
## Résumé (5 lignes)
Omika est un logiciel de gestion d'interventions **offline-first** réécrit en
**.NET 10 / Avalonia UI**, avec une UI partagée unique déployée sur **desktop
(Windows/macOS/Linux), Android et iOS**. Il couvre l'ensemble du cycle terrain —
**agenda/planning, interventions, dossiers, clients, sites, équipe, suivi des temps,
comptes rendus, documents/emails** — avec une base **SQLite locale** et un
**moteur de synchronisation robuste (Outbox + snapshot atomique + réconciliation
d'identités)**. L'**activation par QR sécurisée** et le **token en Keystore/Keychain**
assurent un onboarding et une session maîtrisés, avec **période offline autorisée**.
Les points de vigilance avant commercialisation sont la **journalisation de
diagnostic temporaire** à nettoyer, quelques **commentaires/écrans obsolètes**, et le
**repli fichier du token desktop**.