All docs moved with git mv so --follow preserves history. Flattens the single-folder layout into goal-oriented folders and adds a README.md index at every level. - docs/README.md — new landing page with "I want to…" intent table - docs/architecture/ — overview, data-model, app-design - docs/features/ — billing-payments, cpe-management, vision-ocr, flow-editor - docs/reference/ — erpnext-item-diff, legacy-wizard/ - docs/archive/ — HANDOFF-2026-04-18, MIGRATION, status-snapshots/ - docs/assets/ — pptx sources, build scripts (fixed hardcoded path) - roadmap.md gains a "Modules in production" section with clickable URLs for every ops/tech/portal route and admin surface - Phase 4 (Customer Portal) flipped to "Largely Shipped" based on audit of services/targo-hub/lib/payments.js (16 endpoints, webhook, PPA cron, Klarna BNPL all live) - Archive files get an "ARCHIVED" banner so stale links inside them don't mislead readers Code comments + nginx configs rewritten to use new doc paths. Root README.md documentation table replaced with intent-oriented index. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
435 lines
19 KiB
Markdown
435 lines
19 KiB
Markdown
# Facturation & Paiements — Handoff dev
|
||
|
||
> Référence unique pour toutes les fonctionnalités facture / paiement construites sur
|
||
> la stack `erp.gigafibre.ca` + `client.gigafibre.ca` + `ops`. Lisez
|
||
> [../architecture/overview.md](../architecture/overview.md) d'abord pour le contexte
|
||
> réseau/services.
|
||
|
||
Dernière MàJ : 2026-04-17
|
||
|
||
---
|
||
|
||
## Table des matières
|
||
|
||
1. [Importation du système legacy vers ERPNext](#1-importation-du-système-legacy-vers-erpnext)
|
||
2. [App Frappe custom — `gigafibre_utils`](#2-app-frappe-custom--gigafibre_utils)
|
||
3. [Print Format « Facture TARGO »](#3-print-format--facture-targo-)
|
||
4. [Flux de paiement public (sans Authentik)](#4-flux-de-paiement-public-sans-authentik)
|
||
5. [Infrastructure Docker](#5-infrastructure-docker)
|
||
6. [UI Ops — aperçu client](#6-ui-ops--aperçu-client)
|
||
7. [Configuration & secrets](#7-configuration--secrets)
|
||
8. [Points connus / TODO](#8-points-connus--todo)
|
||
|
||
---
|
||
|
||
## 1. Importation du système legacy vers ERPNext
|
||
|
||
### Particularités
|
||
|
||
- **Source** : MariaDB legacy `gestionclient` (conteneur `legacy-db`, 10.100.80.100).
|
||
- **Cible** : PostgreSQL ERPNext v16 (patches GROUP BY/HAVING/quotes appliqués — cf.
|
||
`feedback_erpnext_postgres.md`).
|
||
- **Correspondance des clés** :
|
||
- `customer.id` legacy → `Customer.legacy_id` (custom field).
|
||
- `service.delivery_id` legacy → `Service Location.legacy_delivery_id` (custom field).
|
||
- `invoice_item.service_id` legacy → chaîné via `service.delivery_id` pour rattacher
|
||
un item à son `Service Location` ERPNext.
|
||
- **Prix** : conservés tels quels (pas de re-calcul des taxes); TPS/TVQ sont imports
|
||
inclus dans `taxes[]` de chaque `Sales Invoice`.
|
||
- **Volumes finaux** : 6 667 customers · 21 K subscriptions · 115 K invoices ·
|
||
1,6 M invoice items · 242 K tickets.
|
||
|
||
### Fonctionnalités — scripts `scripts/migration/*`
|
||
|
||
| Script | Rôle |
|
||
|---|---|
|
||
| `import_invoices.py` | Import complet des factures legacy + JOIN sur `service` pour récupérer `delivery_id` et renseigner `service_location` sur chaque item. |
|
||
| `backfill_service_location.py` | Script one-shot pour remplir `service_location` sur les 1,6 M items déjà importés (batches `execute_values` de 20 K lignes, 227 s). |
|
||
| `import_payments.py` / `reimport_payments.py` / `import_expro_payments.py` | Import des paiements legacy en `Payment Entry` réconciliés. |
|
||
| `import_payment_methods.py` | Carte + ACH + chèque — créés en tant que `Mode of Payment`. |
|
||
| `fix_invoice_outstanding.py` | Réconciliation après import — recalcule `outstanding_amount` via Frappe. |
|
||
| `fix_invoice_customer_names.py` | Renomme customers pour refléter le nom légal courant (`Subscription.bill_to_name`). |
|
||
| `import_payment_arrangements.py` | Ententes de paiement (échelonnement) → `Payment Request` + scheduler. |
|
||
| `geocode_locations.py` | Géocode toutes les `Service Location` avec Mapbox. |
|
||
|
||
### Restauration Item ↔ Service Location (particularité majeure)
|
||
|
||
Sans ce lien le PDF ne peut pas grouper les lignes par adresse.
|
||
|
||
```
|
||
legacy.invoice_item.service_id
|
||
└─→ legacy.service.delivery_id
|
||
└─→ ERPNext Service Location.legacy_delivery_id (custom field)
|
||
```
|
||
|
||
1. Custom field `Sales Invoice Item.service_location` (Link → Service Location) créé
|
||
via `add_missing_custom_fields.py`.
|
||
2. `import_invoices.py` mis à jour : JOIN sur `service` + lookup
|
||
`Service Location.legacy_delivery_id` → écrit `service_location` lors de l'INSERT.
|
||
3. Backfill ponctuel : `backfill_service_location.py`
|
||
(`UPDATE ... FROM (VALUES %s)` via `psycopg2.extras.execute_values`).
|
||
|
||
### Points d'attention
|
||
|
||
- **Mots de passe legacy** = MD5 non-salé → forcer un reset via OTP email/SMS
|
||
(`project_portal_auth.md`).
|
||
- **Devices** rattachés aux **addresses** (Service Location), pas aux customers
|
||
(`feedback_device_hierarchy.md`).
|
||
- **Serials TPLG** ERPNext ≠ serials réels — matching via MAC
|
||
(`feedback_device_serial_mapping.md`).
|
||
|
||
---
|
||
|
||
## 2. App Frappe custom — `gigafibre_utils`
|
||
|
||
### Particularités
|
||
|
||
- **Chemin source** (serveur ERP) : `/opt/erpnext/custom/gigafibre_utils/`
|
||
- **Cuite dans l'image Docker** — `/opt/erpnext/custom/Dockerfile` copie l'app dans
|
||
`apps/gigafibre_utils` et la pip-installe en editable.
|
||
- **Raison d'exister** : contourner le sandbox Frappe `safe_exec` (qui bloque
|
||
`__import__` et certaines opérations) en exposant des `@frappe.whitelist()` Python
|
||
natifs appelables depuis Jinja et côté client.
|
||
|
||
### Arborescence
|
||
|
||
```
|
||
gigafibre_utils/
|
||
├── Dockerfile # FROM frappe/erpnext:v16.10.1 + chromium + cairosvg
|
||
└── gigafibre_utils/
|
||
├── __init__.py
|
||
├── hooks.py
|
||
├── modules.txt
|
||
├── patches.txt
|
||
├── api.py # 823 lignes, toutes les méthodes whitelisted
|
||
└── www/
|
||
└── pay-public.html # Page publique /pay-public (sans Authentik)
|
||
```
|
||
|
||
### Fonctionnalités — `api.py`
|
||
|
||
#### 2.1 Rendu visuel (QR, logo, descriptions)
|
||
|
||
| Méthode | `allow_guest` | Rôle |
|
||
|---|:-:|---|
|
||
| `invoice_qr(invoice)` | ✅ | PNG binaire du QR code (payload = URL `/pay-public` signée, TTL 60 j). |
|
||
| `invoice_qr_base64(invoice)` | ✅ | Base64 brut (pour embed direct dans Jinja). |
|
||
| `invoice_qr_datauri(invoice)` | ✅ | `data:image/png;base64,…` prêt à injecter en `<img src>`. |
|
||
| `logo_base64(height)` | ✅ | Logo TARGO rasterisé via cairosvg (SVG → PNG 96 px). |
|
||
| `short_item_name(name, max_len)` | ✅ | Nettoie/tronque les noms d'item (retire caractères parasites, limite longueur). |
|
||
|
||
**Particularité QR** : la fonction `_sign_pay_token` génère un token HMAC-SHA256
|
||
(clé `gigafibre_pay_secret`) → `{invoice, expires_at}` encodé base64-url. Le QR
|
||
pointe vers `https://client.gigafibre.ca/pay-public?inv=…&t=…`.
|
||
|
||
**Particularité logo** : wkhtmltopdf (QtWebKit 2013) ignore `<defs><style>` SVG.
|
||
Solution retenue : rendu serveur-side via **cairosvg → PNG base64** injecté dans
|
||
le template. Aucune dépendance côté client.
|
||
|
||
#### 2.2 Génération PDF (`invoice_pdf`)
|
||
|
||
- Contourne **complètement** la pipeline PDF Frappe (qui ajoute 15 mm de marge top
|
||
forcée si pas de `#header-html`, et post-process avec pypdf).
|
||
- Lance `chromium --headless=new --no-pdf-header-footer --print-to-pdf` directement
|
||
dans un `tempfile.TemporaryDirectory`.
|
||
- Réponse HTTP : `Content-Type: application/pdf` + `Content-Disposition: inline`
|
||
(⚠️ **ne pas** utiliser `"binary"` — empêche l'affichage iframe et Acrobat échoue
|
||
à ouvrir le fichier téléchargé).
|
||
- Producer : **Skia/PDF m147** (identique à Antigravity/Gemini → fidélité pixel-parfait).
|
||
|
||
Usage :
|
||
```
|
||
GET /api/method/gigafibre_utils.api.invoice_pdf?name=SINV-2026-700010
|
||
GET /api/method/gigafibre_utils.api.invoice_pdf?name=SINV-xxx&print_format=Facture+TARGO
|
||
```
|
||
|
||
#### 2.3 Code de parrainage (`referral_code`)
|
||
|
||
- Déterministe par Customer — **HMAC-SHA256** (clé `gigafibre_pay_secret`) sur
|
||
`customer.name` → 30 bits → **6 caractères Crockford base32** (pas de `0/O/1/I/L/U`
|
||
ambigus).
|
||
- Stable dans le temps (pas stocké en DB).
|
||
- ~1,07 milliard de codes possibles → collisions négligeables pour <10 M customers.
|
||
|
||
---
|
||
|
||
## 3. Print Format « Facture TARGO »
|
||
|
||
### Particularités
|
||
|
||
- **Install** : `scripts/migration/setup_invoice_print_format.py` (idempotent).
|
||
- **Doctype** : `Sales Invoice`.
|
||
- **Générateur** : **`pdf_generator="chrome"`** — Chromium `--print-to-pdf`
|
||
headless. C'est **la meilleure méthode** dans ERPNext v16 ; wkhtmltopdf
|
||
(QtWebKit 2013) est obsolète et donne un rendu dégradé (SVG `<defs><style>`
|
||
ignorés, flexbox cassé, polices Unicode QC incomplètes). Avantages :
|
||
- **CSS moderne** : flexbox/grid, `@page` avec `counter(page)` / `counter(pages)`
|
||
pour la pagination (p. ex. « Page 1 de 2 » en `@top-right`), `break-inside: avoid`.
|
||
- **Polices système** : les accents/caractères spéciaux (é, à, œ, ℃) passent
|
||
sans mapping manuel.
|
||
- **Rendu pixel-perfect** : même moteur que le navigateur de preview — le
|
||
preview HTML et le PDF sont identiques.
|
||
- **Performances** : ~1 s pour une facture typique (vs ~3 s wkhtmltopdf).
|
||
- **Prérequis** : Chromium installé dans le container (voir Dockerfile
|
||
`FROM frappe/erpnext:v16.10.1 + apt-get install chromium`), clé
|
||
`chromium_path: /usr/bin/chromium` dans `common_site_config.json`.
|
||
- **Marges** : forcées par le Print Format `margin_top=5, bottom=5, left=15,
|
||
right=15` (mm) — Chrome PDF ignore `@page { margin: … }` dans le CSS
|
||
(`preferCSSPageSize=false`), il faut passer par les options ERPNext.
|
||
- **Template Jinja** : ~25 Ko, inliné dans le script `setup_invoice_print_format.py`
|
||
(constante `html_template = r"""…"""`) — source de vérité unique, versionnée.
|
||
|
||
### Fonctionnalités clés du template
|
||
|
||
1. **Prélude de résolution de données** — récupère `customer`, `service_locations`,
|
||
`prev_invoice`, `recent_payments`, `remaining_balance`, `account_number`, etc.
|
||
depuis le doc `Sales Invoice`.
|
||
2. **Position de la fenêtre d'enveloppe** — bloc adresse client à **50 mm du haut**
|
||
(2″) pour s'aligner sur enveloppe #10 à fenêtre (ouverture 2″-3½″).
|
||
- `margin-top` du `.cl-block` varie selon la présence d'un `prev_invoice`
|
||
(10 mm si bloc SOMMAIRE DU COMPTE présent en amont, 33 mm sinon).
|
||
3. **Regroupement par Service Location** — la section « FRAIS COURANTS » itère
|
||
`current_charges_locations[]`, chaque location a ses items (pas de sous-total
|
||
par location pour réduire la hauteur).
|
||
4. **Période de service inline** — calculée par item :
|
||
- Utilise `service_start_date` / `service_end_date` si présents (ERPNext deferred).
|
||
- Sinon déduit la période courante sauf si l'item est **one-time** (mots-clés :
|
||
`install`, `activation`, `rabais`, `frais unique`, `remise`, ou montant négatif).
|
||
- Format : `(1er avril au 30 avril)` plein mois, `(16 avril au 30 avril)` prorata.
|
||
5. **Colonne de droite (meta + QR + parrainage + conditions)** :
|
||
- Meta-band : N° compte · Date · N° facture
|
||
- **Montant dû** (boîte verte)
|
||
- QR code (base64 inline, 60 j TTL)
|
||
- Boîte parrainage (fond gris, accent vert) — code 6 car. via `gigafibre_utils.api.referral_code`.
|
||
- Conditions légales (« sera **assujettie** à des frais de retard… »)
|
||
- Contactez-nous : **1867 chemin de la Rivière, Ste-Clotilde, J0L 1W0 · 855 888-2746**
|
||
6. **Mini-footer CPRST** en bas de page.
|
||
|
||
### Particularités Jinja
|
||
|
||
- Variables `svc_start` / `svc_end` **doivent être définies avant la boucle items**
|
||
(sinon `UndefinedError`).
|
||
- Les appels `frappe.call(...)` dans le sandbox Jinja requièrent un `frappe.local.request`
|
||
vivant → le rendu hors contexte web (ex. `bench execute`) échoue : **toujours
|
||
tester via HTTP** (l'endpoint `invoice_pdf` fait ça correctement).
|
||
|
||
### Fichiers connexes
|
||
|
||
- `scripts/migration/invoice_preview.jinja` — copie de travail lisible (pas utilisée
|
||
en prod — seul le Print Format installé compte).
|
||
- `scripts/migration/test_jinja_render.py` — rend le template localement via Chrome
|
||
macOS pour comparaison pixel (référence).
|
||
|
||
---
|
||
|
||
## 4. Flux de paiement public (sans Authentik)
|
||
|
||
### Vue d'ensemble
|
||
|
||
```
|
||
QR (facture) / SMS / Email Authentik-less
|
||
↓ ↑
|
||
https://client.gigafibre.ca/pay-public?inv=X&t=HMAC
|
||
↓
|
||
validate_pay_token → affiche résumé facture
|
||
↓
|
||
[Payer avec Stripe]
|
||
↓
|
||
create_checkout_session → redirect Stripe
|
||
↓
|
||
Stripe Checkout (hors ERP)
|
||
↓
|
||
stripe_webhook → Payment Entry reconciled
|
||
```
|
||
|
||
### Trois TTL de tokens (signés HMAC-SHA256)
|
||
|
||
| Token | TTL | Usage |
|
||
|---|---|---|
|
||
| **QR code** | 60 jours | Lien permanent sur facture imprimée. |
|
||
| **Magic link** | 15 minutes | SMS/email en cas de token expiré. |
|
||
| **pay_redirect** | 1 heure | Bridge depuis portail authentifié (customer connecté). |
|
||
|
||
### Fonctionnalités — `api.py`
|
||
|
||
| Méthode | `allow_guest` | Rôle |
|
||
|---|:-:|---|
|
||
| `pay_token(invoice, ttl_days)` | ❌ (admin) | Génère une URL signée (debug/test). |
|
||
| `validate_pay_token(invoice, token)` | ✅ | Vérifie signature+TTL, retourne résumé facture. |
|
||
| `request_magic_link(invoice, channel, last4)` | ✅ | Envoie SMS/email (gated par `last4` du téléphone). |
|
||
| `create_checkout_session(invoice, token)` | ✅ | Crée Stripe Checkout Session. |
|
||
| `stripe_webhook()` | ✅ | Vérifie signature Stripe, crée Payment Entry. |
|
||
| `pay_redirect(invoice)` | ⚠️ sous Authentik | Bridge portal → pay-public (bypass staff @targo.ca/@gigafibre.ca). |
|
||
|
||
### Landing page `/pay-public`
|
||
|
||
- Servie par Frappe depuis `gigafibre_utils/www/pay-public.html`.
|
||
- Vanilla JS — pas de build step, pas de framework.
|
||
- Lit `inv` + `t` depuis `frappe.form_dict`.
|
||
- Appelle `validate_pay_token` → affiche facture OU formulaire magic-link.
|
||
- Bouton « Payer avec Stripe » → `create_checkout_session` → redirect.
|
||
|
||
### Carve-out Traefik (contournement Authentik)
|
||
|
||
Fichier : `/opt/traefik/dynamic/pay-public.yml`.
|
||
|
||
Routeurs avec **priority ≥ 250** (> priorité 150 des règles `/api` Authentik) :
|
||
|
||
- `client-pay-public` — `PathPrefix(/pay-public)` (landing)
|
||
- `client-api-validate-pay-token`
|
||
- `client-api-magic-link`
|
||
- `client-api-create-checkout-session`
|
||
- `client-api-stripe-webhook`
|
||
|
||
Tous servis sur `client-portal-svc`, **aucun middleware** (pas de ForwardAuth).
|
||
|
||
### Stripe
|
||
|
||
- **Checkout Session API** : créée côté serveur (`_stripe_post`), redirect 303 vers
|
||
`session.url`.
|
||
- **Webhook** : vérifie `Stripe-Signature` (`_stripe_verify_signature`) avec
|
||
`gigafibre_stripe_webhook_secret` → crée `Payment Entry` rattaché à la facture.
|
||
- **Clé réutilisée** depuis targo-hub (`sk_live_51QCMOI…`).
|
||
|
||
### Twilio (magic link SMS)
|
||
|
||
- Auth basic (`twilio_account_sid:twilio_auth_token`).
|
||
- From `twilio_from_number=+14382313838`.
|
||
- Anti-abuse : `request_magic_link` exige `last4` matchant les 4 derniers chiffres
|
||
du téléphone customer avant d'envoyer.
|
||
|
||
---
|
||
|
||
## 5. Infrastructure Docker
|
||
|
||
### Image custom ERPNext
|
||
|
||
`/opt/erpnext/custom/Dockerfile` :
|
||
|
||
```dockerfile
|
||
FROM frappe/erpnext:v16.10.1
|
||
USER root
|
||
RUN apt-get install -y libcairo2 libpango-1.0-0 libpangocairo-1.0-0 \
|
||
libgdk-pixbuf-2.0-0 shared-mime-info \
|
||
chromium fonts-liberation fonts-noto-color-emoji
|
||
USER frappe
|
||
COPY --chown=frappe:frappe gigafibre_utils apps/gigafibre_utils
|
||
RUN env/bin/pip install -e apps/gigafibre_utils cairosvg
|
||
RUN grep -qx gigafibre_utils apps/apps.txt || echo gigafibre_utils >> apps/apps.txt
|
||
```
|
||
|
||
### Particularités
|
||
|
||
- Chromium installé à `/usr/bin/chromium` (config key `chromium_path`).
|
||
- cairosvg (Python) pour rasterisation SVG → PNG.
|
||
- App `gigafibre_utils` **copiée dans l'image** (pas un volume) → toute modif
|
||
requiert `docker compose build erpnext-backend && docker compose up -d`.
|
||
- ⚠️ Piège : `sites/apps.txt` peut se retrouver avec une ligne parasite
|
||
`apps.txt` qui casse `bench` (`ModuleNotFoundError: No module named 'apps'`) —
|
||
nettoyer si ça arrive.
|
||
|
||
### Build & déploiement
|
||
|
||
```bash
|
||
cd /opt/erpnext/custom
|
||
docker compose build erpnext-backend
|
||
docker compose up -d --force-recreate erpnext-backend
|
||
```
|
||
|
||
---
|
||
|
||
## 6. UI Ops — aperçu client
|
||
|
||
### Fonctionnalité
|
||
|
||
Onglet « Aperçu client » dans le volet de détail facture
|
||
(`apps/ops/src/components/shared/detail-sections/InvoiceDetail.vue`).
|
||
|
||
- `q-tabs` : **Détails** (champs grid) ↔ **Aperçu client** (iframe PDF).
|
||
- Iframe pointe sur `gigafibre_utils.api.invoice_pdf?name=…` → rendu Chrome
|
||
pixel-parfait identique au PDF client.
|
||
|
||
### Particularité Vue scoped
|
||
|
||
Les styles `.modal-field-grid / .mf / .mf-label` définis dans `DetailModal.vue`
|
||
avec `scoped` **ne cascadent pas** vers les composants enfants montés via
|
||
`<component :is>`. Il faut les **dupliquer** dans le `<style scoped>` du composant
|
||
enfant (contre-intuitif — documenter pour les nouveaux devs).
|
||
|
||
---
|
||
|
||
## 7. Configuration & secrets
|
||
|
||
Toutes les clés vivent dans `/opt/erpnext/custom/erpnext/sites/common_site_config.json`
|
||
(montage bind du volume `sites` sur le conteneur `erpnext-backend`).
|
||
|
||
### Clés requises
|
||
|
||
| Clé | Valeur / format | Utilisation |
|
||
|---|---|---|
|
||
| `gigafibre_pay_secret` | 64 hex | HMAC signing tokens + referral codes |
|
||
| `gigafibre_pay_host` | `https://client.gigafibre.ca/pay-public` | Base URL QR/magic-link |
|
||
| `gigafibre_stripe_secret_key` | `sk_live_…` | Stripe Checkout API |
|
||
| `gigafibre_stripe_webhook_secret` | `whsec_…` | **TODO** — webhook signature verify |
|
||
| `twilio_account_sid` | `AC…` | SMS magic-link |
|
||
| `twilio_auth_token` | (hex) | SMS auth |
|
||
| `twilio_from_number` | `+14382313838` | SMS from |
|
||
| `chromium_path` | `/usr/bin/chromium` | PDF generator |
|
||
|
||
### Commande pour poser une clé
|
||
|
||
```bash
|
||
docker exec erpnext-backend-1 bench set-config -g <key> "<value>"
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Points connus / TODO
|
||
|
||
### ⚠️ TODO bloquants
|
||
|
||
- [ ] **Webhook Stripe** : enregistrer un second endpoint pointant vers
|
||
`https://client.gigafibre.ca/api/method/gigafibre_utils.api.stripe_webhook`
|
||
dans le dashboard Stripe, récupérer le `whsec_…`, poser via
|
||
`bench set-config -g gigafibre_stripe_webhook_secret`.
|
||
|
||
### Améliorations suggérées
|
||
|
||
- [ ] **Inverse lookup parrainage** : `customer_from_referral_code(code)` pour
|
||
appliquer le crédit 50 $ automatiquement à la souscription du parrain.
|
||
- [ ] **Rate-limit** sur `request_magic_link` (actuellement protégé par last4
|
||
seulement — OK pour l'abus téléphone, pas pour énumération d'invoices).
|
||
- [ ] **Refresh token** côté Traccar : le token généré expire le 2031-04-17 ;
|
||
prévoir rotation automatique (script cron + API `/api/session/token`).
|
||
- [ ] **Archivage local des PDFs** : stocker dans `File Doctype` pour historique
|
||
et éviter de re-rendre via Chromium à chaque affichage.
|
||
|
||
### Pièges connus
|
||
|
||
- `frappe.response.type = "binary"` ⇒ Acrobat échoue à ouvrir le fichier
|
||
téléchargé → **toujours utiliser `"pdf"`** pour les PDFs.
|
||
- Postgres v16 + ERPNext : bugs `GROUP BY`/`HAVING`/double-quotes —
|
||
patches dans `feedback_erpnext_postgres.md`.
|
||
- Script `bench execute` échoue sur `gigafibre_utils.api.invoice_pdf` hors
|
||
contexte web (Jinja appelle `frappe.call` qui requiert `frappe.local.request`).
|
||
Tester via HTTP.
|
||
- Traccar token ≠ FCM token — les `APA91b…` sont pour Firebase push, pas pour
|
||
l'API Traccar (cf. conversation 2026-04-17).
|
||
|
||
---
|
||
|
||
## Fichiers à connaître (index rapide)
|
||
|
||
| Fichier | Rôle |
|
||
|---|---|
|
||
| `/opt/erpnext/custom/Dockerfile` | Image ERPNext custom |
|
||
| `/opt/erpnext/custom/gigafibre_utils/gigafibre_utils/api.py` | **823 lignes** — toutes les méthodes whitelisted |
|
||
| `/opt/erpnext/custom/gigafibre_utils/gigafibre_utils/www/pay-public.html` | Landing publique |
|
||
| `/opt/traefik/dynamic/pay-public.yml` | Carve-out Authentik |
|
||
| `scripts/migration/setup_invoice_print_format.py` | Install/update du Print Format |
|
||
| `scripts/migration/import_invoices.py` | Import legacy → ERPNext |
|
||
| `scripts/migration/backfill_service_location.py` | Backfill item↔location 1,6 M lignes |
|
||
| `scripts/migration/test_jinja_render.py` | Rendu local pour comparaison pixel |
|
||
| `apps/ops/src/components/shared/detail-sections/InvoiceDetail.vue` | Onglet Aperçu client |
|
||
| `services/targo-hub/lib/traccar.js` | Proxy Traccar (Bearer token) |
|