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-electroniqueapi/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.phpapp/views/facturation-electronique.phpapp/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 :
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.phpapp/controllers/FactureController.phpapp/controllers/AvoirController.phpapp/controllers/AchatController.php
Ils instancient PennylaneDirtySyncService et appellent markDirty apres certains changements metiers.
Exemples de raisons dirty :
devis.sentfacture.sentavoir.status_changedsupplier_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.phpapp/cron/PennylaneDirtySyncCron.phpapp/bd/pennylane-dirty-jobs.sql
Table :
Colonnes principales :
dedupe_keydocument_typedocument_idreasonstatusattemptslast_errornext_retry_atlocked_atcreated_atupdated_at
Statuts :
pendingprocessingdonefailed
Flux :
- Un document est modifie dans Solutravo.
- Le controleur appelle
markDirty. - Le service cree ou met a jour un job local deduplique.
- Le service appelle le backend Laravel :
- Si l'appel echoue, le job reste en attente avec retry.
Commande cron :
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 :
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 :
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 :
Endpoints principaux :
GET /callbackGET /statusGET /readinessPOST /activationPOST /connect-urlPOST /disconnectGET /settingsPUT /settingsPOST /quotes/{id}/syncPOST /invoices/{id}/syncPOST /suppliers/{id}/syncPOST /supplier-invoices/{id}/syncGET /quotes/{id}/statusGET /invoices/{id}/statusGET /suppliers/{id}/statusGET /supplier-invoices/{id}/statusGET /{type}/{id}/attachments/statusPOST /{type}/{id}/attachments/retryPOST /{type}/{id}/dirtyGET /sync-logsPOST /sync-logs/{id}/retryPOST /sync-runs/previewGET /sync-runs/{id}POST /sync-runs/{id}/applyGET /conflictsPOST /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.php2026_07_30_000001_add_pennylane_collision_tracking_tables.php2026_07_30_000002_enable_pennylane_credit_note_sync.php2026_07_30_000003_add_credit_note_to_pennylane_sync_logs.php2026_07_30_000004_create_pennylane_attachment_links_table.php2026_08_28_000001_add_pennylane_dirty_tracking_to_documents.php2026_08_28_000002_add_pennylane_provisioning_tracking.php2026_08_31_000001_add_pennylane_supplier_purchase_sync.php2026_09_07_000001_enable_pennylane_purchase_sync_defaults.php
Tables dediees :
pennylane_connectionspennylane_sync_logspennylane_sync_runspennylane_document_linkspennylane_sync_conflictspennylane_attachment_links
Colonnes ajoutees aux tables metier :
id_pennylaneis_pennylanepennylane_synced_atpennylane_last_errorpennylane_dirty_atpennylane_dirty_reasonpennylane_last_synced_hashpennylane_sync_versionpennylane_sync_statusselon les tables
Tables metier concernees :
devisfacturesclientsachatsfournisseurs
Flux principaux¶
Activation¶
- L'utilisateur ouvre
facturation-electronique. - Le frontend charge
status,readiness,settings. - Si la societe est prete, l'utilisateur lance l'activation.
- Le backend peut provisionner la societe Pennylane.
- Le backend retourne une URL OAuth.
- Pennylane redirige vers
GET /api/pennylane/callback. - Le backend stocke la connexion et redirige vers Solutravo.
Envoi d'un devis¶
- UI :
POST /api/pennylane/quotes/{id}/sync. - Backend charge le devis, le client et les lignes.
- Le client est cree ou retrouve dans Pennylane si necessaire.
- Le backend analyse les collisions.
- Le devis est cree via l'API Pennylane.
- Solutravo stocke l'ID Pennylane et un log.
- Les appendices peuvent etre envoyes apres la creation.
Envoi d'une facture¶
- UI :
POST /api/pennylane/invoices/{id}/sync. - Backend charge la facture.
- Il genere ou recupere le PDF via Solutravo.
- Le PDF est envoye comme piece jointe.
- La facture est importee dans Pennylane.
- 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¶
- Solutravo marque le document dirty.
- Backend pose
pennylane_dirty_at. - Lors du prochain sync :
- si le document a un
id_pennylaneet est dirty, il est resynchronise ; - pour une facture ou un avoir, le backend utilise un endpoint de mise a jour d'import ;
- pour un devis, il met a jour la ressource quote.
- Si succes, les champs dirty sont remis a null.
Synchronisation automatique¶
Fichiers :
app/Console/Commands/SyncAutomaticPennylane.phpapp/Console/Kernel.php
Planification :
Frequence observee :
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_URLSOLUTRAVO_INTERNAL_PDF_TOKEN
Backend Laravel¶
PENNYLANE_CLIENT_IDPENNYLANE_CLIENT_SECRETPENNYLANE_PROVISIONING_CLIENT_IDPENNYLANE_PROVISIONING_CLIENT_SECRETPENNYLANE_PROVISIONING_SCOPESPENNYLANE_PROVISIONING_USER_IDSPENNYLANE_REDIRECT_URIPENNYLANE_FRONTEND_REDIRECT_URLPENNYLANE_INVOICE_PDF_URL_TEMPLATESOLUTRAVO_INTERNAL_PDF_TOKENPENNYLANE_DEFAULT_PURCHASE_LEDGER_ACCOUNT_IDPENNYLANE_BASE_URLPENNYLANE_OAUTH_BASE_URLPENNYLANE_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_pennylaneis_pennylanepennylane_document_linkspennylane_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¶
- Reproduire le cas en staging avec une facture puis un avoir.
- Observer la reponse Pennylane lors du retry ou de la resync.
- Ajouter une detection explicite de document distant archive/non modifiable.
- Decider le comportement :
- recreer un document ;
- relier un nouveau document ;
- desarchiver si l'API le permet ;
- bloquer avec message utilisateur actionnable.
- Conserver l'historique dans
pennylane_document_linksetpennylane_sync_logs. - 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¶
- Activer Pennylane pour une societe de test.
- Envoyer un devis.
- Envoyer une facture.
- Envoyer un avoir.
- Modifier la facture et verifier dirty/resync.
- Archiver la facture dans Pennylane et tenter un nouvel envoi.
- Archiver l'avoir dans Pennylane et tenter un nouvel envoi.
- 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_URLpointe 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 ?