Ventes & Facturation
Ventes & Facturation
Périmètre : clients, devis, commandes, factures, avoirs, règlements, fournisseurs (devis/commandes/factures/règlements miroir), entités de facturation, modes de paiement, comptabilité opérationnelle et e-invoicing (réforme FR 2026/2027 — provider : Agicap).
/workspace/<ressource> à plat (et non /workspace/sales/<ressource> — convention retenue à l'implémentation). Routes API : /api/workspace/quotes, /api/workspace/orders, /api/workspace/invoices, /api/workspace/credit-notes, /api/workspace/payments, /api/workspace/supplier-invoices, /api/workspace/supplier-payments, /api/workspace/dunnings, /api/workspace/einvoicing/*. Source : docs/10-domain-sales-billing.md.Vocabulaire & entités
| Terme | Entité | Définition |
|---|---|---|
| Client | Customer | Entité acheteuse rattachée à une Company. |
| Fournisseur | Supplier | Entité vendeuse rattachée à une Company. |
| Entité de facturation | BillingEntity | Entité légale émettrice (ex. Pulse Group SAS). Une org peut en avoir plusieurs. |
| Devis | Quote / QuoteLine | Proposition commerciale chiffrée ; numéro alloué à la création. |
| Commande | Order / OrderLine | Engagement client ferme ; issu d'une conversion de devis ou libre. |
| Facture | Invoice / InvoiceLine | Document légal ; numéro alloué à l'émission (DRAFT → ISSUED). |
| Avoir | CreditNote | Facture négative ; numérotation séparée par BillingEntity (préfixe creditNoteNumberPrefix, défaut AV-, suivi de la séquence — ex. AV-1). |
| Règlement | Payment | Encaissement client ; alloué à une ou plusieurs factures. |
| Numéro légal | Invoice.number | Séquence sans trou, immuable, par BillingEntity (compteur invoiceSequence, monotone, sans segmentation par année ni par type). |
| PA (Plateforme Agréée) | — | Opérateur fiscal (Agicap) émettant/recevant les e-factures via Peppol. |
| PPF | — | Portail Public de Facturation — annuaire destinataires + concentrateur fiscal. |
| Factur-X | — | Format hybride PDF/A-3 + XML CII embarqué (EN 16931). |
| E-reporting | EReportingSubmission | Transmission des données B2C / encaissements hors e-invoicing. |
| 3-way match | — | Rapprochement commande + PV réception + facture fournisseur. |
| Facture fournisseur | SupplierInvoice | Upload PDF + OCR ; qualifiée analytiquement (nature, exercice, contrat). |
| Règlement fournisseur | SupplierPayment | Sortie vers fournisseur ; enregistré sur la fiche SupplierInvoice. |
Le modèle complet est dans
docs/04-data-model.md§8. La comptabilité analytique (plan comptable, nature de prestation, clôture FNP/CCA/CAP) fait l'objet d'un domaine dédié :docs/23-domain-accounting.md.
Écrans
| Écran | Route | Contenu |
|---|---|---|
| Tableau de bord direction | /workspace/direction-dashboard | KPI transverses : CA du mois, DSO moyen + top 10, encours bailleur, prévision de trésorerie. |
| Liste clients | /workspace/customers | DataTable + encours TTC + CA YTD + DSO. |
| Fiche client | …/customers/[id] | Synthèse KYB, devis, commandes, factures, contrats LLD, conditions. |
| Liste devis | /workspace/quotes | DataTable + kanban (Drafts / Envoyés / Acceptés / Convertis). |
| Création devis | /workspace/quotes/new | Wizard 5 étapes : client → BillingEntity → lignes → conditions → envoi. |
| Fiche devis | …/quotes/[id] | Lignes éditables si DRAFT, envoi, conversion commande, PDF. |
| Page publique devis | /q/[token] | Acceptation / refus client via Quote.publicToken (hors auth). |
| Liste commandes | /workspace/orders | DataTable + statuts CONFIRMED → SHIPPED → RECEIVED → INVOICED. |
| Fiche commande | …/orders/[id] | Lignes, tracking expédition, génération facture. |
| Liste factures | /workspace/invoices | DataTable + statut e-invoicing Agicap, actions de masse. |
| Création facture | /workspace/invoices/new | Depuis commande, contrat LLD ou libre ; aperçu Factur-X. |
| Fiche facture | …/invoices/[id] | Timeline e-invoicing, règlements, avoirs, re-soumission Agicap. |
| Liste règlements | /workspace/payments | Saisie et lettrage des encaissements (pas d'endpoint d'import bancaire ni de rapprochement automatique). |
| Fournisseurs | /workspace/suppliers | Miroir module clients. |
| Factures fournisseur | /workspace/supplier-invoices | Upload PDF + OCR + 3-way match + affectation analytique. |
| E-reporting | /workspace/einvoicing/ereporting | Génération DRAFT + soumission périodique à Agicap. |
| Soumissions e-invoice | /workspace/einvoicing/submissions | DataTable cross-factures paginée, filtres statut/provider. |
| Paramètres facturation | /workspace/admin/settings/billing | BillingEntities, modes de paiement, templates PDF, numérotation, Agicap. |
Modèle de données (points clés)
Invoice.number: alloué sous verrou consultatif Postgres (pg_advisory_xact_lock) à la transitionDRAFT → ISSUED. Jamais avant. Test de concurrence validé : 12 émissions parallèles →FAC-1…FAC-12, sans trou ni doublon.- Format numéro :
{prefix}{sequence}— concaténation brute du préfixe (BillingEntity.invoiceNumberPrefix, défautFAC-) et du compteur entier (BillingEntity.invoiceSequence), sans année ni zéro de remplissage — ex.FAC-1,FAC-42. Pas de reset annuel : le compteur est monotone sur toute la vie de l'entité. Invoice.facturXProfile: stocké à l'émission (audit). PDF/A-3 + XML CII embarqué généré viaxmlbuilder2; validation XSD vialibxmljs2.EInvoiceSubmission: relation 1-N avecInvoice(plusieurs tentatives, champattemptNumber). Contrainte@@unique([invoiceId, attemptNumber]).SupplierInvoice.accountingCodeId+fiscalYear: contrôle bloquant pour la clôture — une facture fournisseur validée doit impérativement porter ces deux champs.SupplierInvoiceAllocation: ventilation multi-contrats d'une charge réseau — la somme des lignes doit égalertotalHtde la facture.EReportingSubmission: contrainte d'unicité(organizationId, yearMonth, type); statutsDRAFT → SUBMITTED → ACCEPTED / REJECTED.Quote.publicToken: clé signée pour la page d'acceptation publique/q/[token]; hors authentification.SupplierEInvoiceReceipt: idempotence à la réception inbound — dédup par(provider, externalInvoiceId).
API
Ventes
| Méthode | Route | Permission |
|---|---|---|
GET | /api/workspace/quotes | sales.quote:read |
POST | /api/workspace/quotes | sales.quote:create |
GET | …/quotes/[id] | sales.quote:read |
PATCH | …/quotes/[id] | sales.quote:update |
POST | …/quotes/[id]/convert | sales.order:create |
POST | …/quotes/[id]/refuse | sales.quote:update |
GET | /api/workspace/orders | sales.order:read |
POST | /api/workspace/orders | sales.order:create |
GET | …/orders/[id] | sales.order:read |
PATCH | …/orders/[id] | sales.order:update |
POST | …/orders/[id]/invoice | sales.invoice:create |
GET | /api/workspace/invoices | sales.invoice:read |
POST | /api/workspace/invoices | sales.invoice:create |
GET | …/invoices/[id] | sales.invoice:read |
POST | …/invoices/[id]/issue | sales.invoice:issue |
GET | …/invoices/[id]/pdf | sales.invoice:read |
GET | …/invoices/[id]/validate-fr2026 | sales.invoice:read |
GET | …/invoices/[id]/einvoice | sales.invoice:read |
POST | …/invoices/[id]/submit-einvoice | sales.invoice:issue |
POST | …/invoices/[id]/resubmit-einvoice | sales.invoice:issue |
GET | /api/workspace/credit-notes | sales.creditnote:read |
POST | /api/workspace/credit-notes | sales.creditnote:create |
GET | …/credit-notes/[id] | sales.creditnote:read |
GET | /api/workspace/payments | sales.payment:read |
POST | /api/workspace/payments | sales.payment:create |
GET | /api/workspace/dunnings | recouvrement.dunning:read |
POST | /api/workspace/dunnings | recouvrement.dunning:create |
POST | …/dunnings/[id]/advance | recouvrement.dunning:manage |
POST | …/dunnings/[id]/resolve | recouvrement.dunning:cancel |
POST | …/dunnings/[id]/cancel | recouvrement.dunning:cancel |
DRAFT → SENT se fait par PATCH …/quotes/[id] ({ status: "SENT" }, permission sales.quote:update) ; le service pose seulement l'horodatage sentAt et n'envoie aucun email automatiquement. Pas de permission sales.quote:send. Le refus passe par …/quotes/[id]/refuse (motif structuré) ou par un PATCH vers REFUSED. La conversion en commande est gardée par sales.order:create (et non sales.quote:convert, qui n'existe pas).Avoir : créé via POST /api/workspace/credit-notes (permission sales.creditnote:create), pas via …/invoices/[id]/credit-note. Annulation de facture : il n'existe ni endpoint …/invoices/[id]/cancel ni permission sales.invoice:cancel ; une facture ISSUED se neutralise par un avoir total (qui la fait passer CANCELED).Paiements : seuls GET et POST /api/workspace/payments existent. Les routes …/payments/import et …/payments/reconcile et la permission sales.payment:reconcilen'existent pas.Relances impayés : il n'y a pas de …/invoices/[id]/reminder. Le recouvrement est porté par le module dunning dédié (/api/workspace/dunnings, permissions recouvrement.dunning:*).Achats (miroir)
| Méthode | Route | Permission |
|---|---|---|
GET | /api/workspace/suppliers | purchasing.supplier:read |
POST | /api/workspace/suppliers | purchasing.supplier:create |
GET | /api/workspace/supplier-invoices | purchasing.invoice:read |
POST | /api/workspace/supplier-invoices | purchasing.invoice:create |
GET | …/supplier-invoices/[id] | purchasing.invoice:read |
PATCH | …/supplier-invoices/[id] | purchasing.invoice:update |
PATCH | …/supplier-invoices/[id]/order | purchasing.invoice:update |
PUT | …/supplier-invoices/[id]/lines | purchasing.invoice:create |
POST | …/supplier-invoices/[id]/ocr | finance.invoice:manage |
GET | …/supplier-invoices/[id]/match | purchasing.invoice:read |
POST | …/supplier-invoices/[id]/validate-three-way | finance.invoice:manage |
POST | …/supplier-invoices/[id]/override-three-way | finance.invoice:manage |
GET | …/supplier-invoices/[id]/allocations | purchasing.invoice:read |
POST | …/supplier-invoices/[id]/allocations | purchasing.invoice:create |
POST | …/supplier-invoices/[id]/allocate-contract | purchasing.invoice:create |
GET | /api/workspace/supplier-payments | purchasing.payment:read |
POST | /api/workspace/supplier-payments | purchasing.payment:create |
…/invoices/[id]/validate : le changement de statut d'une facture fournisseur passe par PATCH …/supplier-invoices/[id] (purchasing.invoice:update), et la validation du rapprochement 3-way par POST …/supplier-invoices/[id]/validate-three-way (finance.invoice:manage). L'affectation analytique se fait via …/allocations (purchasing.invoice:create), pas via un PATCH …/allocate. L'upload PDF + OCR passe par …/supplier-invoices/[id]/ocr, pas par …/invoices/upload.Facturation électronique (Agicap)
| Méthode | Route | Auth |
|---|---|---|
POST | /api/workspace/invoices/[id]/submit-einvoice | sales.invoice:issue |
POST | /api/workspace/invoices/[id]/resubmit-einvoice | sales.invoice:issue |
GET | /api/workspace/invoices/[id]/einvoice | sales.invoice:read |
GET | /api/workspace/einvoicing/ereporting | sales.invoice:issue |
POST | /api/workspace/einvoicing/ereporting/generate | sales.invoice:issue |
GET | /api/workspace/einvoicing/ereporting/[id] | sales.invoice:issue |
POST | /api/workspace/einvoicing/ereporting/[id]/submit | sales.invoice:issue |
GET | /api/workspace/einvoicing/submissions | sales.invoice:read |
POST | /api/webhooks/einvoicing/agicap | Signature HMAC |
POST | /api/webhooks/einvoicing/agicap-inbound | Signature HMAC |
Exemple de référence d'endpoint :
/api/workspace/invoices/[id]/issueAuth Valide un brouillon et émet la facture : vérifie d'abord les mentions obligatoires
FR 2026 (retourne 422 avec la liste détaillée si l'une manque), puis — dans une
même transaction — alloue le numéro légal sous advisory lock, passe ISSUED et
estampille le profil Factur-X. Pas de corps de requête (l'ID suffit), aucun
email ni soumission Agicap automatique : la transmission e-invoicing se
déclenche séparément via POST …/invoices/[id]/submit-einvoice.
Requête
curl -s -X POST "$API/api/workspace/invoices/$ID/issue" \
-H "Cookie: $SESSION"
Réponse
{
"id": "inv_…",
"number": "FAC-42",
"status": "ISSUED",
"facturXProfile": "EN16931",
"issueDate": "2026-06-16T10:00:00Z"
}
Comptabilité opérationnelle
| Méthode | Route | Permission |
|---|---|---|
GET | /api/workspace/accounting/journal-sales | billing.export:download |
GET | /api/workspace/accounting/journal-purchases | billing.export:download |
GET | /api/workspace/accounting/balance-customers | billing.export:download |
GET | /api/workspace/accounting/vat-summary | billing.export:download |
GET | /api/workspace/accounting/export?format=fec|sage|cegid|pennylane | billing.export:download |
Workflows
Cycle vente standard
Opportunity (CRM)
│
└─→ Quote (DRAFT) → SENT → ACCEPTED → CONVERTED
│
Order (CONFIRMED)
│
SHIPPED → RECEIVED
│
Invoice (DRAFT) → ISSUED → PARTIALLY_PAID → PAID
↘ OVERDUE ↘ CANCELED
Statuts de facture (InvoiceStatus) : DRAFT, ISSUED, PARTIALLY_PAID, OVERDUE, PAID, CANCELED. PARTIALLY_PAID/PAID sont posés par les encaissements (Payment) ; OVERDUE par le job d'échéance ; CANCELED par un avoir total.
À l'émission (DRAFT → ISSUED) : contrôle des mentions FR 2026 (422 si incomplet), puis advisory lock, numérotation légale irréversible et génération Factur-X — le tout atomique. La soumission Agicap n'est pas déclenchée par l'émission : elle se fait via POST …/invoices/[id]/submit-einvoice.
Numérotation légale (garde-fou)
Séquence par BillingEntity (compteur invoiceSequence, monotone, sans segmentation par année ni par type et sans reset annuel). Le numéro est consommé une seule fois, à la transition vers ISSUED. Toute tentative d'émission concurrente attend la libération du verrou (pg_advisory_xact_lock). La suppression d'une facture ISSUED est interdite — seule une annulation avec création d'avoir total est possible.
Facturation électronique B2B — réforme FR
| Échéance | Obligation |
|---|---|
| 1ᵉʳ septembre 2026 | Réception obligatoire pour toutes les entreprises ; émission pour grandes entreprises et ETI. |
| 1ᵉʳ septembre 2027 | Émission obligatoire pour PME et TPE. |
Pulse ERP vise la conformité complète (émission + réception + e-reporting) au 1ᵉʳ septembre 2026, indépendamment du seuil par BillingEntity.
Workflow émission (Agicap, Phase 7 — livré) :
- Facture
ISSUED→ génération Factur-X (XML CII + PDF/A-3). - Contrôle bloquant des mentions obligatoires FR 2026 (SIREN client, adresse livraison, nature opération, option TVA sur débits).
- Résolution du destinataire (SIREN/SIRET → identifiant Agicap/Peppol).
- Soumission API Agicap →
EInvoiceSubmission.providerInvoiceId. - Suivi cycle de vie via webhook HMAC
POST /api/webhooks/einvoicing/agicap— statuts :SUBMITTED → RECEIVED_BY_PDP → DEPOSITED → RECEIVED_BY_RECIPIENT → APPROVED / REFUSED / IN_DISPUTE / PAID / REJECTED_BY_PDP. - En cas de rejet/refus : re-soumission via
POST …/invoices/[id]/resubmit-einvoice(incrémenteattemptNumber).
Workflow réception inbound : webhook POST /api/webhooks/einvoicing/agicap-inbound → parser CII (cii-parser.ts) → matching fournisseur par SIREN → création SupplierInvoice status RECEIVED. Idempotence via SupplierEInvoiceReceipt.
Relances impayés (module recouvrement)
Le recouvrement n'est pas porté par la facture (pas de champ lastReminderSentAt/reminderCount, pas de …/invoices/[id]/reminder) mais par le module dunning dédié (Dunning, /api/workspace/dunnings).
- Job
detect-overdue-invoices: itère sur les organisations actives et crée unDunningpar facture échue impayée (idempotent — un seul dossier parInvoice, RG-01-01). - Workflow d'étapes (
DunningStep, RG-01-02) :NONE → RELANCE_1 → RELANCE_2 → MED → LEGAL, avancé viaPOST …/dunnings/[id]/advance. Les délais entre étapes sont configurables par organisation (dunningStep2DelayDays,dunningMedDelayDays,dunningTransferDelayDays). - Horodatage par étape :
relance1SentAt,relance2SentAt,medSentAt; accusés via…/dunnings/[id]/track-ack. - Transition
MED → LEGAL(transmission au juridique) séparée et irréversible (saufLEGAL_RETURNED, RG-01-06). Résolution/annulation via…/dunnings/[id]/resolveet…/dunnings/[id]/cancel.
E-reporting périodique
POST /api/workspace/einvoicing/ereporting/generate — body { yearMonth, type } (types : TRANSACTIONS, PAYMENTS, B2C). Soumission via …/[id]/submit. Contrainte d'unicité (organizationId, yearMonth, type) évite les doubles envois.
Règles métier
- Numérotation sans trou, immuable, par
BillingEntity— obligation légale FR. Advisory lock Postgres à chaque émission. - Date facture = date d'émission = date où la facture passe
ISSUED. Non modifiable après. - TVA multi-taux dans une facture (5,5 %, 10 %, 20 %). Auto-liquidation pour clients intra-UE (mention obligatoire + vérification VIES).
- Facture > 5 € HT : mentions légales obligatoires (SIRET, N° TVA, conditions règlement, taux pénalité, indemnité 40 €).
- Avoir partiel : total avoirs ne peut pas dépasser le total facture. Avoir total → facture passe
CANCELED. - Paiement : ne peut pas dépasser le montant dû.
- Suppression facture
ISSUEDinterdite ; soft delete surDRAFTuniquement. - 3-way match fournisseur : refus de paiement si écart > seuil (5 % ou montant absolu configurable).
SupplierInvoicevalidée sansaccountingCodeIdoufiscalYear: bloquant pour la clôture comptable.- Ventilation
SupplierInvoiceAllocation: somme des lignes doit égalertotalHtde la facture.
KPIs
CA HT MTD, YTD, comparatif N-1 · panier moyen · DSO global et par client · % factures payées dans les délais · encours > 30/60/90 jours · taux d'acceptation devis · cycle moyen Quote → Order → Invoice → Paid · TVA collectée / déductible / nette · marge brute par produit.
Notifications
| Événement | Canal | Destinataire |
|---|---|---|
| Devis envoyé | Client | |
| Devis accepté (lien public) | In-app + email | Commercial |
| Facture émise | Email (avec PDF) | Client |
| Facture proche échéance (J-3) | Client (si activé) | |
| Facture en retard (J+1) | Client + commercial | |
| Facture > 30j retard | In-app + email | Comptable + manager |
| Avoir émis | Client | |
| Paiement reçu | In-app + email | Comptable |
| Facture fournisseur reçue (Agicap) | In-app | Comptable |
| Facture rejetée / refusée (Agicap) | In-app + email | Comptable + commercial |
| E-reporting transmis / en échec | In-app | Comptable |
| 3-way match KO | In-app | Comptable |
Achats
Référence technique du domaine Achats fournisseurs — devis multi-fournisseurs, inbox OCR IA, comparateur, commandes, réception→asset, rapprochement 3 voies et boucle achats↔leasing.
Location
Référence technique du domaine Location — affaires, contrats, financement PMT, agréments bailleurs, cessions, assurance, maintenance, prélèvements et engagements bancaires.