Skip to content

Architecture Multi-Tenant B2B

Version: 1.0 Date: 2026-03-03


Table des matieres

  1. Introduction
  2. Modele de Donnees
  3. Onboarding B2B
  4. KYB (Know Your Business)
  5. Isolation Tenant
  6. Billing Tenant
  7. API Tenant
  8. Sub-Tenants
  9. Factures et Avoirs Tenant
  10. Factures Entrantes

1. Introduction

Modele multi-tenant de Scell.io

Scell.io utilise un modele multi-tenant par rangee (row-level) avec une base de donnees partagee. Chaque tenant (entreprise cliente) dispose d'un espace isole au sein de la meme infrastructure PostgreSQL.

Ce modele est concu pour les partenaires B2B (cabinets comptables, ERP, plateformes SaaS) qui souhaitent integrer les services de facturation et signature electronique de Scell.io pour le compte de leurs propres clients.

Hierarchie des acteurs

Tenant (Partenaire ou Entreprise)
  Qui? Cabinet comptable, editeur ERP, marketplace, ou entreprise SaaS
  Role: Initie l'onboarding, recoit les webhooks
  Attribut: is_partner = true/false

  |-- Sub-Tenant (Client du tenant / Filiale / Departement)
  |     Qui? Entite operationnelle du tenant
  |     Role: Espace logique pour isoler les factures
  |
  |     |-- Company (Entite juridique)
  |     |     Role: SIRET, adresse, donnees legales
  |     |
  |     |-- Invoices, Credit Notes, Signatures

Cas d'usage concret

Un cabinet comptable (Tenant avec is_partner=true) inscrit ses 200 clients (autres Tenants) via l'API d'onboarding. Chaque client genere ses factures de maniere autonome avec sa propre cle API. Le cabinet supervise l'ensemble depuis son dashboard. Scell.io facture chaque tenant individuellement en fonction de sa consommation.


2. Modele de Donnees

Diagramme des relations

