446 lines
28 KiB
Markdown
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**.
|