gigafibre-fsm/apps/field-tech/README.md
louispaulb e66b328efb
Some checks failed
field-tech-android / build-apk (push) Failing after 8m56s
feat(field-tech): app Capacitor native (geofence Transistorsoft + scan MLKit) + CI
App technicien : appairage 1x (QR), géorepérage natif en arrière-plan (app fermée)
-> checkpoints au hub /field/ts, scan série/MAC on-device MLKit. UI réutilisée depuis
le hub (/field). APK Android buildé (debug, 19 Mo, arm64-v8a + armeabi-v7a).

- apps/field-tech : Capacitor 6 + Vite ; src/main.js (appairage @capacitor/preferences
  + BackgroundGeolocation.addGeofences + redirection /field) ; projet android/ avec les
  4 correctifs Gradle commités (force work-runtime 2.9.1, minSdk 24, repos maven xms.g
  + Huawei, googlePlayServicesLocationVersion 21.0.1) ; abiFilters arm (APK 32->19 Mo).
- README : recette de build complète (toolchain M4, les 4 fixes, install/appairage) ;
  BUILD-ANDROID.md (Docker + Android Studio + release signée) ; CI-SETUP.md (runner Gitea).
- CI Gitea Actions (.gitea/workflows) : android (runner Linux HORS prod, cache Gradle/npm
  -> artefact APK) + ios (runner macOS, workflow_dispatch, en attente compte Apple).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:47:27 -04:00

8.3 KiB
Raw Blame History

Targo Tech — app technicien (Capacitor)

App mobile native des techniciens TARGO/Gigafibre. Capture passive du temps par intervention : appairage une seule fois (QR), puis géorepérage natif en arrière-plan (app fermée) → arrivées/départs détectés automatiquement → checkpoints au hub. Le tech ne tape rien sur place. UI (liste/carte/Street View/photo/scan) réutilisée depuis le hub (/field). Scan série/MAC on-device MLKit.


Fonctionnement