mermaid
erDiagram
    Tenant ||--o{ SubTenant : "contient"
    Tenant ||--o{ Company : "possede"
    Tenant ||--o{ TenantInvoice : "recoit (facturation)"
    Tenant ||--o{ TenantTransaction : "consomme"
    Tenant ||--o{ KybDocument : "fournit"
    Tenant ||--o{ OnboardingSession : "initie (si partenaire)"

    SubTenant ||--o{ Company : "represente"
    SubTenant ||--o{ TenantInvoice : "concerne"

    Company ||--o{ Invoice : "emet/recoit"
    Company ||--o{ CreditNote : "emet"
    Company ||--o{ Signature : "signe"
    Company ||--o{ ApiKey : "possede"

    OnboardingSession ||--o{ KybDocument : "collecte"
    OnboardingSession ||--o{ Tenant : "cree (tenant enfant)"

    Tenant {
        uuid id PK
        string name
        string slug
        string siret
        string vat_number
        string api_key_hash
        string api_key_prefix
        string publishable_key_hash
        string publishable_key_prefix
        string environment
        string kyb_status
        decimal balance
        boolean is_active
        boolean is_partner
        uuid parent_tenant_id FK
        json settings
    }

    SubTenant {
        uuid id PK
        uuid tenant_id FK
        string external_id
        string name
        string siret
        string status
    }

    Company {
        uuid id PK
        uuid tenant_id FK
        uuid sub_tenant_id FK
        string name
        string siret
        string status
    }

Modele Tenant

Le Tenant represente une entreprise cliente ou un partenaire operant dans son propre espace :

php
Tenant {
    id: UUID
    parent_tenant_id: UUID           // Partenaire parent (si ce tenant est un client)
    name: "ACME SAS"
    slug: "acme-sas"
    legal_name: "ACME Societe par Actions Simplifiee"
    siret: "12345678900001"
    vat_number: "FR12345678900"

    // Contacts
    billing_email: "compta@acme.fr"
    technical_email: "tech@acme.fr"

    // Authentification - Secret Keys (backend)
    api_key_hash: "sha256:..."           // Hash de la cle secrete API
    api_key_prefix: "sk_live_aBcD"       // Prefixe pour identification (sk_live_ ou sk_test_)

    // Authentification - Publishable Keys (frontend)
    publishable_key_hash: "sha256:..."   // Hash de la cle publiable
    publishable_key_prefix: "pk_live_xyz"  // Prefixe de la cle publiable (pk_live_ ou pk_test_)

    environment: "production"            // production | sandbox

    // KYB
    kyb_status: "verified"           // pending | documents_submitted | under_review | verified | rejected
    kyb_verified_at: "2026-02-15"

    // Billing
    balance: 150.00                  // Solde en EUR
    stripe_customer_id: "cus_..."

    // Webhooks
    webhook_url: "https://..."
    webhook_secret: "whsec_..."

    // Configuration
    is_partner: false                // true = partenaire integrant Scell.io
    settings: {
        fiscal_permissions: ["fiscal:read", "fiscal:write"],
        invoice_prefix: "FA",
        default_vat_rate: 20,
    }

    is_active: true
}

Methodes cles :

MethodeDescription
generateApiKey(string $env)Genere une cle secrete API (sk_live_* ou sk_test_*)
generatePublishableKey(string $env)Genere une cle publiable (pk_live_* ou pk_test_*)
findByApiKey(string $key)Recherche un tenant par sa cle API secrete (hash)
findByPublishableKey(string $key)Recherche un tenant par sa cle publiable (hash)
hasSufficientBalance(float $amount)Verifie le solde disponible
debit(float $amount)Debite le solde
credit(float $amount)Credite le solde
isKybCompleted()Verifie que le KYB est valide
markKybVerified()Marque le KYB comme verifie

Modele SubTenant

Le SubTenant represente un client ou une entite du tenant :

php
SubTenant {
    id: UUID
    tenant_id: UUID
    external_id: "CLI-42"            // ID dans le systeme du tenant
    name: "Filiale Nord SAS"
    email: "contact@filiale-nord.fr"
    siret: "98765432100001"
    vat_number: "FR98765432100"
    address: { line1: "...", city: "Lille", postal_code: "59000" }
    status: "active"
    metadata: {}
}

L'external_id permet au tenant de mapper ses propres identifiants clients avec Scell.io.


3. Onboarding B2B

Flux complet

Le processus d'onboarding permet a un Tenant partenaire (is_partner=true) d'inscrire un nouveau Tenant client en 6 etapes :

mermaid
sequenceDiagram
    participant P as Tenant Partenaire
    participant API as Scell.io API
    participant INSEE as INSEE Sirene
    participant VIES as VIES API
    participant T as Nouveau Tenant Client

    P->>API: POST /v1/tenant/onboarding/sessions
    API->>P: session_id + redirect_url

    P->>API: POST /v1/tenant/onboarding/verify-siret {siret}
    API->>INSEE: Verification SIRET
    INSEE->>API: Donnees entreprise
    API->>P: Entreprise verifiee

    P->>API: POST /v1/tenant/onboarding/verify-vat {vat_number}
    API->>VIES: Verification TVA
    VIES->>API: Numero valide
    API->>P: TVA verifiee

    P->>API: POST /v1/tenant/onboarding/documents {kbis, id_card, bank_details}
    API->>API: Stockage S3 + validation format
    API->>P: Documents recus

    P->>API: POST /v1/tenant/onboarding/complete
    API->>API: Cree Tenant + genere cle API
    API->>P: authorization_code (10 min)

    P->>API: POST /v1/tenant/onboarding/exchange {code}
    API->>P: {api_key: "sk_live_...", tenant_id: "..."}
    API->>P: Webhook: onboarding.completed

Etape 1 : Creer une session

bash
curl -X POST https://api.scell.io/api/v1/tenant/onboarding/sessions \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "api",
    "external_id": "client-42",
    "callback_url": "https://partner.com/callback",
    "metadata": { "plan": "premium" }
  }'

Reponse :

json
{
  "id": "sess_abc123...",
  "status": "in_progress",
  "redirect_url": "https://onboarding.scell.io/sess_abc123...",
  "expires_at": "2026-03-04T14:30:00+00:00"
}

Modes disponibles :

  • api : onboarding 100% API (le partner gere l'UI)
  • redirect : redirection vers le formulaire Scell.io
  • embedded : widget integrable (voir section Widget)

Etape 2 : Verifier le SIRET

bash
curl -X POST https://api.scell.io/api/v1/tenant/onboarding/verify-siret \
  -H "X-API-Key: sk_live_..." \
  -d '{
    "session_id": "sess_abc123...",
    "siret": "12345678900001"
  }'

Reponse :

json
{
  "valid": true,
  "company": {
    "name": "ACME SAS",
    "siret": "12345678900001",
    "siren": "123456789",
    "naf_code": "6201Z",
    "legal_form": "SAS",
    "address": {
      "line1": "10 rue de la Paix",
      "city": "Paris",
      "postal_code": "75002",
      "country": "FR"
    },
    "active": true
  }
}

La verification utilise l'API INSEE Sirene avec authentification mTLS. Les resultats sont caches 24 heures.

Etape 3 : Verifier la TVA

bash
curl -X POST https://api.scell.io/api/v1/tenant/onboarding/verify-vat \
  -H "X-API-Key: sk_live_..." \
  -d '{
    "session_id": "sess_abc123...",
    "vat_number": "FR12345678900"
  }'

Verification via l'API VIES (VAT Information Exchange System) de la Commission Europeenne.

Etape 4 : Uploader les documents KYB

bash
curl -X POST https://api.scell.io/api/v1/tenant/onboarding/documents \
  -H "X-API-Key: sk_live_..." \
  -F "session_id=sess_abc123..." \
  -F "document_type=kbis" \
  -F "file=@kbis.pdf"

Etape 5 : Completer l'onboarding

bash
curl -X POST https://api.scell.io/api/v1/tenant/onboarding/complete \
  -H "X-API-Key: sk_live_..." \
  -d '{
    "session_id": "sess_abc123...",
    "billing_email": "compta@acme.fr",
    "technical_email": "tech@acme.fr"
  }'

Reponse :

json
{
  "status": "approved",
  "authorization_code": "auth_xyz789...",
  "expires_in": 600
}

Le code d'autorisation est valide 10 minutes.

Etape 6 : Echanger le code

bash
curl -X POST https://api.scell.io/api/v1/tenant/onboarding/exchange \
  -d '{
    "code": "auth_xyz789...",
    "session_id": "sess_abc123..."
  }'

Reponse :

json
{
  "tenant_id": "9f3a1b2c-...",
  "api_key": "sk_live_aBcDeFgH...",
  "environment": "production",
  "name": "ACME SAS",
  "kyb_status": "verified"
}

Widget d'onboarding

Pour le mode embedded, Scell.io fournit un widget JavaScript standalone :

html
<div id="scell-onboarding"></div>

<script type="module">
  import { ScellOnboarding } from '@scell/onboarding-widget';

  const widget = new ScellOnboarding({
    container: '#scell-onboarding',
    apiKey: 'sk_live_...',
    mode: 'embedded',
    locale: 'fr',
    onComplete: (result) => {
      console.log('Tenant cree:', result.tenant_id);
      console.log('Cle API:', result.api_key);
    },
    onError: (error) => {
      console.error('Erreur:', error);
    },
  });

  widget.mount();
</script>

Le widget est disponible dans frontend/packages/onboarding-widget/ et distribue en tant que package npm @scell/onboarding-widget.

Etapes du widget :

  1. StepSiret : Saisie et verification du SIRET
  2. StepVerify : Confirmation des informations (TVA, adresse)
  3. StepDocuments : Upload des documents KYB
  4. StepComplete : Finalisation et reception des credentials

4. KYB (Know Your Business)

Documents requis

Pour finaliser l'onboarding, trois documents sont obligatoires :

TypeCodeDescriptionFormats acceptes
Extrait KbiskbisJustificatif d'immatriculation (< 3 mois)PDF, JPG, PNG
Piece d'identiteid_cardCNI ou passeport du representant legalPDF, JPG, PNG
RIBbank_detailsReleve d'identite bancairePDF, JPG, PNG

Documents optionnels :

TypeCodeDescription
Bilanfinancial_statementBilan comptable du dernier exercice
Justificatif de domicileproof_of_addressMoins de 3 mois
Licence professionnellebusiness_licenseSi activite reglementee

Statuts de verification

mermaid
stateDiagram-v2
    [*] --> pending: Session creee
    pending --> documents_submitted: Documents uploades
    documents_submitted --> under_review: Revue admin lancee
    under_review --> verified: Approuve
    under_review --> rejected: Refuse
    rejected --> documents_submitted: Nouveaux documents
    verified --> [*]
StatutDescription
pendingEn attente de documents
documents_submittedDocuments recus, en attente de revue
under_reviewRevue en cours par l'equipe Scell.io
verifiedKYB valide, acces production autorise
rejectedKYB refuse (raison fournie)

API KYB (cote tenant)

EndpointMethodeDescription
/v1/tenant/kyb/statusGETStatut actuel du KYB
/v1/tenant/kyb/documentsGETLister les documents
/v1/tenant/kyb/documentsPOSTUploader un document
/v1/tenant/kyb/documents/{id}GETDetail d'un document
/v1/tenant/kyb/documents/{id}DELETESupprimer un document
/v1/tenant/kyb/documents/{id}/downloadGETTelecharger

API KYB (cote admin)

EndpointMethodeDescription
/v1/admin/kyb/pendingGETDocuments en attente de revue
/v1/admin/kyb/documents/{id}/reviewPOSTApprouver ou refuser
/v1/admin/kyb/documents/{id}/downloadGETTelecharger

Impact du KYB sur les operations

KYB StatusSandbox (sk_test_*, pk_test_*)Production (sk_live_*, pk_live_*)
pendingAcces completAcces refuse (403)
documents_submittedAcces completAcces refuse (403)
under_reviewAcces completAcces refuse (403)
verifiedAcces completAcces complet
rejectedAcces completAcces refuse (403)

5. Isolation Tenant

L'isolation des donnees entre tenants est garantie a quatre niveaux.

Niveau 1 : Middleware TenantApiKeyMiddleware

Le middleware intercepte chaque requete tenant et :

  1. Valide la cle API (format sk_live_* ou sk_test_*)
  2. Charge le tenant depuis la base (hash de la cle)
  3. Verifie que le tenant est actif
  4. Verifie le KYB pour les cles de production
  5. Injecte le tenant dans la requete ($request->attributes->set('tenant', $tenant))
php
// Les controllers accedent au tenant via :
$tenant = $request->attributes->get('tenant');
$tenantId = $request->attributes->get('tenant_id');
$environment = $request->attributes->get('tenant_environment');

Niveau 2 : Trait HasTenantScope

Le trait HasTenantScope est applique aux modeles fiscaux (FiscalEntry, FiscalClosing, FiscalAttestation, etc.) et ajoute un Global Scope Eloquent automatique :

php
// Applique automatiquement WHERE tenant_id = ? a toutes les requetes
static::addGlobalScope('tenant', function (Builder $builder) {
    $request = request();
    if ($request && $request->has('tenant_id')) {
        $builder->where('tenant_id', $request->get('tenant_id'));
    }
});

Ce scope garantit qu'un tenant ne peut jamais acceder aux donnees d'un autre tenant, meme en cas d'erreur dans le code du controller.

Methodes de scope additionnelles :

php
// Filtrage explicite par tenant
FiscalEntry::forTenant($tenantId)->get();

// Filtrage par sub-tenant
Invoice::forSubTenant($subTenantId)->get();

// Desactiver temporairement le scope (admin uniquement)
FiscalEntry::withoutTenantScope()->get();

Niveau 3 : Middleware EnsureTenantIsolation

Ce middleware configure une variable de session PostgreSQL pour le Row Level Security (RLS) :

php
// Au debut de la requete
DB::statement("SET app.current_tenant_id = ?", [$tenantId]);

// A la fin de la requete
DB::statement("RESET app.current_tenant_id");

Cette variable peut etre utilisee par des policies PostgreSQL pour une isolation au niveau base de donnees, independante de l'application.

Niveau 4 : Middleware SubTenantMiddleware

Pour les routes impliquant un sub-tenant (ex: /tenant/sub-tenants/{subTenantId}/invoices), ce middleware verifie que le sub-tenant appartient bien au tenant authentifie :

php
$subTenant = SubTenant::where('id', $subTenantId)
    ->where('tenant_id', $tenant->id)
    ->firstOrFail();

Middlewares appliques aux routes tenant

MiddlewareRouteRole
tenant.keyToutes les routes /tenant/*Authentification par cle API
tenant.rate-limitToutes les routes /tenant/*Rate limiting (100 req/min)
tenant.balance:invoiceCreation de facturesVerification du solde
tenant.balance:credit_noteCreation d'avoirsVerification du solde
tenant.isolationRoutes critiquesRLS PostgreSQL
sub-tenantRoutes /sub-tenants/{id}/*Validation sub-tenant
fiscal.kill-switchCreation factures/avoirsKill switch fiscal
fiscal.period-guardOperations fiscalesPeriode fiscale valide
fiscal.scopeRoutes fiscalesVerification des permissions

6. Billing Tenant

Systeme de credits

Scell.io utilise un systeme de credits prepaye. Chaque tenant dispose d'un solde en EUR :

OperationCout unitaire
Facture electronique0.04 EUR
Avoir electronique0.04 EUR
Signature electronique1.20 EUR
Facture entrante (reception)Gratuit

Remises volume

Le TenantBillingService applique des remises progressives basees sur la consommation mensuelle de factures :

Seuil mensuelRemise
> 1 000 factures-10%
> 5 000 factures-20%
> 10 000 factures-30%

Facturation mensuelle

Le 1er de chaque mois, la commande GenerateMonthlyInvoices genere une facture consolidee pour chaque tenant :

bash
php artisan billing:generate-monthly-invoices

La facture inclut :

LigneCalcul
Factures electroniquesnombre * 0.04 EUR
Signatures electroniquesnombre * 1.20 EUR
Avoirs electroniquesnombre * 0.04 EUR
Sous-total HTSomme des lignes
Remise volume-X% si seuil atteint
Total HTApres remise
TVA (20%)total_ht * 0.20
Total TTCtotal_ht + tva

Recharge de credits (Top-Up)

Les tenants peuvent recharger leur solde via Stripe :

bash
# Demander un top-up
curl -X POST https://api.scell.io/api/v1/tenant/billing/top-up \
  -H "X-API-Key: sk_live_..." \
  -d '{ "amount": 100.00 }'

# Reponse : URL de paiement Stripe
{
  "checkout_url": "https://checkout.stripe.com/...",
  "amount": 100.00,
  "currency": "EUR"
}

# Confirmer apres paiement
curl -X POST https://api.scell.io/api/v1/tenant/billing/top-up/confirm \
  -H "X-API-Key: sk_live_..." \
  -d '{ "payment_intent_id": "pi_..." }'

Transactions

Chaque debit/credit est enregistre dans TenantTransaction :

TypeDescription
invoice_createdDebit 0.04 EUR
credit_note_createdDebit 0.04 EUR
signature_createdDebit 1.20 EUR
balance_topupCredit N EUR
balance_debitDebit manuel (admin)

API Billing

EndpointMethodeDescription
/v1/tenant/billing/invoicesGETFactures de facturation
/v1/tenant/billing/invoices/{id}GETDetail d'une facture
/v1/tenant/billing/invoices/{id}/downloadGETTelecharger PDF
/v1/tenant/billing/usageGETConsommation du mois
/v1/tenant/billing/top-upPOSTDemander une recharge
/v1/tenant/billing/top-up/confirmPOSTConfirmer la recharge
/v1/tenant/billing/transactionsGETHistorique des transactions

7. API Tenant

Authentification

Deux systemes d'authentification existent :

1. Authentification User (Dashboard Sanctum)

Pour le dashboard administrateur :

bash
curl -H "Authorization: Bearer {sanctum_token}" \
  https://api.scell.io/api/v1/admin/companies

2. Authentification Tenant (Dual Key System)

Scell.io utilise un systeme de deux types de cles pour securiser l'integration :

Secret Keys (sk_*) - Cotes serveur uniquement

Les cles secretes sont utilisees pour les operations sensibles via le header X-API-Key. Elles ne doivent JAMAIS etre exposees en frontend :

bash
curl -H "X-API-Key: sk_live_YOUR_KEY" \
  https://api.scell.io/api/v1/tenant/me

Format : sk_live_ ou sk_test_ + 32 caracteres alphanumeriques

Publishable Keys (pk_*) - Cotes client (frontend)

Les cles publiables sont utilisees dans les widgets et composants frontend via le header X-Publishable-Key. Elles sont sans risque d'exposition :

javascript
import { ScellOnboarding } from '@scell/onboarding-widget';

const widget = new ScellOnboarding({
  publishableKey: 'pk_live_xYz123...',  // Safe to expose in frontend
  mode: 'embedded'
});

Format : pk_live_ ou pk_test_ + 32 caracteres alphanumeriques

Comparaison des deux systemes :

TypeFormatLocalisationSecuriteUsages
Secret Keysk_*Backend/serveurHaute - confidentielleFactures, avoirs, factures entrantes, admin
Publishable Keypk_*Frontend/widgetPublique - safeOnboarding, signatures, widgets

Gestion du profil

bash
# Recuperer le profil
GET /v1/tenant/me

# Mettre a jour
PUT /v1/tenant/me
{
  "billing_email": "nouveau@email.fr",
  "webhook_url": "https://nouveau-webhook.com/scell"
}

# Recuperer le solde
GET /v1/tenant/balance

# Statistiques globales
GET /v1/tenant/stats

# Regenerer la cle API
POST /v1/tenant/regenerate-key

Rate Limiting

Les routes tenant sont limitees a 100 requetes par minute par cle API. Ce seuil est configurable via le modele RateLimitOverride.

Reponse en cas de depassement :

json
{
  "error": "Trop de requetes",
  "code": "RATE_LIMIT_EXCEEDED",
  "retry_after": 30
}

Headers de rate limiting :

HeaderDescription
X-RateLimit-LimitLimite par minute
X-RateLimit-RemainingRequetes restantes
X-RateLimit-ResetTimestamp de reinitialisation

8. Sub-Tenants

Principe

Les sub-tenants permettent a un tenant de segmenter ses operations par client, filiale ou departement. Chaque sub-tenant est un espace logique avec :

  • Son propre external_id (mappable avec le SI du tenant)
  • Ses propres factures et avoirs
  • Ses propres statistiques
  • Sa propre sequence de numerotation

API Sub-Tenants

bash
# Lister les sub-tenants
GET /v1/tenant/sub-tenants

# Creer un sub-tenant
POST /v1/tenant/sub-tenants
{
  "external_id": "CLI-042",
  "name": "ACME Filiale Nord",
  "email": "nord@acme.fr",
  "siret": "98765432100001",
  "vat_number": "FR98765432100"
}

# Recuperer par ID
GET /v1/tenant/sub-tenants/{id}

# Recuperer par external_id
GET /v1/tenant/sub-tenants/by-external-id/{externalId}

# Mettre a jour
PUT /v1/tenant/sub-tenants/{id}

# Supprimer
DELETE /v1/tenant/sub-tenants/{id}

Scoping par sub-tenant

Les factures et avoirs sont automatiquement scopes par sub-tenant :

bash
# Factures d'un sub-tenant specifique
POST /v1/tenant/sub-tenants/{subTenantId}/invoices
GET  /v1/tenant/sub-tenants/{subTenantId}/invoices

# Avoirs d'un sub-tenant
POST /v1/tenant/sub-tenants/{subTenantId}/credit-notes
GET  /v1/tenant/sub-tenants/{subTenantId}/credit-notes

# Statistiques par sub-tenant
GET /v1/tenant/sub-tenants/{subTenantId}/stats/overview

9. Factures et Avoirs Tenant

Creation de factures

Deux modes sont disponibles :

Mode sub-tenant (factures associees a un sous-tenant) :

bash
POST /v1/tenant/sub-tenants/{subTenantId}/invoices

Mode direct (factures sans sous-tenant) :

bash
POST /v1/tenant/invoices

Corps de la requete

json
{
  "invoice_number": "FA-2026-001",
  "issue_date": "2026-03-03",
  "due_date": "2026-04-02",
  "seller_name": "ACME SAS",
  "seller_siret": "12345678900001",
  "seller_address": { "line1": "10 rue de la Paix", "city": "Paris", "postal_code": "75002" },
  "buyer_name": "Client SARL",
  "buyer_siret": "98765432100001",
  "buyer_address": { "line1": "5 avenue Victor Hugo", "city": "Lyon", "postal_code": "69002" },
  "lines": [
    { "description": "Prestation de conseil", "quantity": 5, "unit_price": 100, "tax_rate": 20 }
  ],
  "output_format": "facturx",
  "metadata": { "reference_interne": "CMD-042" }
}

Operations en masse (bulk)

Pour les volumes importants, des endpoints bulk sont disponibles :

bash
# Creation en masse (max 100 factures)
POST /v1/tenant/invoices/bulk
{
  "invoices": [
    { "invoice_number": "FA-001", ... },
    { "invoice_number": "FA-002", ... }
  ]
}

# Soumission en masse au SuperPDP
POST /v1/tenant/invoices/bulk-submit
{
  "invoice_ids": ["uuid-1", "uuid-2", "uuid-3"]
}

# Statut en masse
POST /v1/tenant/invoices/bulk-status
{
  "invoice_ids": ["uuid-1", "uuid-2", "uuid-3"]
}

Cycle de vie d'une facture tenant

mermaid
stateDiagram-v2
    [*] --> draft: Creee
    draft --> draft: Modifiee
    draft --> submitted: Soumise au SuperPDP
    submitted --> processing: En cours de traitement
    processing --> completed: Transmise
    processing --> failed: Erreur
    failed --> submitted: Re-soumise
    completed --> paid: Paiement recu

    draft --> deleted: Supprimee (draft uniquement)
    deleted --> [*]

Avoirs tenant

Meme structure que les factures, avec les particularites :

  • Toujours lies a une facture d'origine (invoice_id)
  • Montant maximum = montant restant creditable de la facture
  • Meme protection d'immutabilite fiscale

10. Factures Entrantes

Principe

Scell.io recoit les factures des fournisseurs via le SuperPDP. Ces factures entrantes sont synchronisees automatiquement et disponibles dans l'espace tenant.

Synchronisation

La synchronisation s'effectue via le SyncIncomingInvoicesJob, declenche quotidiennement ou par webhook SuperPDP :

mermaid
graph LR
    SPDP["SuperPDP<br/>(Portail)"] -->|"Webhook"| WH["SuperPDPWebhookController"]
    WH -->|"Job async"| JOB["ProcessIncomingInvoiceWebhook"]
    JOB -->|"Cree"| INV["Invoice<br/>(direction: incoming)"]

    SPDP -->|"Sync quotidien"| SYNC["SyncIncomingInvoicesJob"]
    SYNC -->|"Cursor pagination"| INV

API Factures entrantes

EndpointMethodeDescription
/v1/tenant/sub-tenants/{id}/invoices/incomingPOSTEnregistrer manuellement
/v1/tenant/sub-tenants/{id}/invoices/incomingGETLister
/v1/tenant/invoices/incoming/{id}GETDetail
/v1/tenant/invoices/incoming/{id}/acceptPOSTAccepter
/v1/tenant/invoices/incoming/{id}/rejectPOSTRejeter (raison requise)
/v1/tenant/invoices/incoming/{id}/mark-paidPOSTMarquer comme payee

Les factures entrantes sont gratuites (pas de debit du solde).

Statuts des factures entrantes

StatutDescription
receivedRecue du SuperPDP
acceptedAcceptee par le tenant
rejectedRejetee par le tenant
disputedEn litige
paidPayee

Documentation Scell.io