# 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` (`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**.