Aller au contenu

Module Pennylane

Objectif

Le module Pennylane connecte Solutravo a Pennylane pour gerer la facturation electronique et la synchronisation comptable.

Il couvre :

  • l'activation Pennylane par societe ;
  • la connexion OAuth ;
  • les preferences de synchronisation ;
  • l'envoi de devis ;
  • l'envoi de factures ;
  • l'envoi d'avoirs ;
  • l'envoi de fournisseurs et factures fournisseurs ;
  • l'historique des transmissions ;
  • les retries ;
  • la resynchronisation apres modification ;
  • la detection de conflits et doublons.

Branches a connaitre

Le module est reparti entre deux applications.

Application Solutravo PHP

  • Production : main
  • Staging : develop

Cette application contient l'interface utilisateur, les vues, les controleurs historiques, la generation PDF interne et la file locale de dirty jobs.

Backend d'integration Laravel

  • Production : integration/prod
  • Staging : integration/staging

Ce backend gere les appels API Pennylane, OAuth, les tokens, les logs, les migrations d'integration et la synchronisation automatique.

Vue d'ensemble architecture

Utilisateur Solutravo
        |
        v
Solutravo PHP
  - vues Pennylane
  - actions documents
  - dirty jobs locaux
  - generation PDF interne
        |
        | HTTP JSON vers INTEGRATION_API_URL
        v
Backend integration Laravel
  - OAuth Pennylane
  - sync documents
  - logs
  - conflicts
  - scheduler auto
        |
        | API Pennylane
        v
Pennylane

Point cle : Solutravo ne parle pas directement a Pennylane depuis l'application PHP historique. Il passe par le backend Laravel d'integration.

Points d'entree Solutravo PHP

Routes

Fichier :

  • app/core/Router.php

Routes a chercher :

  • facturation-electronique
  • api/internal/pennylane/pdf/([^/]+)

La route facturation-electronique affiche l'interface d'activation. La route PDF interne est appelee par le backend d'integration pour recuperer le PDF officiel d'un devis, d'une facture ou d'un avoir.

Activation et UI

Fichiers :

  • app/controllers/PennylaneActivationController.php
  • app/views/facturation-electronique.php
  • app/views/partials/pennylane-frontend.php

Responsabilites :

  • recuperer la societe active ;
  • afficher l'etat de preparation ;
  • afficher la connexion Pennylane ;
  • lancer OAuth ;
  • afficher les preferences ;
  • ouvrir les modales de statut document ;
  • synchroniser manuellement un document ;
  • afficher l'historique et les conflits.

Le partial pennylane-frontend.php expose l'objet JS :

window.SolutravoPennylaneUX

Fonctions importantes :

  • openDocumentModal(documentType, documentId)
  • syncDocument(documentType, documentId, button)
  • retryAttachments(documentType, documentId, button)
  • chargement status, readiness, settings
  • preview/apply sync runs
  • resolution de conflits
  • affichage logs

Controleurs documents

Fichiers :

  • app/controllers/DevisController.php
  • app/controllers/FactureController.php
  • app/controllers/AvoirController.php
  • app/controllers/AchatController.php

Ils instancient PennylaneDirtySyncService et appellent markDirty apres certains changements metiers.

Exemples de raisons dirty :

  • devis.sent
  • facture.sent
  • avoir.status_changed
  • supplier_invoice.updated

Verifier systematiquement que chaque action qui modifie un document deja synchronise declenche bien un dirty.

Dirty tracking cote Solutravo

Fichiers :

  • app/services/PennylaneDirtySyncService.php
  • app/cron/PennylaneDirtySyncCron.php
  • app/bd/pennylane-dirty-jobs.sql

Table :

pennylane_dirty_jobs

Colonnes principales :

  • dedupe_key
  • document_type
  • document_id
  • reason
  • status
  • attempts
  • last_error
  • next_retry_at
  • locked_at
  • created_at
  • updated_at

Statuts :

  • pending
  • processing
  • done
  • failed

Flux :

  1. Un document est modifie dans Solutravo.
  2. Le controleur appelle markDirty.
  3. Le service cree ou met a jour un job local deduplique.
  4. Le service appelle le backend Laravel :
POST /api/pennylane/{type}/{id}/dirty
  1. Si l'appel echoue, le job reste en attente avec retry.

Commande cron :

php app/cron/PennylaneDirtySyncCron.php 50