┌─ Bureau (1×) ─────────┐     ┌─ Téléphone du tech ──────────────────────┐
│ Ops → bouton 📱 tech  │ QR  │ app Targo Tech                            │
│ techFieldLink (token) ├────▶│ src/main.js : token → @capacitor/         │
└───────────────────────┘     │   preferences (persistant)               │
                              │       │                                   │
                              │       ▼                                   │
                              │ Transistorsoft BackgroundGeolocation:     │
                              │  addGeofences(jobs du jour, r=200m)       │
                              │  enter/exit ── autoSync ──▶ hub /field/ts │  ← app FERMÉE + reboot
                              │       │                                   │
                              │       ▼ (à l'ouverture)                   │
                              │ location.replace(hub /field?t=token)      │
                              │  → liste/détail/carte/StreetView/photo    │
                              │  → scan série/MAC = MLKit on-device       │
                              └───────────────────────────────────────────┘
  • Appairage 1×pairScan() (MLKit) ou pairPaste() lit le lien tech (techFieldLink), extrait le token, le persiste. Plus jamais de lien à ouvrir sur place.
  • Géorepérage natif@transistorsoft/capacitor-background-geolocation enregistre un geofence par job du jour (identifier = token signé du job). Les enter/exit sont POSTés nativement au hub /field/ts (déjà déployé) qui mappe l'identifier → checkpoint → dérive actual_start/actual_end. Survit à l'app fermée et au reboot — c'est tout l'intérêt par rapport à un watchPosition JS.
  • UI hébergée — après appairage, l'app charge /field?t=token (servi par le hub lib/roster.js) : liste des jobs, carte Mapbox, Google Street View, photo, scan. Aucune UI dupliquée dans l'app.
  • Scan série/MAC — MLKit on-device en priorité (instantané, hors-ligne) ; replis BarcodeDetector web puis proxy IA Gemini (/field/vision) pour étiquettes texte seul.

Backend (tout déjà déployé sur le hub) : GET /field/tech?t= (jobs du jour du tech), GET /field/job?t=, POST /field/checkpoint, POST /field/ts (webhook geofence Transistorsoft, auto-auth via identifier signé), POST /field/photo, POST /field/device, POST /field/vision. Tokens = HMAC signés (par job / par tech), sans PII, stateless.


Recette de build Android

Les 4 correctifs Gradle ci-dessous sont déjà appliqués et commités dans android/ (Capacitor ne réécrit pas ces fichiers lors d'un cap sync). Ils ne sont à ré-appliquer que si on régénère android/ from scratch (rm -rf android && npx cap add android).

Prérequis (testé sur MacBook Pro M4, sans sudo)

# JDK 17
brew install openjdk@17
export JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home

# Android SDK (cmdline-tools) → ~/Library/Android/sdk
export ANDROID_HOME="$HOME/Library/Android/sdk"
sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"

Build (APK debug)

cd apps/field-tech
npm install
npm run build                 # Vite → www/
npx cap sync android          # copie www + plugins (NE touche pas aux fixes Gradle)
cd android && ./gradlew assembleDebug
# → android/app/build/outputs/apk/debug/app-debug.apk

Les 4 correctifs Gradle (pourquoi)

# Symptôme Fichier Correctif
1 androidx.work:work-runtime:2.10.0 requires compileSdk 35 android/app/build.gradle configurations.all { resolutionStrategy { force 'androidx.work:work-runtime:2.9.1'; force '…-ktx:2.9.1' } } (on reste en compileSdk 34)
2 Manifest merger failed: minSdkVersion 22 < 24 (tslocationmanager) android/variables.gradle minSdkVersion = 24
3 package com.transistorsoft.xms.g.common does not exist android/build.gradle dans allprojects.repositories : les 2 repos maven locaux des plugins (…background-geolocation/libs, …background-fetch/libs) + maven { url 'https://developer.huawei.com/repo/' }. Les AAR tslocationmanager* (classes xms.g) vivent dans node_modules/.../libs ; le plugin n'ajoute pas ce repo → étape manuelle (= INSTALL-ANDROID officiel Transistorsoft).
4 cannot find symbol EVENT_PROVIDERCHANGE/EVENT_AUTHORIZATION/… (67 err) android/variables.gradle googlePlayServicesLocationVersion = '21.0.1' → le plugin sélectionne l'AAR tslocationmanager-v21 (API moderne attendue par Capacitor v6.1.5 ; major <21 prendrait l'ancien 3.6.4 sans ces constantes).

Optimisation taille

android/app/build.gradle filtre les ABI sur arm64-v8a + armeabi-v7a (téléphones réels ; l'émulateur Apple Silicon est arm64 → test local OK). Pour un émulateur Intel x86_64, ajouter 'x86_64' au bloc ndk { abiFilters … }.

Release / Play Store (plus tard)

Préférer compileSdk/targetSdk 35 + AGP 8.7+ (et retirer le force work-runtime) plutôt que rester en 34 ; + clé licence Transistorsoft en meta-data du manifest + keystore. Détails : BUILD-ANDROID.md.


Installer & appairer

  • USB (débogage USB activé) : ~/Library/Android/sdk/platform-tools/adb install -r app-debug.apk
  • Sans fil : envoyer l'APK (Drive/courriel) → ouvrir sur le tél → autoriser « Installer applis inconnues ».
  • 1er lancement → écran d'appairage → scanner le QR (ou coller le lien) du bouton 📱 dans Ops → l'app mémorise le token, arme le geofence, et charge la liste des jobs.
  • Tester le geofence : se déplacer vers/depuis une adresse de job (rayon 200 m), app fermée → vérifier dans Ops que actual_start/end se posent.

CI (git.targo.ca)

Push sur apps/field-tech/**.gitea/workflows/field-tech-android.yml build l'APK sur un runner Linux auto-hébergé (jamais sur erp/prod) → artefact téléchargeable. Voir CI-SETUP.md. iOS : field-tech-ios.yml (runner macOS + compte Apple, workflow_dispatch).

iOS

Même src/main.js. Scaffolder quand le compte Apple Developer est validé : npx cap add ios + cocoapods + Xcode + licence Transistorsoft iOS + NSLocationAlwaysAndWhenInUseUsageDescription + mode arrière-plan location.

Sécurité

  • Aucun secret dans le repo : seul figure l'URL publique https://msg.gigafibre.ca (src/main.js) et le token Mapbox public pk.… (côté hub /field). Les secrets serveur restent dans le hub (ops_secret.php / env), jamais ici.
  • Tokens d'appairage = HMAC signés, sans PII. Keystores / certificats : gitignorés (jamais commités).

Arborescence

apps/field-tech/
├── index.html            # écran d'appairage (coquille)
├── src/main.js           # appairage + init géorepérage + redirection /field
├── capacitor.config.json # appId ca.targo.field
├── vite.config.js        # build → www/
├── android/              # projet natif (4 fixes Gradle commités) — artefacts gitignorés
├── Dockerfile            # build headless (alternative au build local) — voir BUILD-ANDROID.md
├── README.md  BUILD-ANDROID.md  CI-SETUP.md