Ventes & Facturation

Référence technique du domaine Ventes & Facturation — devis, commandes, factures, encaissements, achats fournisseur, facturation électronique B2B (réforme FR 2026).

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).

Les écrans et les routes API vivent sous /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

TermeEntitéDéfinition
ClientCustomerEntité acheteuse rattachée à une Company.
FournisseurSupplierEntité vendeuse rattachée à une Company.
Entité de facturationBillingEntityEntité légale émettrice (ex. Pulse Group SAS). Une org peut en avoir plusieurs.
DevisQuote / QuoteLineProposition commerciale chiffrée ; numéro alloué à la création.
CommandeOrder / OrderLineEngagement client ferme ; issu d'une conversion de devis ou libre.
FactureInvoice / InvoiceLineDocument légal ; numéro alloué à l'émission (DRAFT → ISSUED).
AvoirCreditNoteFacture négative ; numérotation séparée par BillingEntity (préfixe creditNoteNumberPrefix, défaut AV-, suivi de la séquence — ex. AV-1).
RèglementPaymentEncaissement client ; alloué à une ou plusieurs factures.
Numéro légalInvoice.numberSé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.
PPFPortail Public de Facturation — annuaire destinataires + concentrateur fiscal.
Factur-XFormat hybride PDF/A-3 + XML CII embarqué (EN 16931).
E-reportingEReportingSubmissionTransmission des données B2C / encaissements hors e-invoicing.
3-way matchRapprochement commande + PV réception + facture fournisseur.
Facture fournisseurSupplierInvoiceUpload PDF + OCR ; qualifiée analytiquement (nature, exercice, contrat).
Règlement fournisseurSupplierPaymentSortie 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

ÉcranRouteContenu
Tableau de bord direction/workspace/direction-dashboardKPI transverses : CA du mois, DSO moyen + top 10, encours bailleur, prévision de trésorerie.
Liste clients/workspace/customersDataTable + encours TTC + CA YTD + DSO.
Fiche client…/customers/[id]Synthèse KYB, devis, commandes, factures, contrats LLD, conditions.
Liste devis/workspace/quotesDataTable + kanban (Drafts / Envoyés / Acceptés / Convertis).
Création devis/workspace/quotes/newWizard 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/ordersDataTable + statuts CONFIRMED → SHIPPED → RECEIVED → INVOICED.
Fiche commande…/orders/[id]Lignes, tracking expédition, génération facture.
Liste factures/workspace/invoicesDataTable + statut e-invoicing Agicap, actions de masse.
Création facture/workspace/invoices/newDepuis 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/paymentsSaisie et lettrage des encaissements (pas d'endpoint d'import bancaire ni de rapprochement automatique).
Fournisseurs/workspace/suppliersMiroir module clients.
Factures fournisseur/workspace/supplier-invoicesUpload PDF + OCR + 3-way match + affectation analytique.
E-reporting/workspace/einvoicing/ereportingGénération DRAFT + soumission périodique à Agicap.
Soumissions e-invoice/workspace/einvoicing/submissionsDataTable cross-factures paginée, filtres statut/provider.
Paramètres facturation/workspace/admin/settings/billingBillingEntities, 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 transition DRAFT → ISSUED. Jamais avant. Test de concurrence validé : 12 émissions parallèles → FAC-1FAC-12, sans trou ni doublon.
  • Format numéro : {prefix}{sequence} — concaténation brute du préfixe (BillingEntity.invoiceNumberPrefix, défaut FAC-) 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é via xmlbuilder2 ; validation XSD via libxmljs2.
  • EInvoiceSubmission : relation 1-N avec Invoice (plusieurs tentatives, champ attemptNumber). 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 égaler totalHt de la facture.
  • EReportingSubmission : contrainte d'unicité (organizationId, yearMonth, type) ; statuts DRAFT → 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éthodeRoutePermission
