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

28 KiB

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.