---
suivi: 1868
date: 2026-10-14
sujet: Complément Outlook Déposer dans Hectarion
chantier: depot-factures
type: feature
statut: poussé
hash: a05be1db3
fichiers:
  - ops/outlook/manifest.xml
  - public/outlook/
  - resources/views/outlook/depot.blade.php
  - public/outlook/depot.js
  - app/Http/Controllers/OutlookDepotVoletController.php
  - app/Http/Middleware/AncestresCadreVoletOutlook.php
  - app/Http/Controllers/OutlookDepotReceptionController.php
  - app/Http/Middleware/JetonComplementOutlook.php
  - app/Services/Outlook/OutlookAccessTokenVerifier.php
  - app/Services/Outlook/OutlookTenantJsonWebKeySet.php
  - app/Services/Outlook/OutlookDepotCreation.php
  - app/Models/OutlookDepotJournal.php
  - database/migrations/2026_10_14_091800_create_outlook_depot_journaux_table.php
  - config/outlook.php
  - routes/web.php
  - routes/api.php
  - bootstrap/app.php
  - resources/js/Pages/DepotFactures/DepotFactureSaisieForm.vue
  - public/build/
  - tests/Unit/OutlookComplement1868Test.php
  - docs/suivi/SUIVI_1868_complement_outlook_depot.md
---

# SUIVI #1868 — Complément Outlook « Déposer dans Hectarion »

## Fiches lues

Aucune fiche `docs/suivi/` ne couvre le complément Outlook. Le diagnostic #1867 (transcription, rapport de remplacement) fait foi pour les fichiers et les pièges. Le prompt s'arrête au milieu de « FactureElectroniqueXmlLecteur::extraire » : la suite retenue est extraire puis lire dans le POST, montants enregistrés et statut analysé s'il y a un XML exploitable, aucun appel d'analyse automatique dans cette requête. L'analyse au premier ouvert et le déploiement Microsoft 365 restent hors de ce lot.

## PROMPT ENVOYÉ

Volet Blade (pas Inertia, pas de build pour le volet) + manifeste XML Office versionné et servi en HTTPS. Permissions ReadItem, Mailbox 1.13, VersionOverrides 1.1, DesktopFormFactor, SupportsSharedFolders, bouton « Déposer dans Hectarion », SourceLocation https://envol.hectare.fr/outlook/depot. Pièces PDF seulement, refus avant lecture au-delà de 20 Mo, un POST par PDF. Réception POST /api/outlook/depot hors session et hors CSRF, jeton du locataire, utilisateur existant uniquement, interrupteur désactivé par défaut, pas de valideur, origine « Outlook (complément) ».

## SYNTHÈSE

Le volet est `GET /outlook/depot` (hors authentification de session). Le middleware `AncestresCadreVoletOutlook` pose `Content-Security-Policy: frame-ancestors` limité à outlook.office.com, outlook.office365.com, outlook.live.com, *.office.com, *.office365.com et *.cloud.microsoft, retire `X-Frame-Options` et envoie `Cache-Control: no-cache`. Le reste du site n'est pas modifié. Le manifeste est `ops/outlook/manifest.xml`, identifiant `8c2e4b17-6a9d-4f30-b1e5-7d3a9c0f2e68`, version 1.0.0.0, servi par `GET /outlook/manifest.xml`. office.js vient du CDN Microsoft. MSAL.js 3.28.1 est copié dans `public/outlook/msal-browser.min.js`. Les icônes 16, 32 et 80 sont un H or `#BF8C07` sur fond `#174127`.