GET/api/workspace/quotessales.quote:read
POST/api/workspace/quotessales.quote:create
GET…/quotes/[id]sales.quote:read
PATCH…/quotes/[id]sales.quote:update
POST…/quotes/[id]/convertsales.order:create
POST…/quotes/[id]/refusesales.quote:update
GET/api/workspace/orderssales.order:read
POST/api/workspace/orderssales.order:create
GET…/orders/[id]sales.order:read
PATCH…/orders/[id]sales.order:update
POST…/orders/[id]/invoicesales.invoice:create
GET/api/workspace/invoicessales.invoice:read
POST/api/workspace/invoicessales.invoice:create
GET…/invoices/[id]sales.invoice:read
POST…/invoices/[id]/issuesales.invoice:issue
GET…/invoices/[id]/pdfsales.invoice:read
GET…/invoices/[id]/validate-fr2026sales.invoice:read
GET…/invoices/[id]/einvoicesales.invoice:read
POST…/invoices/[id]/submit-einvoicesales.invoice:issue
POST…/invoices/[id]/resubmit-einvoicesales.invoice:issue
GET/api/workspace/credit-notessales.creditnote:read
POST/api/workspace/credit-notessales.creditnote:create
GET…/credit-notes/[id]sales.creditnote:read
GET/api/workspace/paymentssales.payment:read
POST/api/workspace/paymentssales.payment:create
GET/api/workspace/dunningsrecouvrement.dunning:read
POST/api/workspace/dunningsrecouvrement.dunning:create
POST…/dunnings/[id]/advancerecouvrement.dunning:manage
POST…/dunnings/[id]/resolverecouvrement.dunning:cancel
POST…/dunnings/[id]/cancelrecouvrement.dunning:cancel
Pas d'endpoint d'envoi de devis : le passage 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éthodeRoutePermission
GET/api/workspace/supplierspurchasing.supplier:read
POST/api/workspace/supplierspurchasing.supplier:create
GET/api/workspace/supplier-invoicespurchasing.invoice:read
POST/api/workspace/supplier-invoicespurchasing.invoice:create
GET…/supplier-invoices/[id]purchasing.invoice:read
PATCH…/supplier-invoices/[id]purchasing.invoice:update
PATCH…/supplier-invoices/[id]/orderpurchasing.invoice:update
PUT…/supplier-invoices/[id]/linespurchasing.invoice:create
POST…/supplier-invoices/[id]/ocrfinance.invoice:manage
GET…/supplier-invoices/[id]/matchpurchasing.invoice:read
POST…/supplier-invoices/[id]/validate-three-wayfinance.invoice:manage
POST…/supplier-invoices/[id]/override-three-wayfinance.invoice:manage
GET…/supplier-invoices/[id]/allocationspurchasing.invoice:read
POST…/supplier-invoices/[id]/allocationspurchasing.invoice:create
POST…/supplier-invoices/[id]/allocate-contractpurchasing.invoice:create
GET/api/workspace/supplier-paymentspurchasing.payment:read
POST/api/workspace/supplier-paymentspurchasing.payment:create
Pas d'endpoint …/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éthodeRouteAuth
POST/api/workspace/invoices/[id]/submit-einvoicesales.invoice:issue
POST/api/workspace/invoices/[id]/resubmit-einvoicesales.invoice:issue
GET/api/workspace/invoices/[id]/einvoicesales.invoice:read
GET/api/workspace/einvoicing/ereportingsales.invoice:issue
POST/api/workspace/einvoicing/ereporting/generatesales.invoice:issue
GET/api/workspace/einvoicing/ereporting/[id]sales.invoice:issue
POST/api/workspace/einvoicing/ereporting/[id]/submitsales.invoice:issue
GET/api/workspace/einvoicing/submissionssales.invoice:read
POST/api/webhooks/einvoicing/agicapSignature HMAC
POST/api/webhooks/einvoicing/agicap-inboundSignature HMAC

Exemple de référence d'endpoint :

POST/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éthodeRoutePermission
GET/api/workspace/accounting/journal-salesbilling.export:download
GET/api/workspace/accounting/journal-purchasesbilling.export:download
GET/api/workspace/accounting/balance-customersbilling.export:download
GET/api/workspace/accounting/vat-summarybilling.export:download
GET/api/workspace/accounting/export?format=fec|sage|cegid|pennylanebilling.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éanceObligation
1ᵉʳ septembre 2026Ré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é) :

  1. Facture ISSUED → génération Factur-X (XML CII + PDF/A-3).
  2. Contrôle bloquant des mentions obligatoires FR 2026 (SIREN client, adresse livraison, nature opération, option TVA sur débits).
  3. Résolution du destinataire (SIREN/SIRET → identifiant Agicap/Peppol).
  4. Soumission API Agicap → EInvoiceSubmission.providerInvoiceId.
  5. 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.
  6. En cas de rejet/refus : re-soumission via POST …/invoices/[id]/resubmit-einvoice (incrémente attemptNumber).

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 un Dunning par facture échue impayée (idempotent — un seul dossier par Invoice, RG-01-01).
  • Workflow d'étapes (DunningStep, RG-01-02) : NONE → RELANCE_1 → RELANCE_2 → MED → LEGAL, avancé via POST …/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 (sauf LEGAL_RETURNED, RG-01-06). Résolution/annulation via …/dunnings/[id]/resolve et …/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 ISSUED interdite ; soft delete sur DRAFT uniquement.
  • 3-way match fournisseur : refus de paiement si écart > seuil (5 % ou montant absolu configurable).
  • SupplierInvoice validée sans accountingCodeId ou fiscalYear : bloquant pour la clôture comptable.
  • Ventilation SupplierInvoiceAllocation : somme des lignes doit égaler totalHt de 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énementCanalDestinataire
Devis envoyéEmailClient
Devis accepté (lien public)In-app + emailCommercial
Facture émiseEmail (avec PDF)Client
Facture proche échéance (J-3)EmailClient (si activé)
Facture en retard (J+1)EmailClient + commercial
Facture > 30j retardIn-app + emailComptable + manager
Avoir émisEmailClient
Paiement reçuIn-app + emailComptable
Facture fournisseur reçue (Agicap)In-appComptable
Facture rejetée / refusée (Agicap)In-app + emailComptable + commercial
E-reporting transmis / en échecIn-appComptable
3-way match KOIn-appComptable