Point d'attention : la couverture dirty depend des appels places dans les controleurs. Une modification faite ailleurs peut ne pas etre transmise.

Generation PDF interne

Fichier :

  • app/controllers/InternalPennylanePdfController.php

Route :

/api/internal/pennylane/pdf/{document_type_path}?id={id}&societe_id={societe_id}

Types acceptes :

Path Type logique
generer-devis-pdf ou devis quote
generer-facture-pdf ou facture invoice
generer-avoir-pdf ou avoir credit_note

Protection :

  • header HTTP : X-Solutravo-Internal-Token
  • variable attendue : SOLUTRAVO_INTERNAL_PDF_TOKEN

Le backend Laravel utilise cette route via le template :

PENNYLANE_INVOICE_PDF_URL_TEMPLATE

Ne pas publier la valeur reelle du token ou l'URL interne complete si elle n'est pas publique.

Points d'entree backend Laravel

Routes

Fichier :

  • routes/api.php

Prefixe :

/api/pennylane

Endpoints principaux :

  • GET /callback
  • GET /status
  • GET /readiness
  • POST /activation
  • POST /connect-url
  • POST /disconnect
  • GET /settings
  • PUT /settings
  • POST /quotes/{id}/sync
  • POST /invoices/{id}/sync
  • POST /suppliers/{id}/sync
  • POST /supplier-invoices/{id}/sync
  • GET /quotes/{id}/status
  • GET /invoices/{id}/status
  • GET /suppliers/{id}/status
  • GET /supplier-invoices/{id}/status
  • GET /{type}/{id}/attachments/status
  • POST /{type}/{id}/attachments/retry
  • POST /{type}/{id}/dirty
  • GET /sync-logs
  • POST /sync-logs/{id}/retry
  • POST /sync-runs/preview
  • GET /sync-runs/{id}
  • POST /sync-runs/{id}/apply
  • GET /conflicts
  • POST /conflicts/{id}/resolve

Controleur Laravel

Fichier :

  • app/Http/Controllers/PennylaneV1Controller.php

Responsabilites :

  • resoudre la societe via societe_id ;
  • valider les entrees ;
  • exposer les endpoints JSON ;
  • router les types de documents ;
  • deleguer au service PennylaneV1Service.

Mappings importants :

quote            -> devis
invoice          -> factures
credit_note      -> factures avec type = avoir
supplier         -> fournisseurs
supplier_invoice -> achats

Service Laravel principal

Fichier :

  • app/Http/Services/PennylaneV1Service.php

Responsabilites :

  • construire l'URL OAuth ;
  • echanger le code OAuth ;
  • chiffrer et stocker les tokens ;
  • refresh token ;
  • provisioning ;
  • synchroniser devis, factures, avoirs, fournisseurs, factures fournisseurs ;
  • detecter les conflits ;
  • gerer les pieces jointes ;
  • marquer les documents dirty ;
  • resynchroniser les documents dirty ;
  • gerer logs et erreurs ;
  • executer la synchronisation automatique.

Tables et migrations backend

Migrations importantes :

  • 2026_07_17_000001_add_pennylane_v1_integration_tables.php
  • 2026_07_30_000001_add_pennylane_collision_tracking_tables.php
  • 2026_07_30_000002_enable_pennylane_credit_note_sync.php
  • 2026_07_30_000003_add_credit_note_to_pennylane_sync_logs.php
  • 2026_07_30_000004_create_pennylane_attachment_links_table.php
  • 2026_08_28_000001_add_pennylane_dirty_tracking_to_documents.php
  • 2026_08_28_000002_add_pennylane_provisioning_tracking.php
  • 2026_08_31_000001_add_pennylane_supplier_purchase_sync.php
  • 2026_09_07_000001_enable_pennylane_purchase_sync_defaults.php

Tables dediees :

  • pennylane_connections
  • pennylane_sync_logs
  • pennylane_sync_runs
  • pennylane_document_links
  • pennylane_sync_conflicts
  • pennylane_attachment_links

Colonnes ajoutees aux tables metier :

  • id_pennylane
  • is_pennylane
  • pennylane_synced_at
  • pennylane_last_error
  • pennylane_dirty_at
  • pennylane_dirty_reason
  • pennylane_last_synced_hash
  • pennylane_sync_version
  • pennylane_sync_status selon les tables