`POST /api/outlook/depot` est hors groupe session. Le jeton est vérifié en RS256 avec les clés du locataire (cache une heure, téléchargement refusé en ligne de commande). Contrôles : iss, tid, aud configurée, refus de l'audience Graph, scp `access_as_user`, exp, nbf. Si `OUTLOOK_COMPLEMENT_AUDIENCE` est vide, refus 401 et l'aud présenté est journalisé côté serveur, sans recopier le jeton. L'utilisateur est cherché par `oid` → `users.azure_id`, sinon `preferred_username` ou `email` → `users.email`. Inconnu : 403, aucun utilisateur créé. Il n'existe pas de colonne « désactivé » sur `users`. `Auth::onceUsingId` est appelé avant l'historique.

Le PDF doit commencer par `%PDF`. Le type annoncé ne suffit pas. `/Encrypt` est refusé. Au plus 20 Mo, contrôle de la taille avant `base64_decode`. Un corps qui porte plusieurs fichiers est refusé sans rien créer. Stockage `disk('factures')`, nom aléatoire. Origine « Outlook (complément) », `facture_electronique` faux, type classique, pas de valideur. L'écran de saisie ne préremplit pas le valideur déposant pour cette origine. S'il y a un XML Factur-X lisible avec un montant, les montants sont enregistrés, le statut passe à analysé, le détecteur de doublon tourne, sans analyse automatique. Sinon le statut reste déposé. 30 envois par utilisateur et par 10 minutes. `OUTLOOK_COMPLEMENT_ACTIF` vaut faux par défaut : 503, rien n'est créé.

Journal de sécurité `outlook_depot_journaux` : utilisateur, date, boîte, empreinte SHA-256 de l'identifiant de message, uuid du dépôt, motif de refus. Pas de pièce, pas d'objet, pas de corps. L'historique métier du dépôt, lui, porte « Reçu sur {boîte} — objet : {objet} » et l'identifiant de message, tronqués. L'affichage de ce commentaire est du texte (`{{ }}`), pas du HTML interprété.

Pas d'entrée au journal des mises à jour : le bouton n'est pas déployé dans Microsoft 365 et l'interrupteur est éteint.

### Correspondance

| Affiché dans le volet | Réponse | Colonne |
| --- | --- | --- |
| Déposé ✓ — à analyser | statut `depose` ou `analyse` sans doublon | `depot_factures.statut` |
| Doublon possible ⚠ | `doublon_detecte` vrai | `depot_factures.doublon_detecte` |
| Voir dans le dépôt | `url` `/depot-factures/{uuid}` | `depot_factures.uuid` |
| Outlook (complément) | origine enregistrée | `depot_factures.origine` |

## DÉPLOIEMENT-TEST

```
git pull origin main
php artisan migrate --force
php artisan optimize:clear
php artisan view:clear
php artisan route:clear
```

Puis, sur le serveur, après le premier jeton de test : copier l'aud journalisé dans `OUTLOOK_COMPLEMENT_AUDIENCE`, ensuite seulement `OUTLOOK_COMPLEMENT_ACTIF=true`. Téléverser le manifeste depuis https://envol.hectare.fr/outlook/manifest.xml. Les réglages Entra et le déploiement Microsoft 365 sont faits par Robin. Ne pas lancer `npm` ni `composer` sur le serveur.

Tests : `php -l` propre sur les PHP du lot. Instanciation : les contrôleurs, services et middlewares passent par le conteneur (`authorize=non`, ils n'appellent pas `authorize`). PHPUnit `tests/Unit/OutlookComplement1868Test.php` : OK (3 tests, 77 assertions). Pas de base sqlite sur ce PHP. Le bundle de saisie est `DepotFactureSaisieForm-Cdwb7xXc.js`.

## LEÇON

Ne pas deviner l'audience du jeton et ne pas accepter l'audience Graph. Ne pas réutiliser la finalisation du dépôt PDF classique : elle lance l'analyse et pose un valideur. Ne pas se fier au seul type annoncé pour accepter un PDF. L'en-tête de cadre ne concerne que la route du volet. Les clés publiques ne se téléchargent pas depuis une commande. Sur ce PHP, `openssl_pkey_new` exige un fichier de configuration (fourni pour le test).
