Files
Omika/docs/ARCHITECTURE_PROJET.md
2026-10-23 18:46:50 +02:00

57 KiB

ARCHITECTURE PROJET OMIKA — Guide complet (pour comprendre et débugger)

Ce document explique comment fonctionne chaque application du dépôt Omika, à quoi sert chaque projet, et où chercher quand un bug apparaît. Il a été rédigé en lecture seule (aucun code modifié), à partir du code source réel et de la documentation existante (docs/*.md).

Public visé : développeur débutant qui doit retrouver la cause d'un bug.


Table des matières

  1. Le vocabulaire indispensable
  2. Vue d'ensemble en une image
  3. Les deux générations (legacy WINDEV → moderne .NET)
  4. La carte des 13 projets
  5. Le graphe des dépendances
  6. Les bases de données et connexions
  7. Flux 1 : démarrage de l'application
  8. Flux 2 : activation de licence
  9. Flux 3 : synchronisation mobile → serveur
  10. Flux 4 : snapshot serveur → mobile
  11. Flux 5 : documents (upload / download)
  12. L'authentification par token
  13. La configuration (où sont les réglages)
  14. Les codes d'erreur et états
  15. Guide de diagnostic : symptôme → où chercher
  16. Les points de trace / logs
  17. Les pièges et limites connus

1. Le vocabulaire indispensable

  • Solution (.slnx) : le conteneur qui regroupe tous les projets. Ici Omika.slnx.
  • Projet (.csproj) : une "brique" de code qui se compile en un exécutable (.exe) ou une bibliothèque (.dll).
  • Bibliothèque : du code réutilisable qui ne démarre pas tout seul (ex. Omika.Core).
  • Exécutable / app : un programme qui démarre (ex. Omika.Api, Omika.Worker, Omika.App.Desktop).
  • Couche (layer) : découpage logique du code (UI, métier, données…).
  • API / Web service : un serveur qui répond à des requêtes HTTP (pour des programmes, pas des humains). Ici Omika.Api.
  • Worker / Service : programme sans fenêtre qui tourne en boucle en arrière-plan. Ici Omika.Worker.
  • Endpoint : une "porte" précise de l'API, ex. POST /api/sync/operations.
  • DI (injection de dépendances) : technique pour assembler automatiquement les briques.
  • Scoped / Singleton / HostedService : durées de vie des services en DI (par requête / unique / arrière-plan).
  • Repository : objet chargé de lire/écrire dans une base de données.
  • Service applicatif : objet qui contient la logique métier (règles), indépendant de l'UI et de la base.
  • DTO (Data Transfer Object) : objet "plat" qui transporte des données entre deux couches.
  • Entité : objet métier qui représente une donnée du domaine (Client, Intervention…).
  • HFSQL : la base de données historique (PC SOFT), accessible via ODBC ou OLE DB.
  • SQLite : base locale embarquée dans le téléphone (mode hors-ligne).
  • Sync : mécanisme qui fait transiter les données téléphone ↔ serveur.
  • Outbox : "boîte d'envoi" locale qui met en file les changements à envoyer au serveur.
  • Snapshot : "photo" complète des données métier renvoyée au mobile pour reconstruire sa base locale.
  • Idempotence : rejouer la même opération ne produit pas d'effet en double (grâce à un OperationId unique).

2. Vue d'ensemble en une image

                          ┌──────────────────────────────────────────────┐
                          │           TÉLÉPHONE / BUREAU                 │
                          │                                              │
                          │  Omika.App.Desktop / Android / iOS (l'UI)    │
                          │        │  utilise                            │
                          │        ▼                                     │
                          │  Omika.App (Vues + ViewModels + Navigation)  │
                          │        │  utilise                            │
                          │        ▼                                     │
                          │  Omika.App.Local (SQLite + SyncEngine)       │
                          └──────────────────┬───────────────────────────┘
                                             │  HTTP (JSON + Bearer token)
                                             ▼
                          ┌──────────────────────────────────────────────┐
                          │              SERVEUR (HFSQL)                 │
                          │                                              │
                          │  Omika.Api  (web service)  ──► écrit TOSRV   │
                          │      │  utilise                              │
                          │      ▼                                       │
                          │  Omika.Worker (service de sync)              │
                          │      │  TOSRV → MobReception → handler       │
                          │      │            → MobLog                   │
                          │      ▼                                       │
                          │  Omika.Core (métier) + Omika.Data (HFSQL)    │
                          └──────────────────────────────────────────────┘

À retenir absolument :

  • Le mobile ne parle jamais directement à HFSQL : il passe toujours par Omika.Api.
  • Le desktop accède à HFSQL directement (historique).
  • Omika.Api reçoit les opérations ; Omika.Worker les applique.
  • Omika.Core est le cerveau métier ; Omika.Data est la main qui touche HFSQL.

3. Les deux générations

Génération 1 (legacy) Génération 2 (ce dépôt)
Langage WINDEV C# / .NET 10
Base de données HFSQL (serveur) HFSQL (serveur) + SQLite (mobile)
Interface Fenêtres WINDEV Avalonia (Desktop / Android / iOS)
Web service Batik_WS_Com_Mob, Batik_WS_Lic_Mob Omika.Api (ASP.NET Core)
Service de sync Omika_S_Comm_DEV (WINDEV) Omika.Worker (.NET)

Les fichiers docs/BATIK.md, docs/OMIKA.md, docs/Documentation_Service.md, docs/Documentation_WebService_*.md sont des exports WINDEV de l'ancien système. Ils servent uniquement de référence historique : on ne copie plus leur code. La logique moderne fait foi.

⚠️ Règle d'or du projet : la base HFSQL existante n'est jamais modifiée (aucune table créée, supprimée ou changée). Le nouveau code ne fait que lire/écrire dans les tables déjà existantes.


4. La carte des 13 projets

Chaque projet est décrit avec son rôle et ses fichiers clés (les endroits où regarder en premier en cas de bug).

4.1 Omika.Core — le cerveau métier (indépendant de l'UI et de la base)

Chemin : src/Omika.Core/ — TargetFramework : net10.0 — Dépend de : rien.

Dossier / fichier Rôle
Domain/Entities/ Les entités métier (Client, Intervention, Licence, Document…).
Domain/ValueObjects/ Objets-valeur (petites notions métier immuables).
Domain/Commands/ Commandes (intentions d'action).
Domain/Exceptions/ Exceptions métier (ex. DomainValidationException).
Contracts/ Les DTO échangés via l'API, organisés par domaine (Clients, Interventions, Mobile, Planning, Security…).
Interfaces/ Les contrats (IClientService, IInterventionRepository, ISyncOperationService, ILicenceRepository…).
Services/ L'implémentation des services métier (ClientService, InterventionService…) + le sous-dossier Services/Mobile.
Services/Mobile/ Le cœur serveur de la sync mobile (voir détails ci-dessous).
Configuration/ Options de configuration (MobileAuthOptions, LicenseOptions, SyncOptions, DevelopmentAuthOptions…).
Constants/ Constantes (ApplicationConstants, LicenseErrorCodes, SyncEntityTypes…).
Runtime/ ApplicationRuntimeContext : décide Batik vs Omika.
ServiceCollectionExtensions.cs Enregistre les services mobile en DI (AddOmikaMobileServices).

Services/Mobile/ en détail (les fichiers les plus importants du serveur) :

Fichier Rôle
LicenseService.cs Vérifie et active/valide une licence dans HFSQL.
TokenService.cs Fabrique et valide les jetons d'authentification (HMAC-SHA256).
MobileBootstrapService.cs Construit le "bootstrap" (profil, droits, collaborateur, licence, paramètres).
MobileSnapshotService.cs Construit le snapshot complet des données métier.
MobileDocumentService.cs Upload/download des documents (via IDocumentFileStorage).
SyncOperationService.cs Reçoit les opérations de sync et les écrit dans TOSRV.
MobileIdentityProvider.cs Fournit le profil et les droits (Batik vs Omika).
MobileEntityIdentityResolver.cs Convertit l'identité publique (GUID mobile) ↔ identité physique (PK HFSQL).
MobileIdentityMapper.cs Aide à la résolution d'identité.

4.2 Omika.Data — la main qui touche HFSQL

Chemin : src/Omika.Data/ — Dépend de : Omika.Core.

Dossier / fichier Rôle
Connection/ Les connexions HFSQL (ODBC + OLE DB PCSOFT.HFSQL).
Configuration/ HfsqlOptions + validateurs (HfsqlOptionsValidator, etc.).
Repositories/Adaptive*.cs Repositories adaptatifs : lisent Batik OU Omika (noms de colonnes différents).
Repositories/Batik/ et Repositories/Omika/ Les variantes par génération de base.
Repositories/Technical/LicenceRepository.cs Accès aux tables de licence (ClientBase, Licence, LicenceHisto).
Repositories/Technical/SyncTransferRepository.cs Accès aux tables de sync (TOSRV, MobReception, MobLog).
HfSql/Models/ Modèles physiques générés des tables HFSQL (106 classes).
HfSql/Mapping/ Attributs de mapping (Table, Column, PrimaryKey…).
SchemaDetection/ Détecte le schéma réel de la base connectée.
Runtime/ Initialisation du runtime (validation de la base).
Migrations/ Migration Batik → Omika.
ServiceCollectionExtensions.cs La clé du branchement : AddOmikaData(), AddOmikaDataLicenseConnection(), AddOmikaDataSyncConnection(), AddOmikaDataServiceConnection(), AddOmikaDataBusinessOleDb().

4.3 Omika.Api — le web service moderne

Chemin : src/Omika.Api/ — SDK : Microsoft.NET.Sdk.Web — Dépend de : Omika.Core, Omika.Data.

Fichier Rôle
Program.cs Le point d'entrée : config, DI, détection de base, endpoints.
MobileAuthMiddleware.cs Valide le token Bearer et stocke l'identité dans HttpContext.Items.
Endpoints/*.cs Tous les endpoints (voir liste ci-dessous).

Endpoints métier : ClientEndpoints, ContactEndpoints, SiteEndpoints, InterventionEndpoints, DossierEndpoints, RendezVousEndpoints, CollaborateurEndpoints, DocumentEndpoints, ReferenceDataEndpoints.

Endpoints mobile :

  • LicenseEndpoints → POST /api/license/activate, POST /api/license/validate
  • MobileBootstrapEndpoints → GET /api/mobile/bootstrap
  • SyncEndpoints → POST /api/sync/operations, GET /api/sync/status/{id}, GET /api/sync/snapshot
  • MobileDocumentEndpoints → POST /api/documents, GET /api/documents/{guid}

Endpoints techniques : HealthEndpoints (/health), DevelopmentAuthEndpoints (POST /api/dev/session, actif uniquement en Development).

4.4 Omika.Worker — le service de synchronisation (arrière-plan)

Chemin : src/Omika.Worker/ — SDK : Microsoft.NET.Sdk.Worker — Dépend de : Omika.Core, Omika.Data.

Fichier Rôle
Program.cs Point d'entrée du Host .NET, enregistre les services et le worker.
SyncWorker.cs BackgroundService : la boucle infinie (cycle + délai).
SyncProcessor.cs La logique de traitement testable (transfert + application).
SyncOperationHandlerRegistry.cs Le registre qui mappe un EntityType → handler.
InterventionSyncOperationHandler.cs Applique les opérations Intervention (CREATE/UPDATE/ARCHIVE).
SuiviTempsSyncOperationHandler.cs Applique les opérations SuiviTemps.
DefaultSyncOperationHandler.cs Fallback : refuse proprement (NO_HANDLER), jamais de faux SUCCESS.

4.5 Omika.App.Local — la couche locale "hors-ligne" du mobile

Chemin : src/Omika.App.Local/ — Dépend de : Omika.Core.

Dossier / fichier Rôle
Sqlite/ La base locale omika.db (schéma, connexion).
Models/ Entités locales (LocalClient, LocalIntervention, OutboxOperation…).
Repositories/ Accès SQLite : LocalRepository<T>, OutboxRepository, IdentityMappingRepository, DocumentCacheRepository, SyncStateRepository, UiPreferencesRepository, LocalBootstrapRepository.
Services/SyncEngine.cs Le moteur de synchronisation (cycle complet).
Services/MobileApiClient.cs Client HTTP typé vers l'API (Bearer token).
Services/MobileSessionService.cs Gestion de la session (token sécurisé + bootstrap + période offline).
Services/OutboxService.cs Met en file les changements locaux à envoyer.
Services/SnapshotApplier.cs Applique le snapshot serveur à SQLite (transaction atomique).
Services/MobileIdentityReconciliationService.cs Réconcilie les identités locales (GUID mobile ↔ GUID canonique).
Services/DocumentCacheService.cs Cache local des documents.
Services/NetworkConnectivity.cs Détecte si on est en ligne.
Services/*LocalService.cs Services métier locaux (ClientLocalService, SiteLocalService, InterventionLocalService…).
Services/DevelopmentLicense*.cs Licence de développement (Debug uniquement).

4.6 Omika.App — l'interface utilisateur partagée (Avalonia)

Chemin : src/Omika.App/ — Dépend de : Omika.Core, Omika.App.Local — Packages : Avalonia 12.1.1, CommunityToolkit.Mvvm 8.4.2.

Fichier / dossier Rôle
App.axaml / App.axaml.cs Le point d'entrée de l'application Avalonia (classe App).
AppServices.cs Le "composition root" : construit tous les services à la main (pas de conteneur DI).
Views/ Les écrans .axaml + code-behind (≈ 25 vues).
ViewModels/ La logique de chaque écran (MVVM).
ViewLocator.cs Fait le lien Vue ↔ ViewModel par convention de nommage.
Navigation/ La navigation entre pages (NavigationService, AppPage…).
Services/ Services de plateforme (ThemeService, PlatformPhoneLauncher, PlatformSecureTokenStore…).
Styles/ Apparence (couleurs, typographie, contrôles).
Assets/ Ressources (icônes…).

4.7 Les hôtes plateforme (démarrage)

Projet Chemin Rôle Fichier clé
Omika.App.Desktop src/Omika.App.Desktop/ Hôte Windows (WinExe) Program.cs (Main + BuildAvaloniaApp)
Omika.App.Android src/Omika.App.Android/ Hôte Android (net10.0-android) MainActivity.cs, Application.cs
Omika.App.iOS src/Omika.App.iOS/ Hôte iOS (net10.0-ios) AppDelegate.cs, Main.cs

4.8 Omika.Tests — les tests automatiques

Chemin : tests/Omika.Tests/ — Framework : xUnit. Dépend de presque tous les projets. Vérifie le comportement du code sans base HFSQL réelle pour la plupart des tests.

4.9 tools/ — outils de diagnostic (lecture seule)

Chemin : tools/ — Petits programmes "sondes" (Probe) pour explorer la base HFSQL sans rien écrire. Dans la solution : Omika.HfSqlProbe, HfSqlModelAudit, HfSqlOleDbAudit. Hors solution : ≈ 14 autres Omika.*Probe + scripts PowerShell (probe_*.ps1, start_*_e2e.ps1) et Python (generate_models.py, extract_schema_tables.py).


5. Le graphe des dépendances

Omika.Core   ◄── Omika.Data   ◄── Omika.Api      (web service)
               ▲               ▲
               │               Omika.Worker      (service de sync)
               │
Omika.Core   ◄── Omika.App.Local  ◄── Omika.App  ◄── Omika.App.Desktop
                                                   ◄── Omika.App.Android
                                                   ◄── Omika.App.iOS

Omika.Tests  →  (utilise presque tout)
  • Omika.Core ne dépend de personne (c'est la base).
  • Omika.Data ne dépend que de Omika.Core.
  • Omika.Api et Omika.Worker partagent Core + Data.
  • L'UI (App) dépend de Core + App.Local, jamais de Data (le mobile ne touche jamais HFSQL directement).

6. Les bases de données et connexions

6.1 HFSQL (côté serveur)

Base historique, répartie en plusieurs bases physiques sur un serveur HFSQL (ex. 127.0.0.1:4900). D'après les docs des "lots serveur" :

Base HFSQL Rôle Connexion .NET
BATIK_TEST / OMIKA_TEST Données métier Business / WebService
BATIK.01_BLS Table TOSRV (file d'attente) Service (OLE DB)
base métier WebService Tables MobReception + MobLog WebService
BASE_LICENCES Licences License (OLE DB)

Les 3 tables techniques de synchronisation :

Table Base Rôle Colonnes clés
TOSRV Service File d'entrée (opérations reçues du mobile) TS_PKID, TS_Date, TS_Mobile, TS_CMD, TS_Buffer, TS_Lu, TS_JSON
MobReception WebService File de travail (en attente de traitement) MR_PKID, MR_IDToSRV, MR_JSON, MR_CMD, MR_Buffer, MR_NumSerie, MR_Fait, MR_DateHeureRecep
MobLog WebService Journal des résultats terminaux ML_PKID, ML_DateCreat, ML_Message, ML_Niveau

Les tables de licence (base Licence) : ClientBase (CL_PKID, CL_Code, CL_Nom), Licence (LI_*), LicenceHisto (LH_*).

6.2 SQLite (côté mobile)

Fichier omika.db dans le dossier de données de l'application. Contient :

  • les tables métier (copie locale : Client, Site, Intervention, Dossier, RendezVous, Collaborateur, DocumentMetadata, SuiviTemps, Personne) ;
  • les tables techniques (Outbox, IdentityMapping, SyncState, DocumentCache, LocalBootstrap, UiPreferences).

6.3 Comment les connexions sont branchées (DI)

Tout se joue dans src/Omika.Data/ServiceCollectionExtensions.cs :

Méthode Effet
AddOmikaData() Connexion Business/WebService (ODBC historique), repositories métier, détecteur de schéma, IWebServiceHfSqlConnection.
AddOmikaDataLicenseConnection() Connexion License (OLE DB), branche ILicenceRepository.
AddOmikaDataSyncConnection() Connexion Sync (OLE DB) — OBSOLÈTE, conservée pour compat.
AddOmikaDataServiceConnection() Connexion Service (OLE DB), branche IServiceHfSqlConnection (table TOSRV).
AddOmikaDataBusinessOleDb() Override : remplace IHfsqlConnection ODBC par OLE DB + ajoute IWebServiceTransactionContext.

Important pour le debug : dans MS DI, le dernier enregistrement gagne. C'est pour cela que AddOmikaDataBusinessOleDb() doit être appelé après AddOmikaData().


7. Flux 1 : démarrage de l'application

Point d'entrée : src/Omika.App.Desktop/Program.cs

Main() [STAThread]
  → BuildAvaloniaApp().StartWithClassicDesktopLifetime(args)
      → AppBuilder.Configure<App>()
          .UsePlatformDetect()
          .WithInterFont()
          .LogToTrace()

Ensuite src/Omika.App/App.axaml.cs :

Initialize()
  → AvaloniaXamlLoader.Load(this)   (charge App.axaml)
  → AttachDeveloperTools()          (Debug uniquement)

OnFrameworkInitializationCompleted()
  → _services = new AppServices()           (composition root)
  → _services.ThemeService.Application = this
  → _ = InitializeServicesAsync()            (async, sans bloquer l'UI)
  → crée MainWindow (desktop) / MainView (mobile) avec DataContext = RootViewModel

AppServices.cs construit à la main : la base SQLite, les repositories locaux, le SyncEngine, le MobileApiClient, la navigation, ShellViewModel, RootViewModel.

Puis InitializeServicesAsync() :

  1. _services.InitializeAsync() → schéma SQLite + thème ;
  2. _services.InitializeSessionAsync() → restaure le token + charge le bootstrap ;
  3. _services.RootViewModel.EvaluateSessionAsync() → décide Activation ou Shell.

Où chercher en cas de bug de démarrage :

  • Écran vide / crash au lancement → App.axaml.cs, AppServices.cs.
  • L'app reste sur l'écran d'activation → RootViewModel.EvaluateSessionAsync(), MobileSessionService.HasValidSessionAsync().
  • Erreur SQLite → LocalDatabase (schéma), AppServices.InitializeAsync().

8. Flux 2 : activation de licence

Côté mobile (déclencheur) : MobileSessionService.ActivateAsync(codeClient, numeroLicence).

1. MobileSessionService.ActivateAsync
2.   → MobileApiClient.ActivateAsync   (POST /api/license/activate, JSON)
3.     → LicenseService.ActivateAsync  (côté serveur, Omika.Core)
4.       → LicenceRepository.GetClientByCodeAsync   (HFSQL: ClientBase)
5.       → LicenceRepository.GetByNumeroAndClientAsync (HFSQL: Licence)
6.       → vérifie : active ? non expirée ? non attribuée à un autre mobile ?
7.       → si OK : AssignDeviceAsync (LI_IDMobile) + AddHistoAsync (LicenceHisto)
8.     → renvoie LicenseResponse { IsValid, AccessToken (si valide), ... }
9.   → si valide : PersistSessionAsync (token en secure storage + bootstrap en SQLite)
10.  → RootViewModel.OnActivationSucceeded() → affiche le Shell

Règles métier (dans LicenseService.ActivateAsync), dans l'ordre :

# Vérification Code d'erreur si échec
1 Données présentes (codeClient, numéro, deviceId) E1001 (DataEmpty)
2 Client existe E2001 (ClientInconnu)
3 Licence existe et appartient au client E3001 (LicenceInconnue)
4 Licence active E3002 (LicenceInactive)
5 Licence non expirée E3003 (LicenceExpiree)
6 DeviceId non attribué à un autre mobile E3004 (LicenceAttribuee)

Où chercher en cas de bug d'activation :

  • "Client inconnu" / "Licence inconnue" → LicenceRepository + la base Licence HFSQL (noms de base/table réels !).
  • "Licence inactive/expirée" → données HFSQL (LI_Active, LI_DateExpiration).
  • "Licence attribuée à un autre mobile" → LI_IDMobile déjà renseigné.
  • Le token ne revient pas → LicenseEndpoints.BuildSession + TokenService (clé MobileAuth:SigningKey).

9. Flux 3 : synchronisation mobile → serveur

C'est le flux le plus important. Il y a deux acteurs : le mobile (SyncEngine) et le serveur (Omika.Api + Omika.Worker).

9.1 Côté mobile — SyncEngine.SyncAsync()

1. Vérifie le réseau (NetworkConnectivity.IsOnlineAsync)
2. Lit l'Outbox (OutboxRepository.GetReadyAsync)
3. Pour chaque opération prête :
   - diffère si une dépendance n'est pas encore réconciliée
   - envoie : SendOperationAsync (POST /api/sync/operations)
     OU SendDocumentAsync (POST /api/documents) pour les documents
4. Interroge les statuts (GetStatusAsync pour chaque OperationId)
5. Réconcilie les identités (MobileIdentityReconciliationService.ReconcileAsync)
6. Applique le snapshot (SnapshotApplier.ApplyAsync)

Protégé par un SemaphoreSlim (un seul cycle à la fois). Déclenché par ShellViewModel.SyncNowAsync (bouton de sync).

9.2 Côté API — SyncOperationService.ReceiveAsync()

1. Valide OperationId, DeviceId, EntityType, OperationType, EntityGuid, Payload
2. Vérifie l'idempotence (opération déjà reçue ?)
3. Construit le SyncEnvelope (identités depuis le token, jamais du payload)
4. InsertToSrvAsync → écrit dans TOSRV
5. WriteLogAsync → écrit un état "Pending" dans MobLog
6. Retourne "RECEIVED"

9.3 Côté Worker — SyncWorker → SyncProcessor.ProcessAsync()

Phase 1 : TransferToReceptionAsync
   TOSRV → MobReception (copie), puis suppression TOSRV
Phase 2 : GetPendingOperationsAsync
   lit MobReception (MR_Fait = false), exclut les opérations terminales
Phase 3 : trie les opérations (CREATE → UPDATE → ARCHIVE/DELETE, puis par date)
Phase 4 : pour chaque opération :
   handler = HandlerRegistry.GetHandler(EntityType)
   résultat = handler.ApplyAsync(opération)
   → Success : MarkSuccessAsync (supprime MobReception + écrit MobLog SUCCESS)
   → TransientFailure : IncrementRetryAsync (retry, max 30)
   → PermanentFailure : MarkRejectedAsync (écrit MobLog REJECTED)

9.4 Les handlers

Handler EntityType Actions supportées
InterventionSyncOperationHandler Intervention CREATE, UPDATE, ARCHIVE. DELETE → échec permanent.
SuiviTempsSyncOperationHandler SuiviTemps (création/suivi des temps)
DefaultSyncOperationHandler tout le reste Refuse proprement (NO_HANDLER)

9.5 L'idempotence et le retry (anti-bug essentiel)

  • Chaque opération a un OperationId (GUID) unique généré par le mobile.
  • Si la même opération est rejouée (réseau coupé, retry), le serveur la reconnaît et ne la duplique pas.
  • Retry : RetryCount incrémenté à chaque échec transitoire (TRANSIENT_FAILURE). Au-delà de MaxRetryCount (défaut 30), l'opération passe en REJECTED (terminal).
  • Un échec définitif (PERMANENT_FAILURE) est rejeté immédiatement (ex. DELETE non supporté).

Où chercher en cas de bug de sync :

  • "L'opération reste en attente" → TOSRV/MobReception (le Worker n'a pas tourné ?).
  • "L'opération est rejetée" → MobLog.ML_Message (code d'erreur + message).
  • "Rien n'est envoyé" → OutboxRepository, SyncEngine (hors-ligne ?), NetworkConnectivity.
  • "Double application" → vérifier l'idempotence par OperationId.

10. Flux 4 : snapshot serveur → mobile

Quand le mobile se synchronise, il récupère une "photo" complète des données :

1. MobileApiClient.GetSnapshotAsync (GET /api/sync/snapshot, authentifié)
2.   → MobileSnapshotService.GetSnapshotAsync (côté serveur)
       - charge Clients, Collaborateurs, Sites, Interventions, Dossiers,
         RendezVous (filtrés par collaborateur), Documents, SuiviTemps
       - résout les identités publiques (GUID) via MobileEntityIdentityResolver
3.   → renvoie MobileSnapshotResponse
4. SnapshotApplier.ApplyAsync (côté mobile)
   - validation du snapshot (ex. Id unique des Personnes)
   - transaction SQLite : DELETE + INSERT de chaque table métier
   - commit atomique (en cas d'erreur : rollback, l'ancienne base reste utilisable)

Point important (bug fréquent) : le snapshot ne touche jamais les tables techniques (Outbox, IdentityMapping, SyncState, DocumentCache, LocalBootstrap).

Où chercher en cas de bug de snapshot :

  • "Snapshot timeout" → MobileSnapshotService (les docs mentionnent un ancien problème de N+1 résolu par du chargement BULK — voir LoadInterventionsAsync).
  • "Snapshot non appliqué" → SnapshotApplier + logs (REFRESH FAILED stage=...).
  • "Données locales vides" → le filtre par collaborateur (FilterRendezVousByCollaborateur).

11. Flux 5 : documents

Upload (mobile → serveur)

SyncEngine.SendDocumentAsync
  → lit le fichier local référencé dans le payload
  → MobileApiClient.UploadDocumentAsync (POST /api/documents)
  → MobileDocumentService.UploadAsync (serveur)
      → IDocumentFileStorage.SanitizeFileName / Write (stockage physique)
      → IDocumentRepository.CreateAsync (métadonnées en base)
  → renvoie DocumentUploadResponse

Download (serveur → mobile)

MobileApiClient.DownloadDocumentAsync (GET /api/documents/{guid})
  → MobileDocumentService.DownloadAsync
      → résout DocumentGuid → identité physique
      → IDocumentRepository.GetByPhysicalIdentityAsync
      → IDocumentFileStorage.Read (contenu binaire)
  → renvoie DocumentDownloadResponse

Règles de sécurité : le DocumentGuid est l'identifiant public ; le PK HFSQL et le chemin physique du serveur ne sont jamais exposés au mobile.

Où chercher en cas de bug document :

  • "Fichier local introuvable" → SyncEngine.SendDocumentAsync (état Rejected).
  • "Nom de fichier invalide" → IDocumentFileStorage.SanitizeFileName.
  • "Taille dépassée" → IDocumentFileStorage.Write (exception DOCUMENT_SIZE_EXCEEDED).
  • "Document introuvable" → MobileDocumentService.DownloadAsync (résolution d'identité).

12. L'authentification par token

  • Émission : après une licence valide, TokenService.GenerateToken(identity) crée un jeton stateless signé en HMAC-SHA256 : payloadBase64.signature.
  • Validation : MobileAuthMiddleware lit le header Authorization: Bearer <token>, appelle TokenService.ValidateToken, et stocke l'identité dans HttpContext.Items (clé Omika.MobileIdentity). Les endpoints la récupèrent via GetMobileIdentity().
  • Durée de vie : MobileAuth:TokenLifetimeHours (config).
  • Clé de signature : MobileAuth:SigningKey, obligatoire en Production (le Program.cs de l'API lève une erreur au démarrage si elle manque).

Où chercher en cas de bug d'auth :

  • "401 Unauthorized" sur un endpoint → token absent/expiré/invalide → TokenService.ValidateToken.
  • "Clé de signature manquante" au démarrage → config MobileAuth:SigningKey.

13. La configuration

13.1 src/Omika.Api/appsettings.json

  • Omika:DatabaseVersion → Auto / Batik / Omika
  • LicenseConnection → serveur/port/base de la base Licence
  • SyncConnection → serveur/port/base Sync (obsolète)
  • Logging, AllowedHosts

13.2 src/Omika.Worker/appsettings.json

  • Omika:DatabaseVersion
  • Hfsql → connexion Business (serveur, port, base, login, driver)
  • SyncConnection → base Sync (obsolète)
  • Sync → MaxRetryCount (30), RetryDelaySeconds (120), LogRetentionDays (30)
  • Logging

13.3 Config locale mobile

  • MobileApiClient.BaseUrl (défaut http://localhost:5000), surchargeable via mobile-api.local.json (fichier local, non sensible).
  • Le token est stocké en secure storage (jamais en clair dans SQLite).

Les secrets (Password, FilePasswords, SigningKey) doivent venir de configuration sécurisée (variables d'environnement / User Secrets), jamais en clair.


14. Les codes d'erreur et états

14.1 Licence (LicenseErrorCodes)

Code Signification
E0000 OK
E1001 Données vides
E1002 Format JSON invalide
E2001 Client inconnu
E2002 Erreur licence client
E2003 Configuration incomplète
E2004 Clé API incorrecte
E3001 Licence inconnue
E3002 Licence inactive
E3003 Licence expirée
E3004 Licence attribuée à un autre mobile
E3005 Aucune licence

14.2 Synchronisation

  • SyncStatus : Success, Rejected, Pending, StatusNotAvailable.
  • SyncOperationState : Unknown, Pending, Success, Rejected.
  • SyncOperationType : Create, Update, Archive, Delete.
  • Résultat de handler : Success, TransientFailure (→ retry), PermanentFailure (→ rejet).

Codes d'erreur de handler (exemples) :

  • INTERVENTION_INVALID_ENTITY_GUID — EntityGuid vide ;
  • INTERVENTION_OPERATION_NOT_SUPPORTED — type d'opération non supporté (ex. DELETE) ;
  • INTERVENTION_NOT_FOUND — entité introuvable (transitoire si un CREATE est en attente) ;
  • INTERVENTION_VALIDATION_FAILED — échec de validation métier ;
  • NO_HANDLER — aucun handler pour cet EntityType.

Codes d'erreur documents :

  • DOCUMENT_GUID_INVALID, DOCUMENT_FILENAME_INVALID, DOCUMENT_SIZE_EXCEEDED, DOCUMENT_UPLOAD_FAILED.

15. Guide de diagnostic

Chaque ligne : symptôme → où regarder en premier.

Démarrage / interface

  • L'app ne démarre pas → Omika.App.Desktop/Program.cs, App.axaml.cs.
  • Écran vide / "Not Found: ...ViewModel" → ViewLocator.cs (le lien Vue↔ViewModel est cassé).
  • Mauvaise page affichée au départ → RootViewModel (Activation vs Shell).
  • Navigation qui ne répond pas → NavigationService.cs, ShellViewModel.OnPageChanged.
  • Bouton de sync qui ne fait rien → ShellViewModel.SyncNowAsync, SyncEngine.

Session / licence

  • L'app reste sur l'écran d'activation → MobileSessionService.HasValidSessionAsync, RootViewModel.EvaluateSessionAsync.
  • "Client inconnu" → LicenceRepository.GetClientByCodeAsync + base Licence réelle.
  • "Licence inconnue" → LicenceRepository.GetByNumeroAndClientAsync + base Licence.
  • "Licence inactive/expirée" → données HFSQL (LI_Active, LI_DateExpiration).
  • "Attribuée à un autre mobile" → LI_IDMobile.
  • Token absent → TokenService.GenerateToken + MobileAuth:SigningKey.

Synchronisation

  • "Hors ligne" affiché → NetworkConnectivity.IsOnlineAsync.
  • Rien n'est envoyé → OutboxRepository.GetReadyAsync, SyncEngine.SyncAsync (étape 2).
  • Opération "différée" → dépendance non réconciliée (SyncEngine, MobileIdentityReconciliationService).
  • Opération rejetée → lire MobLog.ML_Message (code + message), puis le handler correspondant.
  • Opération qui boucle en retry → SyncProcessor.RetryAsync, MaxRetryCount.
  • Données locales fausses → SnapshotApplier, MobileSnapshotService.
  • Doublons → idempotence OperationId (SyncOperationService.ReceiveAsync, SyncProcessor).

Base de données

  • Erreur de connexion HFSQL → src/Omika.Data/Connection/, HfsqlOptions, config Hfsql/LicenseConnection/ServiceConnection.
  • "Table/colonne introuvable" → divergence entre le modèle attendu et la base réelle (HfSql/Models, SchemaDetection).
  • Erreur SQLite → LocalDatabase, SnapshotApplier, MobileIdentityReconciliationService.

16. Les points de trace / logs

  • Console mobile : Console.WriteLine("REFRESH START"), "REFRESH SUCCESS/Failed stage=...", "SQLITE IMPORT END" (dans SyncEngine, SnapshotApplier).
  • Logs serveur : ILogger (.LogInformation / .LogWarning / .LogError) dans SyncEngine, SyncWorker, SyncProcessor, MobileSessionService, etc.
  • API : logs SNAPSHOT START/END dans SyncEndpoints.
  • MobLog : le journal terminal des opérations de sync (succès / rejet) — c'est le premier endroit à consulter pour comprendre pourquoi une opération a été rejetée.

17. Les pièges et limites connus

  1. Divergence de topologie HFSQL : les docs indiquent que certaines bases/tables attendues ne sont pas toutes présentes sur le serveur réel (ex. BASE_LICENCES sans les tables Licence/ClientBase/LicenceHisto ; SYNC_TEST vs OMIKA_TEST). → Toujours vérifier la base réelle avant de conclure à un bug de code.
  2. Un seul Worker actif par file Sync (pas de verrou distribué). Deux Workers simultanés peuvent poser problème.
  3. Support Windows Service : prévu (UseWindowsService()) mais pas encore activé.
  4. Handlers limités : seuls Intervention et SuiviTemps sont branchés ; les autres EntityType passent par le fallback NO_HANDLER (rejet).
  5. DELETE non supporté : la suppression physique est refusée ; on utilise ARCHIVE.
  6. Le desktop accède à HFSQL directement, le mobile uniquement via l'API : ne pas confondre les deux chemins.
  7. DI : le dernier enregistrement gagne (AddOmikaDataBusinessOleDb doit être appelé après AddOmikaData).
  8. Le token n'est jamais stocké en SQLite en clair (secure storage). Ne pas chercher le token dans la base locale.

Annexe — arborescence rapide des fichiers les plus importants

src/
  Omika.Core/
    Services/Mobile/          ← serveur : licence, token, sync, snapshot, bootstrap, documents
    Domain/                   ← entités + objets-valeur + exceptions
    Contracts/                ← DTO
    Interfaces/               ← contrats
    Runtime/                  ← sélection Batik/Omika
  Omika.Data/
    ServiceCollectionExtensions.cs   ← le branchement DI des connexions
    Repositories/Technical/          ← LicenceRepository, SyncTransferRepository
    Repositories/Adaptive*.cs        ← repositories Batik/Omika
    Connection/                      ← connexions HFSQL (ODBC/OLE DB)
  Omika.Api/
    Program.cs               ← point d'entrée + DI + endpoints
    Endpoints/               ← tous les endpoints
    MobileAuthMiddleware.cs  ← validation du Bearer token
  Omika.Worker/
    Program.cs               ← point d'entrée + DI
    SyncWorker.cs            ← la boucle
    SyncProcessor.cs         ← la logique de traitement
    *SyncOperationHandler.cs ← les handlers métier
  Omika.App.Local/
    Services/SyncEngine.cs           ← le moteur de sync mobile
    Services/MobileApiClient.cs      ← le client HTTP
    Services/MobileSessionService.cs ← la session
    Services/SnapshotApplier.cs      ← application du snapshot
    Sqlite/                          ← base locale
  Omika.App/
    App.axaml.cs             ← point d'entrée Avalonia
    AppServices.cs           ← composition root
    Views/ + ViewModels/     ← UI + logique (MVVM)
    Navigation/              ← navigation
tests/Omika.Tests/           ← les tests
tools/                       ← sondes de diagnostic

ANNEXE B — Tracer un bug de synchronisation de bout en bout

Cette annexe est un mode d'emploi pratique. Elle reprend le flux de synchronisation et indique, pour chaque étape, le fichier exact, la méthode exacte, et ce qu'il faut vérifier quand ça ne marche pas.

B.1 Le diagramme de séquence complet (aller-retour)

TÉLÉPHONE (mobile)                    SERVEUR
────────────────────────────          ────────────────────────────────────────
ShellViewModel.SyncNowAsync
  └─ SyncEngine.SyncAsync
       │ 1. NetworkConnectivity.IsOnlineAsync
       │ 2. OutboxRepository.GetReadyAsync
       │
       │ 3. pour chaque opération :
       │    MobileApiClient.SendOperationAsync
       │─────────────────────────────► POST /api/sync/operations
       │                               SyncOperationService.ReceiveAsync
       │                                 1. valide (OperationId, DeviceId, …)
       │                                 2. idempotence (déjà reçu ?)
       │                                 3. SyncTransferRepository.InsertToSrvAsync
       │                                     → INSERT dans TOSRV (base Service)
       │                                 4. SyncTransferRepository.WriteLogAsync
       │                                     → INSERT MobLog (Pending)
       │◄─────────────────────────────  { "status": "RECEIVED" }
       │
       │ 4. MobileApiClient.GetStatusAsync
       │─────────────────────────────► GET /api/sync/status/{operationId}
       │◄─────────────────────────────  SyncStatus (Pending / Success / Rejected)
       │
       │ 5. MobileIdentityReconciliationService.ReconcileAsync
       │      (si le serveur a retourné un GUID canonique différent)
       │
       │ 6. MobileApiClient.GetSnapshotAsync
       │─────────────────────────────► GET /api/sync/snapshot
       │                               MobileSnapshotService.GetSnapshotAsync
       │◄─────────────────────────────  MobileSnapshotResponse
       │    SnapshotApplier.ApplyAsync
       │      (DELETE + INSERT de chaque table métier SQLite, transaction)
       ▼

                                      ====== EN PARALLÈLE (Worker, en boucle) ======
                                      SyncWorker.ExecuteAsync
                                        └─ SyncProcessor.ProcessAsync
                                             Phase 1 : TransferToReceptionAsync
                                                TOSRV → MobReception (copie)
                                                puis DELETE TOSRV
                                             Phase 2 : GetPendingOperationsAsync
                                                lit MobReception (MR_Fait = false)
                                             Phase 3 : trie CREATE → UPDATE → ARCHIVE
                                             Phase 4 : ProcessOperationAsync
                                                → handler.ApplyAsync
                                                → Success / Retry / Reject
                                                → MobLog (terminal)

B.2 Le traçage étape par étape

Pour chaque étape : le fichier → la méthode → ce qu'il faut vérifier.

# Étape Fichier → méthode Vérifier si bug
1 Vérif réseau App.Local/Services/NetworkConnectivity.cs → IsOnlineAsync L'app dit "Hors ligne" à tort → ici.
2 Lire l'Outbox App.Local/Repositories/OutboxRepository.cs → GetReadyAsync "Rien n'est envoyé" → l'opération est-elle bien dans la table SQLite Outbox ?
3 Différer dépendance SyncEngine.cs → bloc operation.DependencyEntityType Opération "différée" → dépendance non réconciliée.
4 Envoi HTTP App.Local/Services/MobileApiClient.cs → SendOperationAsync Erreur 401/500 → token, BaseUrl, API démarrée ?
5 Réception API Core/Services/Mobile/SyncOperationService.cs → ReceiveAsync Validation (OperationId/DeviceId/EntityType/Payload) → code 400.
6 Écriture TOSRV Data/Repositories/Technical/SyncTransferRepository.cs → InsertToSrvAsync Connexion Service (OLE DB) OK ?
7 Transfert SyncTransferRepository.cs → TransferToReceptionAsync TOSRV lu, copié dans MobReception, puis supprimé.
8 Lecture en attente SyncTransferRepository.cs → GetPendingOperationsAsync MobReception avec MR_Fait = false.
9 Tri Worker/SyncProcessor.cs → OrderOf + OrderBy CREATE avant UPDATE avant ARCHIVE.
10 Application Worker/SyncProcessor.cs → ProcessOperationAsync Décision Success / Retry / Reject (voir B.4).
11 Handler Worker/InterventionSyncOperationHandler.cs → ApplyAsync Logique métier Intervention (voir B.5).
12 Résultat SyncTransferRepository.cs → MarkSuccessAsync / MarkRejectedAsync / IncrementRetryAsync MobLog terminal.

B.3 Exemple concret : "mon intervention créée sur le mobile n'arrive jamais"

Suivre cette checklist dans l'ordre :

  1. Le mobile a-t-il bien mis l'opération en Outbox ? → Regarder la table SQLite Outbox (omika.db). Si vide, le problème est en amont (le service local qui crée l'intervention n'a pas appelé OutboxService.EnqueueAsync).

  2. L'opération part-elle ? → SyncEngine.SyncAsync : vérifier les logs REFRESH START / REFRESH FAILED stage=.... Si stage indique une exception, lire result.FailureStage et result.ExceptionType.

  3. L'API a-t-elle reçu ? → Regarder les logs de Omika.Api (ou un outil comme MobLog). Si RECEIVED est retourné, l'opération est dans TOSRV.

  4. Le Worker l'a-t-il traitée ? → Regarder TOSRV (doit se vider) puis MobReception (doit contenir l'opération avec MR_Fait = false), puis MobLog (résultat terminal). Si TOSRV reste plein → le Worker ne tourne pas ou la connexion Service est cassée. Si MobReception reste plein → le handler échoue (voir MobLog.ML_Message).

  5. Le résultat est-il revenu au mobile ? → Le mobile interroge /api/sync/status/{operationId}. Si Rejected, lire le code d'erreur dans MobLog.ML_Message et le corréler avec la section B.5.

B.4 L'arbre de décision de ProcessOperationAsync (le cœur du Worker)

Worker/SyncProcessor.cs → ProcessOperationAsync :

1. RetryCount >= MaxRetryCount (30) ?
     OUI → RejectAsync("MAX_RETRY") → REJECTED (terminal)
     NON → continuer

2. Idempotence : GetTerminalResultAsync(OperationId) existe déjà ?
     OUI + Success   → reconstruit SUCCESS sans réappliquer le handler
     OUI + Rejected  → reconstruit REJECTED sans réappliquer le handler
     NON → continuer

3. handler = HandlerRegistry.GetHandler(EntityType) ?? DefaultSyncOperationHandler

4. Ouvre UNE transaction OLE DB locale (Business/WebService)

5. result = handler.ApplyAsync(operation)

     Success          → MarkSuccessAsync + Commit      → SUCCESS
     PermanentFailure → Rollback + MarkRejectedAsync   → REJECTED
     TransientFailure → Rollback + IncrementRetryAsync → RETRY (retryCount + 1)
     Exception        → Rollback + IncrementRetryAsync → RETRY

À retenir pour le debug :

  • TRANSIENT_FAILURE = erreur temporaire → retry (ex. un CREATE pas encore arrivé).
  • PERMANENT_FAILURE = erreur définitive → rejet immédiat (ex. DELETE non supporté, validation métier).
  • L'idempotence empêche de réappliquer deux fois le même OperationId.

B.5 Le handler Intervention en détail (exemple à connaître)

Worker/InterventionSyncOperationHandler.cs — ApplyAsync :

EntityGuid vide ? → PERMANENT_FAILURE ("INTERVENTION_INVALID_ENTITY_GUID")

OperationType :
  Create  → HandleCreateAsync
  Update  → HandleUpdateAsync
  Archive → HandleArchiveAsync
  Delete  → PERMANENT_FAILURE ("INTERVENTION_OPERATION_NOT_SUPPORTED")
            (la suppression physique est interdite → utiliser ARCHIVE)

CREATE (HandleCreateAsync)

  • Désérialise le payload (InterventionSyncPayload). Si invalide → INTERVENTION_INVALID_PAYLOAD.
  • Appelle IInterventionService.CreateAsync.
  • Si succès → Success(created.Id, requestedEntityGuid: operation.EntityGuid, canonicalEntityGuid: ...).
  • Si DomainValidationException → PERMANENT_FAILURE ("INTERVENTION_VALIDATION_FAILED").

UPDATE (HandleUpdateAsync)

  • Désérialise le payload. Si invalide → INTERVENTION_INVALID_PAYLOAD.
  • Résout l'identité physique (ResolvePhysicalIdentity). Si non résolvable → TRANSIENT_FAILURE ("INTERVENTION_NOT_FOUND") — un CREATE peut encore être en attente.
  • Vérifie l'existence. Si absente → TRANSIENT_FAILURE (retry).
  • Sinon → IInterventionService.UpdateAsync.

ARCHIVE (HandleArchiveAsync)

  • Résout l'identité physique. Si non résolvable → TRANSIENT_FAILURE.
  • Vérifie l'existence. Si absente → TRANSIENT_FAILURE.
  • Sinon → IInterventionService.ArchiveAsync.

Le fallback (DefaultSyncOperationHandler)

  • Ne déclare aucun EntityType. S'il est sélectionné, c'est qu'aucun handler n'existe pour ce type → PERMANENT_FAILURE ("NO_HANDLER"). Jamais de faux SUCCESS.

B.6 Récapitulatif "où est le problème ?" en une phrase

Où l'opération est bloquée Cause probable Fichier à ouvrir
Jamais dans l'Outbox Le service local n'a pas enregistré le changement App.Local/Services/*LocalService.cs, OutboxService.cs
Dans l'Outbox mais jamais envoyée Hors ligne, ou SyncEngine en erreur NetworkConnectivity.cs, SyncEngine.cs
Envoyée mais rejetée par l'API (400) Validation échouée (OperationId/DeviceId/Payload) SyncOperationService.ReceiveAsync
Dans TOSRV mais pas traitée Worker arrêté, ou connexion Service cassée SyncWorker.cs, SyncTransferRepository.cs
Dans MobReception mais jamais finalisée Handler échoue (voir MobLog) *SyncOperationHandler.cs, SyncProcessor.cs
MobLog = REJECTED Erreur métier/définitive MobLog.ML_Message → corréler avec B.5


ANNEXE C — Tracer un bug d'activation de licence

C.1 Le diagramme de séquence

TÉLÉPHONE (mobile)                              SERVEUR
────────────────────────────                    ──────────────────────────────
LicenseActivationViewModel
  └─ MobileSessionService.ActivateAsync(codeClient, numeroLicence)
       │ 1. lit deviceId (IDeviceIdProvider)
       │ 2. MobileApiClient.ActivateAsync
       │─────────────────────────────► POST /api/license/activate
       │                               LicenseService.ActivateAsync
       │                                 1. LicenceRepository.GetClientByCodeAsync
       │                                    → SELECT ClientBase (HFSQL)
       │                                 2. LicenceRepository.GetByNumeroAndClientAsync
       │                                    → SELECT Licence (HFSQL)
       │                                 3. vérifie active / expiration / deviceId
       │                                 4. si OK : AssignDeviceAsync (UPDATE Licence)
       │                                            + AddHistoAsync (INSERT LicenceHisto)
       │                               LicenseEndpoints.BuildSession
       │                                 → TokenService.GenerateToken (si valide)
       │◄─────────────────────────────  LicenseResponse + AccessToken
       │ 3. si valide : PersistSessionAsync
       │      → token en secure storage (ISecureTokenStore)
       │      → GET /api/mobile/bootstrap → sauvegardé en SQLite
       ▼
RootViewModel.OnActivationSucceeded → affiche le Shell

C.2 La checklist "l'activation échoue"

Suivre dans l'ordre :

  1. Le mobile appelle-t-il l'API ? → MobileSessionService.ActivateAsync. Vérifier BaseUrl (défaut http://localhost:5000) et que l'API tourne.

  2. Quel code d'erreur est renvoyé ? → LicenseResponse.ErrorCode + ErrorMessage. Corréler avec le tableau C.3.

  3. Le client existe-t-il en base ? → LicenceRepository.GetClientByCodeAsync → table ClientBase (CL_Code).

  4. La licence existe-t-elle pour ce client ? → LicenceRepository.GetByNumeroAndClientAsync → table Licence (LI_Numero + LI_IDClient).

  5. La licence est-elle active / non expirée / non déjà attribuée ? → colonnes LI_Active, LI_DateExpiration, LI_IDMobile.

  6. Le token est-il bien émis ? → LicenseEndpoints.BuildSession + TokenService.GenerateToken (clé MobileAuth:SigningKey obligatoire).

C.3 Correspondance code d'erreur → cause → fichier

Code Cause Où regarder
E1001 Données incomplètes (codeClient, numéro, deviceId) LicenseService.ActivateAsync (étape 1)
E2001 Client inconnu LicenceRepository.GetClientByCodeAsync + table ClientBase
E3001 Licence inconnue LicenceRepository.GetByNumeroAndClientAsync + table Licence
E3002 Licence inactive LI_Active
E3003 Licence expirée LI_DateExpiration
E3004 Licence attribuée à un autre mobile LI_IDMobile

⚠️ Rappel (déjà signalé dans les docs) : la base Licence réelle (BASE_LICENCES) peut ne pas contenir les tables ClientBase/Licence/LicenceHisto. Vérifier la base réelle avant de conclure à un bug de code.


ANNEXE D — Tracer un bug de document (upload / download)

D.1 Upload (mobile → serveur)

SyncEngine.SendDocumentAsync
  │ 1. désérialise le payload (DocumentOutboxPayload)
  │ 2. vérifie File.Exists(payload.LocalPath)   → sinon Rejected
  │ 3. lit le fichier en bytes
  │ 4. MobileApiClient.UploadDocumentAsync
  │─────────────────────────────► POST /api/documents
  │                               MobileDocumentService.UploadAsync
  │                                 → SanitizeFileName (nom sûr ?)
  │                                 → IDocumentFileStorage.Write (stockage)
  │                                 → IDocumentRepository.CreateAsync (métadonnées)
  │◄─────────────────────────────  DocumentUploadResponse
  │ 5. si Success → OutboxState.Accepted
  │    sinon      → OutboxState.Rejected + LastError

Checklist upload :

  • "Fichier local introuvable" → SyncEngine.SendDocumentAsync (étape 2).
  • "Nom de fichier invalide" → MobileDocumentService.UploadAsync (SanitizeFileName).
  • "Taille dépassée" → IDocumentFileStorage.Write (DOCUMENT_SIZE_EXCEEDED).
  • "Upload échoué" → IDocumentRepository.CreateAsync (DOCUMENT_UPLOAD_FAILED).

D.2 Download (serveur → mobile)

MobileApiClient.DownloadDocumentAsync
  │─────────────────────────────► GET /api/documents/{documentGuid}
  │                               MobileDocumentService.DownloadAsync
  │                                 → résout DocumentGuid → identité physique
  │                                 → IDocumentRepository.GetByPhysicalIdentityAsync
  │                                 → IDocumentFileStorage.Read (bytes)
  │◄─────────────────────────────  DocumentDownloadResponse

Checklist download :

  • "Document introuvable" → résolution d'identité (ResolvePhysicalIdentity) ou GetByPhysicalIdentityAsync retourne null.
  • Contenu vide → IDocumentFileStorage.Read (chemin physique invalide ?).

ANNEXE E — Lancer l'API et le Worker en local (guide pratique)

E.1 Prérequis

  • Un serveur HFSQL doit être joignable à 127.0.0.1:4900 (config de test).
  • Le fichier config/hfsql-test.local.json est chargé uniquement en Development/Test (via HfSqlTestConfigurationLoader.AddLocalTestConfiguration). Il contient les connexions Hfsql (Business), LicenseConnection, SyncConnection, ServiceConnection, la clé MobileAuth:SigningKey et les flags E2E.
  • ⚠️ Point de sécurité important : ce fichier de test contient "AllowHfSqlWrites": false → les écritures HFSQL sont désactivées dans ce mode. C'est un environnement de lecture seule pour déboguer sans risque.
  • L'API et le Worker font un fail-fast au démarrage : ils appellent IDatabaseRuntimeInitializer.InitializeAsync() et lèvent une exception si la base est injoignable ou inconnue. Sans HFSQL démarré, ils ne démarrent pas.

E.2 Commandes PowerShell (depuis D:\Avalonia\Omika)

# 1. Restaurer les packages
dotnet restore .\Omika.slnx

# 2. Compiler
dotnet build .\Omika.slnx

# 3a. Lancer l'API (ou utiliser le script tout prêt)
dotnet run --project .\src\Omika.Api\Omika.Api.csproj

# 3b. Lancer le Worker (ou utiliser le script tout prêt)
dotnet run --project .\src\Omika.Worker\Omika.Worker.csproj

Il existe aussi des scripts qui démarrent en arrière-plan et redirigent les logs :

.\tools\start_api_e2e.ps1      # → logs dans api_e2e_out.txt / api_e2e_err.txt
.\tools\start_worker_e2e.ps1   # → logs dans worker_e2e_out.txt / worker_e2e_err.txt

Pour l'application mobile/desktop, l'API doit être joignable à l'URL configurée (défaut http://localhost:5000, surchargeable via mobile-api.local.json).

E.3 Les logs à observer

Composant Où regarder
API Console / api_e2e_out.txt, api_e2e_err.txt. Endpoint /health.
Worker Console / worker_e2e_out.txt, worker_e2e_err.txt. Logs "Worker de synchronisation démarré".
Mobile Console.WriteLine : REFRESH START, REFRESH SUCCESS/FAILED stage=..., SQLITE IMPORT END.
Sync serveur MobLog (résultats terminaux) + logs ILogger dans SyncProcessor/SyncWorker.

Fin du guide. Document généré en lecture seule du code source ; aucun fichier source n'a été modifié.