Tables metier concernees :

  • devis
  • factures
  • clients
  • achats
  • fournisseurs

Flux principaux

Activation

  1. L'utilisateur ouvre facturation-electronique.
  2. Le frontend charge status, readiness, settings.
  3. Si la societe est prete, l'utilisateur lance l'activation.
  4. Le backend peut provisionner la societe Pennylane.
  5. Le backend retourne une URL OAuth.
  6. Pennylane redirige vers GET /api/pennylane/callback.
  7. Le backend stocke la connexion et redirige vers Solutravo.

Envoi d'un devis

  1. UI : POST /api/pennylane/quotes/{id}/sync.
  2. Backend charge le devis, le client et les lignes.
  3. Le client est cree ou retrouve dans Pennylane si necessaire.
  4. Le backend analyse les collisions.
  5. Le devis est cree via l'API Pennylane.
  6. Solutravo stocke l'ID Pennylane et un log.
  7. Les appendices peuvent etre envoyes apres la creation.

Envoi d'une facture

  1. UI : POST /api/pennylane/invoices/{id}/sync.
  2. Backend charge la facture.
  3. Il genere ou recupere le PDF via Solutravo.
  4. Le PDF est envoye comme piece jointe.
  5. La facture est importee dans Pennylane.
  6. Le document est marque synchronise.

Envoi d'un avoir

Un avoir est stocke dans factures, avec type = avoir.

Le backend le traite comme credit_note.

Regles importantes :

  • les montants doivent etre negatifs dans le payload Pennylane ;
  • la liaison a la facture d'origine est tentee apres import ;
  • l'echec de liaison peut etre non bloquant selon le cas.

Resynchronisation apres modification

  1. Solutravo marque le document dirty.
  2. Backend pose pennylane_dirty_at.
  3. Lors du prochain sync :
  4. si le document a un id_pennylane et est dirty, il est resynchronise ;
  5. pour une facture ou un avoir, le backend utilise un endpoint de mise a jour d'import ;
  6. pour un devis, il met a jour la ressource quote.
  7. Si succes, les champs dirty sont remis a null.

Synchronisation automatique

Fichiers :

  • app/Console/Commands/SyncAutomaticPennylane.php
  • app/Console/Kernel.php

Planification :

pennylane:sync-automatic --limit=20

Frequence observee :

toutes les 15 minutes

Commande manuelle utile :

php artisan pennylane:sync-automatic --limit=20
php artisan pennylane:sync-automatic --societe_id=123 --limit=20 --retry-errors

Le scheduler ne traite que les connexions :

  • en mode automatic ;
  • avec token present ;
  • non deconnectees.

Configuration

Variables ou constantes a connaitre sans publier leurs valeurs :

Solutravo PHP

  • INTEGRATION_API_URL
  • SOLUTRAVO_INTERNAL_PDF_TOKEN

Backend Laravel

  • PENNYLANE_CLIENT_ID
  • PENNYLANE_CLIENT_SECRET
  • PENNYLANE_PROVISIONING_CLIENT_ID
  • PENNYLANE_PROVISIONING_CLIENT_SECRET
  • PENNYLANE_PROVISIONING_SCOPES
  • PENNYLANE_PROVISIONING_USER_IDS
  • PENNYLANE_REDIRECT_URI
  • PENNYLANE_FRONTEND_REDIRECT_URL
  • PENNYLANE_INVOICE_PDF_URL_TEMPLATE
  • SOLUTRAVO_INTERNAL_PDF_TOKEN
  • PENNYLANE_DEFAULT_PURCHASE_LEDGER_ACCOUNT_ID
  • PENNYLANE_BASE_URL
  • PENNYLANE_OAUTH_BASE_URL
  • PENNYLANE_OAUTH_SCOPES

Bug connu : documents archives dans Pennylane

Symptome signale

Quand une facture ou un avoir est archive dans Pennylane, Solutravo doit quand meme pouvoir envoyer le document vers Pennylane.

Zone probable du probleme

Le service Laravel s'appuie sur :

  • id_pennylane
  • is_pennylane
  • pennylane_document_links
  • pennylane_dirty_at

Si un document local garde un id_pennylane, le service peut :

  • retourner "deja envoye" si le document n'est pas dirty ;
  • tenter une mise a jour du document distant si le document est dirty.

Le code doit donc gerer explicitement le cas "le document distant existe localement mais est archive ou non modifiable cote Pennylane".

