{"openapi":"3.0.3","info":{"title":"ERP BTP - API de rapprochement \u00e0 3 voies","version":"1.0.0","description":"API du moteur de rapprochement \u00e0 3 voies (bon de commande \/ bon de livraison \/\nfacture) d'un ERP interne BTP.\n\n## Principe du contr\u00f4le\n\nUn paiement n'est autoris\u00e9 que pour la **portion** d'une facture couverte\nsimultan\u00e9ment par :\n\n1. une quantit\u00e9 **command\u00e9e** sur un bon de commande ouvert ;\n2. une quantit\u00e9 **r\u00e9ceptionn\u00e9e** sur un bon de livraison **accept\u00e9** ;\n3. un **prix unitaire** conforme au bon de commande, dans la tol\u00e9rance configur\u00e9e.\n\nToute portion non couverte n'ouvre aucun droit \u00e0 paiement. Tout \u00e9cart hors\ntol\u00e9rance est **signal\u00e9 pour arbitrage humain** - jamais accept\u00e9 ni rejet\u00e9\nsilencieusement.\n\n## Tra\u00e7abilit\u00e9\n\nChaque d\u00e9cision produit une ex\u00e9cution (`match_run`) immuable qui archive\n**qui** a d\u00e9cid\u00e9 (moteur ou utilisateur nomm\u00e9), **quand**, avec quelle\n**version du moteur**, quelles **tol\u00e9rances**, et - au niveau de chaque\nligne - la **preuve chiffr\u00e9e** (`evidence`) ayant servi au calcul.\nRejouer un rapprochement cr\u00e9e une nouvelle ex\u00e9cution ; aucune n'est\njamais \u00e9cras\u00e9e.\n\n## Convention de r\u00e9ponse\n\n- Ressource unique : `{ \"data\": { ... } }`\n- Collection pagin\u00e9e : `{ \"data\": [ ... ], \"links\": {...}, \"meta\": {...} }`\n- Erreur : `{ \"message\": string, \"error_code\": string, \"context\": object }`,\n  plus `errors` (par champ) pour les erreurs de validation.\n\n## Authentification\n\nToken Bearer (Laravel Sanctum), obtenu via `POST \/api\/login`.\nChaque endpoint exige en plus une permission fine (voir la description de\nl'endpoint). La s\u00e9paration des t\u00e2ches est volontaire : le comptable qui\nsaisit les factures ne peut pas arbitrer les \u00e9carts qu'elles d\u00e9clenchent.\n","contact":{"name":"\u00c9quipe ERP"},"license":{"name":"MIT"}},"servers":[{"url":"http:\/\/localhost:8000\/api","description":"Environnement local"},{"url":"{scheme}:\/\/{host}\/api","description":"Environnement param\u00e9trable","variables":{"scheme":{"default":"https","enum":["http","https"]},"host":{"default":"localhost:8000"}}}],"tags":[{"name":"Authentification","description":"Obtention et r\u00e9vocation des tokens d'acc\u00e8s."},{"name":"R\u00e9f\u00e9rentiel","description":"Fournisseurs et chantiers."},{"name":"Bons de commande","description":"Engagement d'achat - la r\u00e9f\u00e9rence en quantit\u00e9 et en prix."},{"name":"Bons de livraison","description":"R\u00e9ception physique - la preuve que la marchandise existe."},{"name":"Factures","description":"Cr\u00e9ances fournisseurs, rapproch\u00e9es d\u00e8s leur soumission."},{"name":"Rapprochement","description":"Ex\u00e9cutions du moteur et piste d'audit."},{"name":"\u00c9carts","description":"File de revue humaine des \u00e9carts d\u00e9tect\u00e9s."},{"name":"Paiements","description":"Autorisations de paiement, et constat de leur r\u00e8glement."},{"name":"Devises","description":"R\u00e9f\u00e9rentiel des devises et historique des taux de change."},{"name":"Audit","description":"Journal d'activit\u00e9, en lecture seule."},{"name":"Comptes","description":"Compte de l'utilisateur courant et administration des utilisateurs."},{"name":"Supervision","description":"Indicateurs de pilotage et sonde de sant\u00e9."}],"security":[{"bearerAuth":[]}],"paths":{"\/health":{"get":{"tags":["Supervision"],"summary":"Sonde de sant\u00e9","description":"V\u00e9rifie que l'application r\u00e9pond **et** que sa base est joignable.\nConsomm\u00e9e par les tests de d\u00e9ploiement et l'orchestrateur.\n","security":[],"responses":{"200":{"description":"Application et base op\u00e9rationnelles.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Health"}}}},"503":{"description":"Application d\u00e9marr\u00e9e mais base injoignable.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Health"}}}}}}},"\/login":{"post":{"tags":["Authentification"],"summary":"Ouvrir une session et obtenir un token","description":"Limit\u00e9 \u00e0 6 tentatives par minute et par IP. Le message d'erreur est\nidentique que le compte existe ou non, pour ne pas transformer le\nformulaire en oracle d'\u00e9num\u00e9ration de comptes.\n","security":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email","example":"controleur@erp.test"},"password":{"type":"string","format":"password","example":"password"},"device_name":{"type":"string","maxLength":100,"example":"web"}}}}}},"responses":{"200":{"description":"Token \u00e9mis.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"token":{"type":"string","example":"3|Xk9..."},"user":{"$ref":"#\/components\/schemas\/User"}}}}}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/logout":{"post":{"tags":["Authentification"],"summary":"R\u00e9voquer le token courant","responses":{"204":{"description":"Token r\u00e9voqu\u00e9."},"401":{"$ref":"#\/components\/responses\/Unauthenticated"}}}},"\/me":{"get":{"tags":["Authentification"],"summary":"Profil, r\u00f4les et permissions de l'utilisateur courant","responses":{"200":{"description":"Profil courant.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"}}},"patch":{"tags":["Comptes"],"summary":"Modifier ses propres informations","description":"Aucune permission requise : la route n'agit que sur l'appelant. Les\nr\u00f4les ne sont volontairement pas accept\u00e9s ici - personne ne s'auto-promeut.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ProfileInput"}}}},"responses":{"200":{"description":"Profil mis \u00e0 jour.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/me\/password":{"put":{"tags":["Comptes"],"summary":"Changer son mot de passe","description":"Le mot de passe actuel est exig\u00e9 bien que l'appelant soit d\u00e9j\u00e0\nauthentifi\u00e9 : sans lui, un token vol\u00e9 suffirait \u00e0 verrouiller le compte\nde sa victime. Les **autres** sessions sont ferm\u00e9es apr\u00e8s le changement,\nle token courant survit.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/PasswordChangeInput"}}}},"responses":{"200":{"description":"Mot de passe mis \u00e0 jour, autres sessions r\u00e9voqu\u00e9es.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"}}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/users":{"get":{"tags":["Comptes"],"summary":"Lister les comptes utilisateurs","description":"Permission requise : `users.view`.","parameters":[{"$ref":"#\/components\/parameters\/Search"},{"name":"role","in":"query","schema":{"type":"string","enum":["admin","buyer","warehouse","accountant","controller"]},"description":"Filtre sur le r\u00f4le attribu\u00e9."},{"$ref":"#\/components\/parameters\/Sort"},{"$ref":"#\/components\/parameters\/Direction"},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e de comptes.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/AdminUser"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"tags":["Comptes"],"summary":"Cr\u00e9er un compte","description":"Permission requise : `users.manage`. Au moins un r\u00f4le est exig\u00e9.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/UserInput"}}}},"responses":{"201":{"description":"Compte cr\u00e9\u00e9.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/AdminUser"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/users\/{user}":{"parameters":[{"name":"user","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Identifiant du compte."}],"get":{"tags":["Comptes"],"summary":"Consulter un compte","description":"Permission requise : `users.view`.","responses":{"200":{"description":"Compte.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/AdminUser"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"patch":{"tags":["Comptes"],"summary":"Modifier un compte","description":"Permission requise : `users.manage`. Un `password` absent ou `null`\nlaisse le mot de passe existant inchang\u00e9.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/UserUpdateInput"}}}},"responses":{"200":{"description":"Compte mis \u00e0 jour.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/AdminUser"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"tags":["Comptes"],"summary":"Supprimer un compte","description":"Permission requise : `users.manage`.\n\nDeux refus m\u00e9tier, tous deux en 409 : supprimer son propre compte\n(`cannot_delete_self`) et supprimer le dernier administrateur\n(`last_administrator`) - l'application deviendrait inadministrable.\n","responses":{"204":{"description":"Compte supprim\u00e9."},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Suppression refus\u00e9e (`cannot_delete_self`, `last_administrator`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}}},"\/roles":{"get":{"tags":["Comptes"],"summary":"Lister les r\u00f4les assignables et leurs permissions","description":"Permission requise : `users.view`. Sert \u00e0 montrer ce qu'un r\u00f4le\nautorise **avant** de l'attribuer.\n","responses":{"200":{"description":"R\u00f4les disponibles.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Role"}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/suppliers":{"get":{"tags":["R\u00e9f\u00e9rentiel"],"summary":"Lister les fournisseurs","description":"Permission requise : `procurement.view`.","parameters":[{"$ref":"#\/components\/parameters\/Search"},{"name":"is_active","in":"query","schema":{"type":"boolean"},"description":"Filtre sur les fournisseurs actifs."},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e de fournisseurs.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Supplier"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"tags":["R\u00e9f\u00e9rentiel"],"summary":"Cr\u00e9er un fournisseur","description":"Permission requise : `procurement.manage`.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SupplierInput"}}}},"responses":{"201":{"description":"Fournisseur cr\u00e9\u00e9.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Supplier"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/suppliers\/{supplier}":{"parameters":[{"name":"supplier","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Identifiant du fournisseur."}],"get":{"tags":["R\u00e9f\u00e9rentiel"],"summary":"Consulter un fournisseur","description":"Permission requise : `procurement.view`.","responses":{"200":{"description":"Fournisseur.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Supplier"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"patch":{"tags":["R\u00e9f\u00e9rentiel"],"summary":"Modifier un fournisseur","description":"Permission requise : `procurement.manage`.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/SupplierInput"}}}},"responses":{"200":{"description":"Fournisseur mis \u00e0 jour.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Supplier"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"tags":["R\u00e9f\u00e9rentiel"],"summary":"Supprimer un fournisseur","description":"Permission requise : `procurement.manage`.\n\nRefus\u00e9 d\u00e8s qu'un bon de commande ou une facture cite la fiche : le\nr\u00e9f\u00e9rentiel est ce qui rend une d\u00e9cision archiv\u00e9e relisible, et une\nfacture rapproch\u00e9e dont l'\u00e9metteur a disparu n'est plus explicable. Le\ngeste attendu dans ce cas est la **d\u00e9sactivation** (`is_active: false`),\nqui retire le fournisseur des listes de saisie sans toucher \u00e0\nl'historique.\n","responses":{"204":{"description":"Fournisseur supprim\u00e9."},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Fournisseur rattach\u00e9 \u00e0 des documents (`supplier_in_use`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}}},"\/projects":{"get":{"tags":["R\u00e9f\u00e9rentiel"],"summary":"Lister les chantiers","description":"Permission requise : `procurement.view`.","parameters":[{"$ref":"#\/components\/parameters\/Search"},{"name":"is_active","in":"query","schema":{"type":"boolean"}},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e de chantiers.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Project"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"tags":["R\u00e9f\u00e9rentiel"],"summary":"Cr\u00e9er un chantier","description":"Permission requise : `procurement.manage`.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ProjectInput"}}}},"responses":{"201":{"description":"Chantier cr\u00e9\u00e9.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Project"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/projects\/{project}":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["R\u00e9f\u00e9rentiel"],"summary":"Consulter un chantier","description":"Permission requise : `procurement.view`.","responses":{"200":{"description":"Chantier.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Project"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"patch":{"tags":["R\u00e9f\u00e9rentiel"],"summary":"Modifier un chantier","description":"Permission requise : `procurement.manage`.","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ProjectInput"}}}},"responses":{"200":{"description":"Chantier mis \u00e0 jour.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Project"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"tags":["R\u00e9f\u00e9rentiel"],"summary":"Supprimer un chantier","description":"Permission requise : `procurement.manage`.\n\nRefus\u00e9 d\u00e8s qu'un bon de commande porte le chantier : les montants\nagr\u00e9g\u00e9s par chantier perdraient leur r\u00e9f\u00e9rent. La d\u00e9sactivation joue le\nm\u00eame r\u00f4le sans casser l'historique.\n","responses":{"204":{"description":"Chantier supprim\u00e9."},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Chantier d\u00e9j\u00e0 engag\u00e9 (`project_in_use`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}}},"\/purchase-orders":{"get":{"tags":["Bons de commande"],"summary":"Lister les bons de commande","description":"Permission requise : `procurement.view`.","parameters":[{"$ref":"#\/components\/parameters\/Search"},{"name":"supplier_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"project_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","schema":{"$ref":"#\/components\/schemas\/PurchaseOrderStatus"}},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e de bons de commande.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/PurchaseOrder"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"tags":["Bons de commande"],"summary":"Cr\u00e9er un bon de commande","description":"Permission requise : `procurement.manage`.\n\nLes lignes sont num\u00e9rot\u00e9es automatiquement dans l'ordre de saisie : ce\nnum\u00e9ro est la r\u00e9f\u00e9rence stable cit\u00e9e ensuite dans les messages d'\u00e9cart.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/PurchaseOrderInput"}}}},"responses":{"201":{"description":"Bon de commande cr\u00e9\u00e9, au statut `open`.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/PurchaseOrder"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/purchase-orders\/{purchaseOrder}":{"parameters":[{"name":"purchaseOrder","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Bons de commande"],"summary":"Consulter un bon de commande et ses lignes","description":"Permission requise : `procurement.view`.","responses":{"200":{"description":"Bon de commande avec ses lignes.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/PurchaseOrder"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/delivery-notes":{"get":{"tags":["Bons de livraison"],"summary":"Lister les bons de livraison","description":"Permission requise : `receiving.view`.","parameters":[{"$ref":"#\/components\/parameters\/Search"},{"name":"purchase_order_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"supplier_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","schema":{"$ref":"#\/components\/schemas\/DeliveryNoteStatus"}},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e de bons de livraison.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/DeliveryNote"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"tags":["Bons de livraison"],"summary":"Enregistrer une r\u00e9ception","description":"Permission requise : `receiving.manage`.\n\nLe bon est cr\u00e9\u00e9 au statut `draft` : **il ne compte pas encore** comme\nmarchandise re\u00e7ue et n'ouvre aucun droit \u00e0 paiement. Le fournisseur est\nrepris du bon de commande et ne peut pas \u00eatre fourni par l'appelant.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/DeliveryNoteInput"}}}},"responses":{"201":{"description":"Bon de livraison enregistr\u00e9 au statut `draft`.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DeliveryNote"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"409":{"description":"Bon de commande cl\u00f4tur\u00e9 ou annul\u00e9 (`purchase_order_not_open`).\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"422":{"description":"Validation \u00e9chou\u00e9e, ou ligne rattach\u00e9e \u00e0 un autre bon de commande\n(`delivery_line_not_on_purchase_order`).\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ValidationError"}}}}}}},"\/delivery-notes\/{deliveryNote}":{"parameters":[{"name":"deliveryNote","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Bons de livraison"],"summary":"Consulter un bon de livraison","description":"Permission requise : `receiving.view`.","responses":{"200":{"description":"Bon de livraison avec ses lignes.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DeliveryNote"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/delivery-notes\/{deliveryNote}\/review":{"parameters":[{"name":"deliveryNote","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Bons de livraison"],"summary":"Contr\u00f4ler la r\u00e9ception (accepter ou refuser)","description":"Permission requise : `receiving.manage`.\n\n**Accepter** rend les quantit\u00e9s opposables au paiement, met \u00e0 jour\nl'avancement du bon de commande, et **relance automatiquement le\nrapprochement** de toutes les factures du m\u00eame bon de commande : une\nfacture re\u00e7ue avant la marchandise devient payable sans intervention.\n\nUn bon d\u00e9j\u00e0 contr\u00f4l\u00e9 ne peut plus changer de statut : rouvrir une\nquantit\u00e9 re\u00e7ue d\u00e9placerait le plafond de paiement d'une facture d\u00e9j\u00e0\nrapproch\u00e9e.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["accepted","rejected"]}}}}}},"responses":{"200":{"description":"R\u00e9ception contr\u00f4l\u00e9e.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DeliveryNote"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Bon d\u00e9j\u00e0 contr\u00f4l\u00e9 (`delivery_note_already_reviewed`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/invoices":{"get":{"tags":["Factures"],"summary":"Lister les factures","description":"Permission requise : `invoicing.view`.","parameters":[{"$ref":"#\/components\/parameters\/Search"},{"name":"supplier_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"purchase_order_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","schema":{"$ref":"#\/components\/schemas\/InvoiceStatus"}},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e de factures.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Invoice"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"tags":["Factures"],"summary":"Soumettre une facture (d\u00e9clenche le rapprochement)","description":"Permission requise : `invoicing.manage`.\n\nLa facture est **rapproch\u00e9e imm\u00e9diatement** : la r\u00e9ponse contient d\u00e9j\u00e0\nle verdict (`latest_match_run`) et, le cas \u00e9ch\u00e9ant, le montant autoris\u00e9\nau paiement (`payment_authorization`).\n\nDeux champs sont volontairement **refus\u00e9s en entr\u00e9e** :\n- `supplier_id`, d\u00e9duit du bon de commande - sinon la comparaison des\n  fournisseurs porterait sur une donn\u00e9e choisie par l'\u00e9metteur ;\n- `total_amount`, recalcul\u00e9 depuis les lignes - un en-t\u00eate qui annonce\n  un montant diff\u00e9rent de la somme de ses lignes est le sc\u00e9nario de\n  fraude le plus banal.\n\nUne ligne peut \u00eatre envoy\u00e9e **sans** `purchase_order_line_id` : elle est\naccept\u00e9e puis signal\u00e9e comme \u00e9cart, plut\u00f4t que rejet\u00e9e \u00e0 la saisie - une\nfraude refus\u00e9e \u00e0 la porte ne laisse aucune trace.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/InvoiceInput"}}}},"responses":{"201":{"description":"Facture enregistr\u00e9e et rapproch\u00e9e.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Invoice"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"409":{"description":"R\u00e9f\u00e9rence d\u00e9j\u00e0 soumise pour ce fournisseur (`duplicate_invoice`) -\nbarri\u00e8re anti-double paiement - ou bon de commande ferm\u00e9\n(`purchase_order_not_open`).\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"422":{"description":"Validation \u00e9chou\u00e9e, ou ligne rattach\u00e9e \u00e0 un autre bon de commande\n(`invoice_line_not_on_purchase_order`).\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ValidationError"}}}}}}},"\/invoices\/{invoice}":{"parameters":[{"$ref":"#\/components\/parameters\/InvoiceId"}],"get":{"tags":["Factures"],"summary":"Consulter une facture, son dernier rapprochement et son autorisation","description":"Permission requise : `invoicing.view`.","responses":{"200":{"description":"Facture compl\u00e8te.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Invoice"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/invoices\/{invoice}\/cancel":{"parameters":[{"$ref":"#\/components\/parameters\/InvoiceId"}],"post":{"tags":["Factures"],"summary":"Annuler une facture","description":"Permission requise : `invoicing.manage`.\n\nR\u00e9voque l'autorisation de paiement en cours et **lib\u00e8re les quantit\u00e9s**\nque la facture retenait sur le bon de commande : une facture\nrectificative pourra les consommer.\n","responses":{"200":{"description":"Facture annul\u00e9e.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Invoice"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Facture d\u00e9j\u00e0 annul\u00e9e (`invoice_already_cancelled`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}}},"\/invoices\/{invoice}\/pdf":{"parameters":[{"$ref":"#\/components\/parameters\/InvoiceId"}],"get":{"tags":["Factures"],"summary":"Exporter la facture en PDF","description":"Permission requise : `invoicing.view`.\n\nLe document porte la facture **et** le verdict de son rapprochement :\nmontant autoris\u00e9 au paiement, montant non couvert, nombre d'\u00e9carts\nouverts. Une facture imprim\u00e9e sans son contr\u00f4le ne dit pas si elle est\npayable - or c'est la question que se pose celui qui l'imprime.\n\nLe fichier est produit \u00e0 la vol\u00e9e, jamais stock\u00e9 : il est int\u00e9gralement\nd\u00e9riv\u00e9 de l'\u00e9tat courant, et l'archiver ferait exister deux v\u00e9rit\u00e9s dont\nl'une vieillirait d\u00e8s le prochain rapprochement.\n","responses":{"200":{"description":"Document PDF.","content":{"application\/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/invoices\/{invoice}\/currency":{"parameters":[{"$ref":"#\/components\/parameters\/InvoiceId"}],"patch":{"tags":["Factures"],"summary":"Changer la devise de r\u00e8glement","description":"Permission requise : `invoicing.manage`.\n\nLa devise n'est pas un d\u00e9tail d'affichage : c'est l'unit\u00e9 dans laquelle\nle prix factur\u00e9 est confront\u00e9 au prix command\u00e9, et donc celle du montant\nautoris\u00e9 au paiement. Le changement **rejoue donc le rapprochement** et\nproduit une nouvelle ex\u00e9cution - la r\u00e9ponse porte d\u00e9j\u00e0 le nouveau\nverdict.\n\nLes montants des lignes ne sont pas convertis : corriger la devise\ncorrige la fa\u00e7on dont la facture a \u00e9t\u00e9 lue, pas ce que le fournisseur a\n\u00e9crit dessus.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["currency"],"properties":{"currency":{"$ref":"#\/components\/schemas\/Currency"}}}}}},"responses":{"200":{"description":"Facture recharg\u00e9e, nouvelle ex\u00e9cution comprise.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Invoice"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Facture annul\u00e9e ou d\u00e9j\u00e0 r\u00e9gl\u00e9e\n(`invoice_currency_not_changeable`) : corriger la devise apr\u00e8s le\nvirement r\u00e9\u00e9crirait le sens d'un paiement d\u00e9j\u00e0 ex\u00e9cut\u00e9.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/invoices\/{invoice}\/match-runs":{"parameters":[{"$ref":"#\/components\/parameters\/InvoiceId"}],"get":{"tags":["Rapprochement"],"summary":"Historique des rapprochements d'une facture","description":"Permission requise : `matching.view`.\n\nPiste d'audit compl\u00e8te, du plus r\u00e9cent au plus ancien. Aucune ex\u00e9cution\nn'est jamais \u00e9cras\u00e9e ni supprim\u00e9e.\n","parameters":[{"name":"status","in":"query","schema":{"$ref":"#\/components\/schemas\/MatchStatus"}},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e d'ex\u00e9cutions.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/MatchRun"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"tags":["Rapprochement"],"summary":"Rejouer le rapprochement d'une facture","description":"Permission requise : `matching.run`.\n\nCr\u00e9e une **nouvelle** ex\u00e9cution attribu\u00e9e \u00e0 l'utilisateur appelant, et\nremplace l'autorisation de paiement pr\u00e9c\u00e9dente (l'ancienne passe au\nstatut `superseded` et reste consultable).\n","responses":{"201":{"description":"Nouvelle ex\u00e9cution enregistr\u00e9e.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/MatchRun"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Facture annul\u00e9e (`invoice_cancelled`) ou en litige apr\u00e8s arbitrage\nd\u00e9favorable (`invoice_disputed`).\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}}},"\/invoices\/{invoice}\/match-runs\/{matchRun}":{"parameters":[{"$ref":"#\/components\/parameters\/InvoiceId"},{"name":"matchRun","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Identifiant de l'ex\u00e9cution."}],"get":{"tags":["Rapprochement"],"summary":"D\u00e9tail d'une ex\u00e9cution, ligne par ligne","description":"Permission requise : `matching.view`.\n\nContient la preuve chiffr\u00e9e (`evidence`) de chaque ligne : quantit\u00e9s\ncommand\u00e9e \/ re\u00e7ue \/ d\u00e9j\u00e0 rapproch\u00e9e, seuils appliqu\u00e9s, prix compar\u00e9s.\nC'est la r\u00e9ponse \u00e0 \u00ab sur la base de quelles donn\u00e9es cette d\u00e9cision\na-t-elle \u00e9t\u00e9 prise \u00bb.\n","responses":{"200":{"description":"Ex\u00e9cution d\u00e9taill\u00e9e.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/MatchRun"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"description":"Ex\u00e9cution inconnue, ou n'appartenant pas \u00e0 cette facture.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}}},"\/match-exceptions":{"get":{"tags":["\u00c9carts"],"summary":"File de revue des \u00e9carts","description":"Permission requise : `matching.view`. Les \u00e9carts ouverts sont pr\u00e9sent\u00e9s en premier.","parameters":[{"name":"review_status","in":"query","schema":{"$ref":"#\/components\/schemas\/ReviewStatus"}},{"name":"type","in":"query","schema":{"$ref":"#\/components\/schemas\/DiscrepancyType"}},{"name":"severity","in":"query","schema":{"$ref":"#\/components\/schemas\/DiscrepancySeverity"}},{"name":"invoice_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"supplier_id","in":"query","schema":{"type":"string","format":"uuid"}},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e d'\u00e9carts.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/MatchException"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/match-exceptions\/{matchException}":{"parameters":[{"name":"matchException","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["\u00c9carts"],"summary":"Consulter un \u00e9cart et son contexte chiffr\u00e9","description":"Permission requise : `matching.view`.","responses":{"200":{"description":"\u00c9cart.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/MatchException"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/match-exceptions\/{matchException}\/review":{"parameters":[{"name":"matchException","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["\u00c9carts"],"summary":"Arbitrer un \u00e9cart","description":"Permission requise : `matching.review` - la plus sensible du syst\u00e8me,\nc'est le seul geste humain capable de d\u00e9bloquer un paiement.\n\n- **`approved`** : l'\u00e9cart est m\u00e9moris\u00e9 comme d\u00e9rogation et le\n  rapprochement est **rejou\u00e9**. C'est le moteur, et lui seul, qui\n  recalcule le montant payable ; l'ex\u00e9cution produite porte le nom du\n  relecteur. Un arbitrage ne peut donc jamais \u00ab \u00e9crire \u00bb un montant \u00e0\n  la main.\n- **`rejected`** : la facture passe en litige, l'autorisation en cours\n  est r\u00e9voqu\u00e9e, et le circuit automatique s'arr\u00eate (`match_run` vaut\n  `null`). La facture doit \u00eatre corrig\u00e9e \u00e0 la source.\n\nLes \u00e9carts non d\u00e9rogeables (fournisseur, devise, bon de commande ferm\u00e9,\nligne hors commande) continuent de bloquer m\u00eame apr\u00e8s un avis\nfavorable : voir `is_overridable`.\n\nLe motif (`note`) est **obligatoire** - un arbitrage sans justification\nest intra\u00e7able a posteriori.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["decision","note"],"properties":{"decision":{"type":"string","enum":["approved","rejected"]},"note":{"type":"string","minLength":3,"maxLength":2000,"example":"Hausse contractuelle du ciment valid\u00e9e par le service achats."}}}}}},"responses":{"200":{"description":"\u00c9cart arbitr\u00e9. `match_run` porte le rapprochement rejou\u00e9 en cas\nd'accord, et vaut `null` en cas de refus.\n","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"exception":{"$ref":"#\/components\/schemas\/MatchException"},"match_run":{"oneOf":[{"$ref":"#\/components\/schemas\/MatchRun"},{"type":"null"}]}}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"\u00c9cart d\u00e9j\u00e0 arbitr\u00e9 (`exception_already_reviewed`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/payment-authorizations":{"get":{"tags":["Paiements"],"summary":"Lister les autorisations de paiement","description":"Permission requise : `payments.view`.\n\nLecture seule : aucune autorisation ne peut \u00eatre cr\u00e9\u00e9e par appel direct,\nelle na\u00eet exclusivement d'un rapprochement.\n","parameters":[{"name":"invoice_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"supplier_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","schema":{"$ref":"#\/components\/schemas\/PaymentAuthorizationStatus"}},{"name":"settled","in":"query","schema":{"type":"boolean"},"description":"Absent, tout est renvoy\u00e9 ; `true` les autorisations r\u00e9gl\u00e9es, `false` celles restant \u00e0 r\u00e9gler."},{"$ref":"#\/components\/parameters\/Search"},{"$ref":"#\/components\/parameters\/Sort"},{"$ref":"#\/components\/parameters\/Direction"},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e d'autorisations.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/PaymentAuthorization"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/payment-authorizations\/{paymentAuthorization}\/settle":{"parameters":[{"name":"paymentAuthorization","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Identifiant de l'autorisation \u00e0 r\u00e9gler."}],"post":{"tags":["Paiements"],"summary":"Constater le r\u00e8glement d'une autorisation","description":"Permission requise : `payments.manage`.\n\nAucun montant n'est accept\u00e9 en entr\u00e9e : celui qui est r\u00e9gl\u00e9 est celui\nqu'a calcul\u00e9 le moteur. C'est ce qui emp\u00eache ce point d'entr\u00e9e de\ndevenir une porte d\u00e9rob\u00e9e vers un paiement non contr\u00f4l\u00e9.\n\nUne autorisation r\u00e9gl\u00e9e n'est plus ni rempla\u00e7able ni r\u00e9vocable : un\nrapprochement ult\u00e9rieur en cr\u00e9era une nouvelle sans effacer celle-ci.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/PaymentSettlementInput"}}}},"responses":{"200":{"description":"R\u00e8glement enregistr\u00e9.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/PaymentAuthorization"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Autorisation d\u00e9j\u00e0 r\u00e9gl\u00e9e (`payment_already_settled`) ou non active (`payment_not_active`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/invoices\/{invoice}\/payment-authorization":{"parameters":[{"$ref":"#\/components\/parameters\/InvoiceId"}],"get":{"tags":["Paiements"],"summary":"Autorisation active d'une facture","description":"Permission requise : `payments.view`.\n`data` vaut `null` lorsque rien n'est payable sur cette facture.\n","responses":{"200":{"description":"Autorisation active, ou `null`.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"oneOf":[{"$ref":"#\/components\/schemas\/PaymentAuthorization"},{"type":"null"}]}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/dashboard\/matching":{"get":{"tags":["Supervision"],"summary":"Indicateurs du contr\u00f4le","description":"Permission requise : `matching.view`.\n\nMontant aujourd'hui autoris\u00e9, montant bloqu\u00e9, charge de revue par type\net par gravit\u00e9. Recalcul\u00e9 \u00e0 la demande : un compteur d\u00e9normalis\u00e9 qui\nd\u00e9rive silencieusement serait pire que pas de compteur sur un tableau\nde bord de contr\u00f4le financier.\n","responses":{"200":{"description":"Indicateurs.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DashboardSummary"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/match-runs":{"get":{"tags":["Rapprochement"],"summary":"Registre global des rapprochements","description":"Permission requise : `matching.view`.\n\nToutes les ex\u00e9cutions du moteur, toutes factures confondues, de la plus\nr\u00e9cente \u00e0 la plus ancienne. Il n'existe **aucune** op\u00e9ration d'\u00e9criture\nsur cette ressource : une ex\u00e9cution est immuable. Corriger un\nrapprochement se fait en le rejouant\n(`POST \/invoices\/{invoice}\/match-runs`), ce qui produit une nouvelle\nex\u00e9cution sans effacer la pr\u00e9c\u00e9dente.\n","parameters":[{"$ref":"#\/components\/parameters\/Search"},{"name":"invoice_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Restreint aux ex\u00e9cutions d'une facture."},{"name":"supplier_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Restreint aux ex\u00e9cutions des factures d'un fournisseur."},{"name":"status","in":"query","schema":{"$ref":"#\/components\/schemas\/MatchStatus"}},{"name":"trigger","in":"query","schema":{"$ref":"#\/components\/schemas\/MatchTrigger"}},{"name":"actor_type","in":"query","schema":{"$ref":"#\/components\/schemas\/ActorType"}},{"$ref":"#\/components\/parameters\/Sort"},{"$ref":"#\/components\/parameters\/Direction"},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e d'ex\u00e9cutions.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/MatchRun"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/match-runs\/{matchRun}":{"parameters":[{"name":"matchRun","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Identifiant de l'ex\u00e9cution."}],"get":{"tags":["Rapprochement"],"summary":"Consulter une ex\u00e9cution","description":"Permission requise : `matching.view`.\n\nSert l'ex\u00e9cution compl\u00e8te : r\u00e9sultat ligne \u00e0 ligne, preuve chiffr\u00e9e\n(`evidence`), tol\u00e9rances et taux fig\u00e9s au moment de la d\u00e9cision.\n","responses":{"200":{"description":"Ex\u00e9cution du moteur.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/MatchRun"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/currencies":{"get":{"tags":["Devises"],"summary":"R\u00e9f\u00e9rentiel des devises","description":"Permission requise : `currencies.view`.\n\nDevises support\u00e9es, leur nombre de d\u00e9cimales, la devise par d\u00e9faut des\ndocuments et la devise d'agr\u00e9gation du pilotage. Servi par l'API plut\u00f4t\nque recopi\u00e9 c\u00f4t\u00e9 client : le nombre de d\u00e9cimales n'est pas cosm\u00e9tique,\net deux arrondis divergents afficheraient deux montants diff\u00e9rents pour\nla m\u00eame autorisation de paiement.\n","responses":{"200":{"description":"R\u00e9f\u00e9rentiel.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CurrencyReference"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/exchange-rates":{"get":{"tags":["Devises"],"summary":"Lister les taux de change","description":"Permission requise : `currencies.view`.\n\nSeul le sens `base \u2192 quote` est stock\u00e9 ; l'inverse et la triangulation\n(USD \u2192 XOF par l'euro) sont calcul\u00e9s. Deux lignes sym\u00e9triques qui\ndivergeraient d'un milli\u00e8me produiraient des rapprochements diff\u00e9rents\nselon le sens de lecture.\n","parameters":[{"$ref":"#\/components\/parameters\/Search"},{"name":"base_currency","in":"query","schema":{"$ref":"#\/components\/schemas\/Currency"}},{"name":"quote_currency","in":"query","schema":{"$ref":"#\/components\/schemas\/Currency"}},{"name":"source","in":"query","schema":{"$ref":"#\/components\/schemas\/ExchangeRateSource"}},{"$ref":"#\/components\/parameters\/Sort"},{"$ref":"#\/components\/parameters\/Direction"},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e de taux.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/ExchangeRateQuote"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"tags":["Devises"],"summary":"Enregistrer un taux","description":"Permission requise : `currencies.manage`.\n\nUn taux vaut \u00e0 partir de sa date d'effet. Publier le cours du jour se\nfait en **ajoutant** une ligne, jamais en modifiant la pr\u00e9c\u00e9dente :\nc'est la superposition des dates d'effet qui permet de rejouer \u00e0\nl'identique un rapprochement ancien. La source `fixed_peg` est refus\u00e9e\nen saisie - une parit\u00e9 r\u00e9glementaire n'est pas une cotation.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ExchangeRateInput"}}}},"responses":{"201":{"description":"Taux enregistr\u00e9.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ExchangeRateQuote"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/exchange-rates\/{exchangeRate}":{"parameters":[{"name":"exchangeRate","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Identifiant du taux."}],"get":{"tags":["Devises"],"summary":"Consulter un taux","description":"Permission requise : `currencies.view`.","responses":{"200":{"description":"Taux de change.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ExchangeRateQuote"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"patch":{"tags":["Devises"],"summary":"Corriger un taux","description":"Permission requise : `currencies.manage`.\n\nCorrige une saisie fautive. La paire n'est pas modifiable : changer la\ndevise d'une ligne existante r\u00e9\u00e9crirait l'historique d'une *autre*\npaire.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ExchangeRateUpdateInput"}}}},"responses":{"200":{"description":"Taux mis \u00e0 jour.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ExchangeRateQuote"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Parit\u00e9 fixe r\u00e9glementaire (`fixed_peg_not_editable`) : la laisser\nr\u00e9\u00e9crire par une saisie humaine fausserait silencieusement tous les\nrapprochements pass\u00e9s qui s'y r\u00e9f\u00e8rent.\n","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"tags":["Devises"],"summary":"Supprimer un taux","description":"Permission requise : `currencies.manage`.","responses":{"204":{"description":"Taux supprim\u00e9."},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"409":{"description":"Parit\u00e9 fixe r\u00e9glementaire (`fixed_peg_not_editable`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}}},"\/audit-logs":{"get":{"tags":["Audit"],"summary":"Journal d'audit","description":"Permission requise : `audit.view`.\n\nQui a chang\u00e9 quoi, quand, sur quel objet. **Lecture seule sans\nexception** - il n'existe ni cr\u00e9ation, ni modification, ni purge par\nl'API, y compris pour un administrateur : un journal que l'on peut\n\u00e9diter n'atteste de rien.\n","parameters":[{"$ref":"#\/components\/parameters\/Search"},{"name":"event","in":"query","schema":{"type":"string","example":"updated"},"description":"Type d'\u00e9v\u00e8nement : `created`, `updated`, `deleted`."},{"name":"subject_type","in":"query","schema":{"type":"string","example":"App\\Models\\Invoice"},"description":"Classe de l'objet concern\u00e9."},{"name":"subject_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"causer_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Auteur du changement. Absent pour une d\u00e9cision du moteur."},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"$ref":"#\/components\/parameters\/Sort"},{"$ref":"#\/components\/parameters\/Direction"},{"$ref":"#\/components\/parameters\/Page"},{"$ref":"#\/components\/parameters\/PerPage"}],"responses":{"200":{"description":"Collection pagin\u00e9e d'entr\u00e9es de journal.","content":{"application\/json":{"schema":{"allOf":[{"$ref":"#\/components\/schemas\/PaginatedEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/AuditLog"}}}}]}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/audit-logs\/facets":{"get":{"tags":["Audit"],"summary":"Valeurs disponibles pour les filtres du journal","description":"Permission requise : `audit.view`.\n\nCalcul\u00e9es sur les donn\u00e9es r\u00e9elles plut\u00f4t que cod\u00e9es en dur : la liste\ndes types d'objets journalis\u00e9s d\u00e9pend de ce qui a effectivement \u00e9t\u00e9\nmodifi\u00e9.\n","responses":{"200":{"description":"Facettes.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"subject_types":{"type":"array","items":{"type":"string"}},"events":{"type":"array","items":{"type":"string"}}}}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/audit-logs\/{auditLog}":{"parameters":[{"name":"auditLog","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Identifiant de l'entr\u00e9e de journal."}],"get":{"tags":["Audit"],"summary":"Consulter une entr\u00e9e du journal","description":"Permission requise : `audit.view`.","responses":{"200":{"description":"Entr\u00e9e de journal.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/AuditLog"}}}}}},"401":{"$ref":"#\/components\/responses\/Unauthenticated"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Token Laravel Sanctum obtenu via `POST \/api\/login`, envoy\u00e9 dans\nl'en-t\u00eate `Authorization: Bearer {token}`.\n"}},"parameters":{"Page":{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1},"description":"Page demand\u00e9e."},"PerPage":{"name":"per_page","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":10},"description":"Taille de page."},"Search":{"name":"search","in":"query","schema":{"type":"string"},"description":"Recherche textuelle sur la r\u00e9f\u00e9rence ou le libell\u00e9."},"Sort":{"name":"sort","in":"query","schema":{"type":"string"},"description":"Cl\u00e9 de tri, choisie dans la liste close accept\u00e9e par la ressource. Une\ncl\u00e9 inconnue est ignor\u00e9e plut\u00f4t que refus\u00e9e : le nom de colonne SQL\nn'est jamais construit depuis la requ\u00eate.\n"},"Direction":{"name":"direction","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"asc"},"description":"Sens du tri, appliqu\u00e9 \u00e0 `sort`."},"InvoiceId":{"name":"invoice","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Identifiant de la facture."}},"responses":{"Unauthenticated":{"description":"Token absent, invalide ou r\u00e9voqu\u00e9 (`unauthenticated`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"Forbidden":{"description":"Permission insuffisante (`forbidden`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"NotFound":{"description":"Ressource introuvable (`not_found`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"TooManyRequests":{"description":"Trop de tentatives, limitation active.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"ValidationError":{"description":"Donn\u00e9es invalides (`validation_failed`).","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ValidationError"}}}}},"schemas":{"MatchTrigger":{"type":"string","enum":["invoice_submitted","manual","delivery_accepted","exception_reviewed","currency_changed"],"description":"\u00c9v\u00e8nement \u00e0 l'origine d'une ex\u00e9cution du moteur. `currency_changed`\nappara\u00eet quand la devise de r\u00e8glement d'une facture a \u00e9t\u00e9 corrig\u00e9e : les\nprix ne se comparent plus dans la m\u00eame unit\u00e9, le montant autoris\u00e9 est\ndonc recalcul\u00e9.\n"},"CurrencyReference":{"type":"object","description":"R\u00e9f\u00e9rentiel des devises manipul\u00e9es par le circuit achats.","properties":{"currencies":{"type":"array","items":{"type":"object","properties":{"code":{"$ref":"#\/components\/schemas\/Currency"},"label":{"type":"string","example":"Franc CFA (BCEAO)"},"symbol":{"type":"string","example":"F CFA"},"decimals":{"type":"integer","example":0,"description":"Nombre de d\u00e9cimales r\u00e9elles de la devise (ISO 4217). Z\u00e9ro pour\nle franc CFA, qui n'a pas de sous-unit\u00e9 : afficher des centimes\ninventerait une pr\u00e9cision qui n'existe pas.\n"}}}},"sources":{"type":"array","items":{"type":"object","properties":{"value":{"$ref":"#\/components\/schemas\/ExchangeRateSource"},"label":{"type":"string","example":"Parit\u00e9 fixe"},"expires":{"type":"boolean","description":"Une parit\u00e9 fixe ne se p\u00e9rime pas ; un taux de march\u00e9, si."}}}},"default_currency":{"allOf":[{"$ref":"#\/components\/schemas\/Currency"}],"description":"Devise propos\u00e9e aux documents cr\u00e9\u00e9s sans devise explicite."},"base_currency":{"allOf":[{"$ref":"#\/components\/schemas\/Currency"}],"description":"Devise d'agr\u00e9gation du pilotage et des seuils absolus."}}},"ExchangeRateQuote":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"base_currency":{"$ref":"#\/components\/schemas\/Currency"},"quote_currency":{"$ref":"#\/components\/schemas\/Currency"},"rate":{"type":"number","format":"double","example":655.957,"description":"Multiplicateur : montant_en_quote = montant_en_base x rate."},"source":{"$ref":"#\/components\/schemas\/ExchangeRateSource"},"source_label":{"type":"string","example":"Parit\u00e9 fixe"},"is_editable":{"type":"boolean","description":"Faux pour une parit\u00e9 fixe r\u00e9glementaire. Servi pour que l'interface\nd\u00e9sactive ses commandes plut\u00f4t que d'attendre un 409.\n"},"effective_from":{"type":"string","format":"date","description":"Date \u00e0 partir de laquelle le taux s'applique."},"created_at":{"type":"string","format":"date-time","nullable":true}}},"ExchangeRateInput":{"type":"object","required":["base_currency","quote_currency","rate","source","effective_from"],"properties":{"base_currency":{"$ref":"#\/components\/schemas\/Currency"},"quote_currency":{"$ref":"#\/components\/schemas\/Currency"},"rate":{"type":"number","format":"double","exclusiveMinimum":true,"minimum":0},"source":{"type":"string","enum":["manual","provider"],"description":"`fixed_peg` est exclu : une parit\u00e9 r\u00e9glementaire ne se saisit pas."},"effective_from":{"type":"string","format":"date"}}},"ExchangeRateUpdateInput":{"type":"object","description":"La paire n'est pas modifiable - seule la cotation l'est.","properties":{"rate":{"type":"number","format":"double","exclusiveMinimum":true,"minimum":0},"source":{"type":"string","enum":["manual","provider"]},"effective_from":{"type":"string","format":"date"}}},"AuditLog":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"log_name":{"type":"string","nullable":true,"example":"default"},"description":{"type":"string","example":"updated"},"event":{"type":"string","nullable":true,"example":"updated"},"event_label":{"type":"string","example":"Modification"},"subject_type":{"type":"string","nullable":true,"example":"App\\Models\\Invoice"},"subject_label":{"type":"string","example":"Facture","description":"Libell\u00e9 m\u00e9tier du type d'objet, pour l'affichage."},"subject_id":{"type":"string","format":"uuid","nullable":true},"causer":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"causer_label":{"type":"string","example":"Systeme","description":"Nom de l'auteur, ou \u00ab Systeme \u00bb quand le changement vient du moteur\net non d'une personne.\n"},"properties":{"type":"object","description":"\u00c9tat avant (`old`) et apr\u00e8s (`attributes`) le changement, tel que le\njournal l'a archiv\u00e9.\n"},"created_at":{"type":"string","format":"date-time","nullable":true}}},"Error":{"type":"object","required":["message","error_code"],"properties":{"message":{"type":"string","description":"Message lisible, en fran\u00e7ais, destin\u00e9 \u00e0 l'utilisateur final.","example":"La facture FAC-2026-0003 est annul\u00e9e : elle ne peut plus \u00eatre rapproch\u00e9e."},"error_code":{"type":"string","description":"Code applicatif stable, destin\u00e9 au client. Contrairement au message,\nil ne change pas avec la formulation.\n","example":"invoice_cancelled"},"context":{"type":"object","additionalProperties":true,"description":"Donn\u00e9es utiles au client pour expliquer ou corriger l'erreur."}}},"ValidationError":{"allOf":[{"$ref":"#\/components\/schemas\/Error"},{"type":"object","properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}},"example":{"lines.0.unit_price":["Le prix unitaire doit \u00eatre strictement positif."]}}}}]},"PaginatedEnvelope":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}},"links":{"type":"object","properties":{"first":{"type":"string","nullable":true},"last":{"type":"string","nullable":true},"prev":{"type":"string","nullable":true},"next":{"type":"string","nullable":true}}},"meta":{"type":"object","properties":{"current_page":{"type":"integer","example":1},"last_page":{"type":"integer","example":4},"per_page":{"type":"integer","example":15},"total":{"type":"integer","example":52},"from":{"type":"integer","nullable":true},"to":{"type":"integer","nullable":true},"path":{"type":"string"}}}}},"Health":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]},"checks":{"type":"object","properties":{"database":{"type":"string","enum":["ok","unreachable"]}}},"matching_engine_version":{"type":"string","example":"1.0.0"},"error":{"type":"string","nullable":true},"timestamp":{"type":"string","format":"date-time"}}},"User":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Nadia Belkacem"},"email":{"type":"string","format":"email"},"roles":{"type":"array","items":{"type":"string","enum":["admin","buyer","warehouse","accountant","controller"]}},"permissions":{"type":"array","items":{"type":"string"},"example":["matching.view","matching.run","matching.review","payments.view"]}}},"AdminUser":{"allOf":[{"$ref":"#\/components\/schemas\/User"},{"type":"object","properties":{"created_at":{"type":"string","format":"date-time","nullable":true}}}]},"Role":{"type":"object","properties":{"name":{"type":"string","enum":["admin","buyer","warehouse","accountant","controller"]},"permissions":{"type":"array","items":{"type":"string"},"example":["invoicing.view","invoicing.manage","matching.run"]}}},"ProfileInput":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string","maxLength":255},"email":{"type":"string","format":"email","maxLength":255}}},"PasswordChangeInput":{"type":"object","required":["current_password","password","password_confirmation"],"properties":{"current_password":{"type":"string","format":"password"},"password":{"type":"string","format":"password","minLength":8},"password_confirmation":{"type":"string","format":"password"}}},"UserInput":{"type":"object","required":["name","email","password","roles"],"properties":{"name":{"type":"string","maxLength":255},"email":{"type":"string","format":"email","maxLength":255},"password":{"type":"string","format":"password","minLength":8},"roles":{"type":"array","minItems":1,"items":{"type":"string","enum":["admin","buyer","warehouse","accountant","controller"]}}}},"UserUpdateInput":{"type":"object","properties":{"name":{"type":"string","maxLength":255},"email":{"type":"string","format":"email","maxLength":255},"password":{"type":"string","format":"password","minLength":8,"nullable":true,"description":"Absent ou `null`, le mot de passe existant est conserv\u00e9."},"roles":{"type":"array","minItems":1,"items":{"type":"string","enum":["admin","buyer","warehouse","accountant","controller"]}}}},"PaymentSettlementInput":{"type":"object","required":["payment_reference"],"properties":{"payment_reference":{"type":"string","minLength":3,"maxLength":100,"description":"R\u00e9f\u00e9rence du virement, obligatoire - c'est elle qui permet de retrouver le paiement en banque."},"payment_method":{"type":"string","nullable":true,"enum":["transfer","check","card","cash","direct_debit"]},"settled_at":{"type":"string","format":"date","nullable":true,"description":"Date de valeur ; par d\u00e9faut, maintenant. Jamais dans le futur."}}},"Supplier":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"code":{"type":"string","example":"SUP-BETON"},"name":{"type":"string","example":"B\u00e9ton Express SAS"},"vat_number":{"type":"string","nullable":true},"email":{"type":"string","format":"email","nullable":true},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","nullable":true}}},"SupplierInput":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string","maxLength":50},"name":{"type":"string","maxLength":255},"vat_number":{"type":"string","maxLength":50,"nullable":true},"email":{"type":"string","format":"email","nullable":true},"is_active":{"type":"boolean"}}},"Project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"code":{"type":"string","example":"CH-A12"},"name":{"type":"string","example":"Chantier A12 - Viaduc Nord"},"client_name":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","nullable":true}}},"ProjectInput":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string","maxLength":50},"name":{"type":"string","maxLength":255},"client_name":{"type":"string","maxLength":255,"nullable":true},"is_active":{"type":"boolean"}}},"PurchaseOrderStatus":{"type":"string","enum":["draft","open","partially_received","fully_received","closed","cancelled"],"description":"Seuls `open`, `partially_received` et `fully_received` acceptent de\nnouveaux bons de livraison et de nouvelles factures.\n"},"PurchaseOrderLine":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"line_number":{"type":"integer","example":1},"item_code":{"type":"string","example":"CIM-42"},"description":{"type":"string"},"unit":{"type":"string","example":"sac"},"quantity_ordered":{"type":"number","format":"double","example":400},"unit_price":{"type":"number","format":"double","example":8.9},"ordered_amount":{"type":"number","format":"double","example":3560}}},"PurchaseOrder":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"reference":{"type":"string","example":"PO-2026-0001"},"status":{"$ref":"#\/components\/schemas\/PurchaseOrderStatus"},"status_label":{"type":"string","example":"Ouvert"},"currency":{"allOf":[{"$ref":"#\/components\/schemas\/Currency"}],"description":"Devise du bon de commande, reference contractuelle de comparaison des prix."},"ordered_at":{"type":"string","format":"date"},"notes":{"type":"string","nullable":true},"supplier":{"$ref":"#\/components\/schemas\/Supplier"},"project":{"$ref":"#\/components\/schemas\/Project"},"lines":{"type":"array","items":{"$ref":"#\/components\/schemas\/PurchaseOrderLine"}},"total_amount":{"type":"number","format":"double"},"lines_count":{"type":"integer"},"delivery_notes_count":{"type":"integer"},"invoices_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time","nullable":true}}},"PurchaseOrderInput":{"type":"object","required":["reference","supplier_id","project_id","ordered_at","lines"],"properties":{"reference":{"type":"string","maxLength":100,"example":"PO-2026-0001"},"supplier_id":{"type":"string","format":"uuid"},"project_id":{"type":"string","format":"uuid"},"currency":{"allOf":[{"$ref":"#\/components\/schemas\/Currency"}],"default":"EUR"},"ordered_at":{"type":"string","format":"date"},"notes":{"type":"string","maxLength":2000,"nullable":true},"lines":{"type":"array","minItems":1,"items":{"type":"object","required":["item_code","description","unit","quantity_ordered","unit_price"],"properties":{"item_code":{"type":"string","maxLength":100},"description":{"type":"string","maxLength":255},"unit":{"type":"string","maxLength":16,"example":"sac"},"quantity_ordered":{"type":"number","format":"double","exclusiveMinimum":true,"minimum":0},"unit_price":{"type":"number","format":"double","exclusiveMinimum":true,"minimum":0}}}}}},"DeliveryNoteStatus":{"type":"string","enum":["draft","accepted","rejected"],"description":"Seul `accepted` compte comme marchandise re\u00e7ue et ouvre un droit \u00e0\npaiement. C'est la parade principale contre les livraisons fictives.\n"},"DeliveryNoteLine":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"purchase_order_line_id":{"type":"string","format":"uuid"},"quantity_received":{"type":"number","format":"double"},"purchase_order_line":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"line_number":{"type":"integer"},"item_code":{"type":"string"},"description":{"type":"string"},"unit":{"type":"string"},"quantity_ordered":{"type":"number","format":"double"}}}}},"DeliveryNote":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"reference":{"type":"string","example":"BL-2026-0001"},"status":{"$ref":"#\/components\/schemas\/DeliveryNoteStatus"},"status_label":{"type":"string"},"counts_as_received":{"type":"boolean","description":"Vrai uniquement si le bon est accept\u00e9."},"received_at":{"type":"string","format":"date"},"notes":{"type":"string","nullable":true},"purchase_order_id":{"type":"string","format":"uuid"},"supplier":{"$ref":"#\/components\/schemas\/Supplier"},"lines":{"type":"array","items":{"$ref":"#\/components\/schemas\/DeliveryNoteLine"}},"lines_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time","nullable":true}}},"DeliveryNoteInput":{"type":"object","required":["reference","purchase_order_id","received_at","lines"],"properties":{"reference":{"type":"string","maxLength":100,"example":"BL-2026-0001"},"purchase_order_id":{"type":"string","format":"uuid"},"received_at":{"type":"string","format":"date"},"notes":{"type":"string","maxLength":2000,"nullable":true},"lines":{"type":"array","minItems":1,"items":{"type":"object","required":["purchase_order_line_id","quantity_received"],"properties":{"purchase_order_line_id":{"type":"string","format":"uuid"},"quantity_received":{"type":"number","format":"double","exclusiveMinimum":true,"minimum":0}}}}}},"InvoiceStatus":{"type":"string","enum":["received","under_review","partially_approved","approved","disputed","cancelled"],"description":"D\u00e9riv\u00e9 du dernier rapprochement : `approved` (tout rapproch\u00e9),\n`partially_approved` (portion payable), `under_review` (\u00e9cart \u00e0\narbitrer), `received` (rien de rapproch\u00e9), `disputed` (arbitrage\nd\u00e9favorable - plus de rapprochement automatique), `cancelled`.\n"},"InvoiceLine":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"line_number":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number","format":"double"},"unit_price":{"type":"number","format":"double"},"invoiced_amount":{"type":"number","format":"double"},"purchase_order_line_id":{"type":"string","format":"uuid","nullable":true,"description":"`null` = ligne hors commande, syst\u00e9matiquement signal\u00e9e."}}},"Invoice":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"reference":{"type":"string","example":"FAC-2026-0001"},"status":{"$ref":"#\/components\/schemas\/InvoiceStatus"},"status_label":{"type":"string"},"currency":{"allOf":[{"$ref":"#\/components\/schemas\/Currency"}],"description":"Devise de facturation, et devise du reglement. Elle peut differer\nde celle du bon de commande : le rapprochement convertit alors au\ntaux en vigueur a la date de la facture.\n"},"invoice_date":{"type":"string","format":"date"},"due_date":{"type":"string","format":"date","nullable":true},"total_amount":{"type":"number","format":"double","description":"Recalcul\u00e9 depuis les lignes, jamais fourni par l'appelant."},"supplier":{"$ref":"#\/components\/schemas\/Supplier"},"purchase_order_id":{"type":"string","format":"uuid"},"lines":{"type":"array","items":{"$ref":"#\/components\/schemas\/InvoiceLine"}},"lines_count":{"type":"integer"},"open_exceptions_count":{"type":"integer"},"latest_match_run":{"oneOf":[{"$ref":"#\/components\/schemas\/MatchRun"},{"type":"null"}]},"payment_authorization":{"oneOf":[{"$ref":"#\/components\/schemas\/PaymentAuthorization"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","nullable":true}}},"InvoiceInput":{"type":"object","required":["reference","purchase_order_id","invoice_date","lines"],"properties":{"reference":{"type":"string","maxLength":100,"example":"FAC-2026-0001"},"purchase_order_id":{"type":"string","format":"uuid"},"currency":{"allOf":[{"$ref":"#\/components\/schemas\/Currency"}],"description":"Par defaut, celle du bon de commande. La renseigner permet de\nfacturer dans une autre devise ; un taux doit alors exister pour\nla date de facture, faute de quoi le rapprochement est bloque et\nsignale (`missing_exchange_rate`).\n"},"invoice_date":{"type":"string","format":"date"},"due_date":{"type":"string","format":"date","nullable":true},"lines":{"type":"array","minItems":1,"items":{"type":"object","required":["description","quantity","unit_price"],"properties":{"purchase_order_line_id":{"type":"string","format":"uuid","nullable":true,"description":"Laisser `null` produit un \u00e9cart `missing_purchase_order_line`."},"description":{"type":"string","maxLength":255},"quantity":{"type":"number","format":"double","exclusiveMinimum":true,"minimum":0},"unit_price":{"type":"number","format":"double","minimum":0}}}}}},"MatchStatus":{"type":"string","enum":["matched","partially_matched","unmatched","exception"],"description":"`unmatched` est un \u00e9tat normal et transitoire (marchandise pas encore\narriv\u00e9e). `exception` signifie qu'un \u00e9cart attend une d\u00e9cision humaine.\n"},"ActorType":{"type":"string","enum":["system","user"],"description":"`system` : d\u00e9cision du moteur, sans intervention.\n`user` : rapprochement d\u00e9clench\u00e9 ou arbitr\u00e9 par un utilisateur nomm\u00e9.\n"},"MatchLineResult":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"invoice_line_id":{"type":"string","format":"uuid"},"purchase_order_line_id":{"type":"string","format":"uuid","nullable":true},"status":{"$ref":"#\/components\/schemas\/MatchStatus"},"status_label":{"type":"string"},"quantity_invoiced":{"type":"number","format":"double"},"quantity_matched":{"type":"number","format":"double","description":"Portion payable - min(command\u00e9, re\u00e7u) \u2212 d\u00e9j\u00e0 rapproch\u00e9 ailleurs."},"quantity_unmatched":{"type":"number","format":"double"},"unit_price_invoiced":{"type":"number","format":"double"},"unit_price_ordered":{"type":"number","format":"double","nullable":true},"price_variance_ratio":{"type":"number","format":"double","nullable":true,"description":"\u00c9cart relatif de prix (0.2 = +20 %)."},"matched_amount":{"type":"number","format":"double"},"evidence":{"type":"object","additionalProperties":true,"description":"Preuve chiffr\u00e9e archiv\u00e9e : quantit\u00e9s command\u00e9e \/ re\u00e7ue \/ d\u00e9j\u00e0\nrapproch\u00e9e, quantit\u00e9 disponible, seuils appliqu\u00e9s, prix compar\u00e9s,\nd\u00e9rogations retenues. Permet d'expliquer la d\u00e9cision des mois plus\ntard, m\u00eame apr\u00e8s \u00e9volution du bon de commande.\n","example":{"quantity_ordered":100,"quantity_received":60,"quantity_already_matched":20,"quantity_available_for_matching":40,"unit_price_ordered":10,"unit_price_invoiced":10,"price_tolerance_threshold":0.5,"price_within_tolerance":true}}}},"MatchRun":{"type":"object","description":"Ex\u00e9cution immuable du moteur - la r\u00e9ponse \u00e0 \u00ab qui, quand, sur quelles donn\u00e9es \u00bb.","properties":{"id":{"type":"string","format":"uuid"},"invoice_id":{"type":"string","format":"uuid"},"status":{"$ref":"#\/components\/schemas\/MatchStatus"},"status_label":{"type":"string"},"decided_by":{"type":"object","properties":{"actor_type":{"$ref":"#\/components\/schemas\/ActorType"},"actor_id":{"type":"string","format":"uuid","nullable":true},"label":{"type":"string","example":"Moteur de rapprochement v1.0.0"}}},"trigger":{"allOf":[{"$ref":"#\/components\/schemas\/MatchTrigger"}],"description":"\u00c9v\u00e9nement \u00e0 l'origine de l'ex\u00e9cution."},"engine_version":{"type":"string","example":"1.0.0"},"currency":{"allOf":[{"$ref":"#\/components\/schemas\/Currency"}],"description":"Devise de la facture - celle des montants ci-dessous et du r\u00e8glement."},"base_currency":{"allOf":[{"$ref":"#\/components\/schemas\/Currency"}],"description":"Devise de r\u00e9f\u00e9rence, utilis\u00e9e pour l'agr\u00e9gation."},"base_matched_amount":{"type":"number","format":"double","description":"Montant autoris\u00e9, converti en devise de r\u00e9f\u00e9rence."},"base_unmatched_amount":{"type":"number","format":"double"},"exchange_rate_snapshot":{"type":"object","nullable":true,"description":"Taux appliqu\u00e9s, fig\u00e9s avec la d\u00e9cision. Un montant converti sans son\ntaux est inv\u00e9rifiable : ce bloc permet de rejouer le calcul \u00e0\nl'identique des mois plus tard.\n","properties":{"invoice_currency":{"$ref":"#\/components\/schemas\/Currency"},"comparison_currency":{"$ref":"#\/components\/schemas\/Currency"},"base_currency":{"$ref":"#\/components\/schemas\/Currency"},"invoice_to_comparison":{"$ref":"#\/components\/schemas\/ExchangeRate"},"invoice_to_base":{"$ref":"#\/components\/schemas\/ExchangeRate"},"base_to_comparison":{"$ref":"#\/components\/schemas\/ExchangeRate"}}},"tolerance_snapshot":{"type":"object","description":"Copie fig\u00e9e des seuils appliqu\u00e9s, pour pouvoir rejouer la d\u00e9cision.","properties":{"price_ratio":{"type":"number","format":"double","example":0.01},"price_absolute":{"type":"number","format":"double","example":0.5},"quantity_ratio":{"type":"number","format":"double","example":0},"quantity_absolute":{"type":"number","format":"double","example":0},"currency":{"allOf":[{"$ref":"#\/components\/schemas\/Currency"}],"description":"Devise dans laquelle le seuil absolu a r\u00e9ellement \u00e9t\u00e9 appliqu\u00e9.\nLe seuil est configur\u00e9 en devise de r\u00e9f\u00e9rence puis converti dans\nla devise du bon de commande : 0,50 a un sens en euro, aucun en\nfranc CFA.\n"}}},"evaluated_at":{"type":"string","format":"date-time"},"invoiced_amount":{"type":"number","format":"double"},"matched_amount":{"type":"number","format":"double","description":"Montant autorisable au paiement."},"unmatched_amount":{"type":"number","format":"double"},"exception_count":{"type":"integer"},"line_results":{"type":"array","items":{"$ref":"#\/components\/schemas\/MatchLineResult"}},"exceptions":{"type":"array","items":{"$ref":"#\/components\/schemas\/MatchException"}},"created_at":{"type":"string","format":"date-time","nullable":true}}},"DiscrepancyType":{"type":"string","enum":["price_variance","quantity_over_ordered","quantity_over_delivered","missing_purchase_order_line","supplier_mismatch","missing_exchange_rate","purchase_order_not_open"],"description":"Seuls `price_variance` et `quantity_over_ordered` sont d\u00e9rogeables : un\nfournisseur erron\u00e9 ne se \u00ab valide \u00bb pas, la facture doit \u00eatre corrig\u00e9e.\n\n`missing_exchange_rate` signale une facture libell\u00e9e dans une autre\ndevise que son bon de commande **sans taux de change connu** \u00e0 la date\nde facture. Le syst\u00e8me ne devine jamais un taux : inventer un taux\nreviendrait \u00e0 inventer le montant qu'on s'appr\u00eate \u00e0 autoriser.\n"},"Currency":{"type":"string","enum":["EUR","USD","XOF"],"description":"Devises support\u00e9es. Attention au nombre de d\u00e9cimales : l'euro et le\ndollar en ont deux, le **franc CFA n'en a aucune** (pas de sous-unit\u00e9).\nUn montant XOF est toujours un entier ; l'afficher avec des centimes\nserait faux.\n\nLe franc CFA est arrim\u00e9 \u00e0 l'euro \u00e0 une parit\u00e9 **fixe et r\u00e9glementaire**\n(1 EUR = 655,957 XOF), et non \u00e0 un taux de march\u00e9.\n"},"ExchangeRateSource":{"type":"string","enum":["fixed_peg","manual","provider"],"description":"Provenance du taux appliqu\u00e9 : parit\u00e9 fixe r\u00e9glementaire, saisie\nmanuelle, ou fournisseur de cotations. Trac\u00e9e parce qu'elle n'engage pas\nla m\u00eame confiance.\n"},"ExchangeRate":{"type":"object","description":"Taux appliqu\u00e9 lors d'une conversion, archiv\u00e9 avec la d\u00e9cision.","properties":{"from":{"$ref":"#\/components\/schemas\/Currency"},"to":{"$ref":"#\/components\/schemas\/Currency"},"rate":{"type":"number","format":"double","description":"Multiplicateur : montant_en_`to` = montant_en_`from` \u00d7 rate.","example":655.957},"source":{"$ref":"#\/components\/schemas\/ExchangeRateSource"},"effective_from":{"type":"string","format":"date","nullable":true,"description":"Date d'effet du taux retenu."}}},"DiscrepancySeverity":{"type":"string","enum":["low","medium","high","critical"]},"ReviewStatus":{"type":"string","enum":["open","approved","rejected"]},"MatchException":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"match_run_id":{"type":"string","format":"uuid"},"invoice_id":{"type":"string","format":"uuid"},"invoice_line_id":{"type":"string","format":"uuid","nullable":true,"description":"`null` pour un \u00e9cart portant sur la facture enti\u00e8re."},"type":{"$ref":"#\/components\/schemas\/DiscrepancyType"},"type_label":{"type":"string","example":"\u00c9cart de prix"},"severity":{"$ref":"#\/components\/schemas\/DiscrepancySeverity"},"is_overridable":{"type":"boolean","description":"Un avis favorable peut-il d\u00e9bloquer le paiement de la ligne ?"},"message":{"type":"string","example":"Ligne 1 : prix unitaire factur\u00e9 612 contre 540 au bon de commande (\u00e9cart 72, tol\u00e9rance 5,4)."},"context":{"type":"object","additionalProperties":true,"description":"Chiffres de l'\u00e9cart, pour arbitrer sans rouvrir les trois documents."},"review_status":{"$ref":"#\/components\/schemas\/ReviewStatus"},"review_status_label":{"type":"string"},"review_note":{"type":"string","nullable":true},"reviewed_at":{"type":"string","format":"date-time","nullable":true},"reviewed_by":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"created_at":{"type":"string","format":"date-time","nullable":true}}},"PaymentAuthorizationStatus":{"type":"string","enum":["active","superseded","revoked"],"description":"`superseded` : remplac\u00e9e par un rapprochement plus r\u00e9cent. Une\nautorisation n'est jamais supprim\u00e9e.\n"},"PaymentAuthorization":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"invoice_id":{"type":"string","format":"uuid"},"match_run_id":{"type":"string","format":"uuid","description":"Ex\u00e9cution dont l'autorisation est issue - toute autorisation est tra\u00e7able jusqu'\u00e0 sa d\u00e9cision."},"amount":{"type":"number","format":"double","description":"Montant autoris\u00e9, dans la devise de facturation - celle du r\u00e8glement."},"currency":{"$ref":"#\/components\/schemas\/Currency"},"base_amount":{"type":"number","format":"double","description":"Contre-valeur en devise de r\u00e9f\u00e9rence, pour le pilotage et la comptabilit\u00e9."},"base_currency":{"$ref":"#\/components\/schemas\/Currency"},"exchange_rate":{"type":"number","format":"double","description":"Taux facture \u2192 devise de r\u00e9f\u00e9rence retenu par le rapprochement."},"status":{"$ref":"#\/components\/schemas\/PaymentAuthorizationStatus"},"status_label":{"type":"string"},"authorized_at":{"type":"string","format":"date-time"},"is_settled":{"type":"boolean","description":"Le reglement est un fait qui s'ajoute a l'autorisation, pas un statut qui la remplace."},"settled_at":{"type":"string","format":"date-time","nullable":true},"payment_reference":{"type":"string","nullable":true},"payment_method":{"type":"string","nullable":true},"settled_by":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}}}},"DashboardSummary":{"type":"object","properties":{"invoices":{"type":"object","additionalProperties":{"type":"integer"},"description":"Nombre de factures par statut, tous statuts pr\u00e9sents m\u00eame \u00e0 z\u00e9ro."},"exceptions":{"type":"object","properties":{"open":{"type":"integer"},"by_type":{"type":"object","additionalProperties":{"type":"integer"}},"by_severity":{"type":"object","additionalProperties":{"type":"integer"}}}},"amounts":{"type":"object","description":"Les cumuls sont exprim\u00e9s en devise de r\u00e9f\u00e9rence : additionner des\neuros, des dollars et des francs CFA ne produirait aucun chiffre\nsens\u00e9. La ventilation par devise de r\u00e8glement reste disponible dans\n`by_currency`.\n","properties":{"authorized_for_payment":{"type":"number","format":"double"},"blocked":{"type":"number","format":"double"},"currency":{"$ref":"#\/components\/schemas\/Currency"},"by_currency":{"type":"array","description":"Montants autoris\u00e9s par devise de r\u00e8glement effective.","items":{"type":"object","properties":{"currency":{"$ref":"#\/components\/schemas\/Currency"},"amount":{"type":"number","format":"double","description":"Montant dans la devise de r\u00e8glement."},"base_amount":{"type":"number","format":"double","description":"Contre-valeur en devise de r\u00e9f\u00e9rence."}}}}}}}}}}}