Strategie de correction

  1. Reproduire le cas en staging avec une facture puis un avoir.
  2. Observer la reponse Pennylane lors du retry ou de la resync.
  3. Ajouter une detection explicite de document distant archive/non modifiable.
  4. Decider le comportement :
  5. recreer un document ;
  6. relier un nouveau document ;
  7. desarchiver si l'API le permet ;
  8. bloquer avec message utilisateur actionnable.
  9. Conserver l'historique dans pennylane_document_links et pennylane_sync_logs.
  10. Ajouter tests pour facture archivee, avoir archive, facture d'origine archivee.

Ne pas corriger en vidant simplement id_pennylane en base sans audit : cela peut creer des doublons et rendre l'historique incomprehensible.

Risques et points d'attention

Securite des routes backend

Les endpoints prennent societe_id. Il faut verifier que le backend ne fait pas confiance au client seul pour l'autorisation.

Routes sensibles :

  • synchronisation manuelle ;
  • changement de settings ;
  • deconnexion ;
  • dirty ;
  • retry ;
  • apply sync run ;
  • resolution de conflit.

Secrets

Ne jamais publier les valeurs des variables d'environnement. Si une valeur sensible a deja ete versionnee, il faut la retirer et la faire tourner.

PDF interne

La route PDF interne donne acces a des documents de facturation. Elle doit rester protegee par token et idealement par restriction reseau.

Avoirs

Les avoirs sont sensibles car ils partagent la table factures avec les factures. Verifier :

  • type = avoir ;
  • montants negatifs dans Pennylane ;
  • lien avec facture d'origine ;
  • statut de la facture d'origine.

Dirty incomplet

Toute modification de document synchronise doit marquer le document dirty. Auditer les chemins alternatifs de modification.

Tests recommandes

Backend Laravel

  • sync facture nouvelle ;
  • sync avoir nouveau ;
  • resync facture dirty ;
  • resync avoir dirty ;
  • document deja lie non dirty ;
  • document distant archive ;
  • update refuse par Pennylane ;
  • PDF interne indisponible ;
  • token OAuth expire ;
  • attachment duplicate ;
  • facture fournisseur sans piece jointe.

Solutravo PHP

  • route PDF refuse sans token ;
  • route PDF retourne un PDF pour devis/facture/avoir ;
  • dirty job cree apres modification ;
  • retry dirty job apres erreur ;
  • dedupe dirty job par document.

Scenario manuel minimal

  1. Activer Pennylane pour une societe de test.
  2. Envoyer un devis.
  3. Envoyer une facture.
  4. Envoyer un avoir.
  5. Modifier la facture et verifier dirty/resync.
  6. Archiver la facture dans Pennylane et tenter un nouvel envoi.
  7. Archiver l'avoir dans Pennylane et tenter un nouvel envoi.
  8. Verifier logs, liens, erreurs et UI.

Commandes utiles

Solutravo PHP

php -l app/services/PennylaneDirtySyncService.php
php -l app/controllers/InternalPennylanePdfController.php
php app/cron/PennylaneDirtySyncCron.php 50

Backend Laravel

php artisan migrate
php artisan pennylane:sync-automatic --limit=20
php artisan pennylane:sync-automatic --societe_id=123 --limit=20 --retry-errors
php artisan schedule:run

Checklist de reprise

  • [ ] Verifier les branches front/backend ciblees.
  • [ ] Verifier que les migrations Pennylane sont appliquees.
  • [ ] Verifier que INTEGRATION_API_URL pointe vers le bon backend.
  • [ ] Verifier que le template PDF pointe vers Solutravo.
  • [ ] Verifier le token PDF interne des deux cotes.
  • [ ] Verifier que le scheduler Laravel tourne.
  • [ ] Verifier que le cron dirty Solutravo tourne.
  • [ ] Reproduire le bug facture/avoir archive.
  • [ ] Ajouter tests avant correction.
  • [ ] Corriger sans supprimer l'historique de liens.

Questions ouvertes

  • Quel comportement produit exact attendre quand un document est archive dans Pennylane ?
  • L'API Pennylane permet-elle de mettre a jour un document archive ?
  • Faut-il recreer un document Pennylane avec le meme numero ?
  • Le lien credit note doit-il etre bloquant pour les avoirs ?
  • Quelle authentification protege les routes backend en production ?