Architecture Multi-Tenant B2B
Version: 1.0 Date: 2026-03-03
Table des matieres
- Introduction
- Modele de Donnees
- Onboarding B2B
- KYB (Know Your Business)
- Isolation Tenant
- Billing Tenant
- API Tenant
- Sub-Tenants
- Factures et Avoirs Tenant
- 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, SignaturesCas 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
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 :
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 :
| Methode | Description |
|---|---|
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 :
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 :
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.completedEtape 1 : Creer une session
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 :
{
"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.ioembedded: widget integrable (voir section Widget)
Etape 2 : Verifier le SIRET
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 :
{
"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
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
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
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 :
{
"status": "approved",
"authorization_code": "auth_xyz789...",
"expires_in": 600
}Le code d'autorisation est valide 10 minutes.
Etape 6 : Echanger le code
curl -X POST https://api.scell.io/api/v1/tenant/onboarding/exchange \
-d '{
"code": "auth_xyz789...",
"session_id": "sess_abc123..."
}'Reponse :
{
"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 :
<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 :
- StepSiret : Saisie et verification du SIRET
- StepVerify : Confirmation des informations (TVA, adresse)
- StepDocuments : Upload des documents KYB
- StepComplete : Finalisation et reception des credentials
4. KYB (Know Your Business)
Documents requis
Pour finaliser l'onboarding, trois documents sont obligatoires :
| Type | Code | Description | Formats acceptes |
|---|---|---|---|
| Extrait Kbis | kbis | Justificatif d'immatriculation (< 3 mois) | PDF, JPG, PNG |
| Piece d'identite | id_card | CNI ou passeport du representant legal | PDF, JPG, PNG |
| RIB | bank_details | Releve d'identite bancaire | PDF, JPG, PNG |
Documents optionnels :
| Type | Code | Description |
|---|---|---|
| Bilan | financial_statement | Bilan comptable du dernier exercice |
| Justificatif de domicile | proof_of_address | Moins de 3 mois |
| Licence professionnelle | business_license | Si activite reglementee |
Statuts de verification
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 --> [*]| Statut | Description |
|---|---|
pending | En attente de documents |
documents_submitted | Documents recus, en attente de revue |
under_review | Revue en cours par l'equipe Scell.io |
verified | KYB valide, acces production autorise |
rejected | KYB refuse (raison fournie) |
API KYB (cote tenant)
| Endpoint | Methode | Description |
|---|---|---|
/v1/tenant/kyb/status | GET | Statut actuel du KYB |
/v1/tenant/kyb/documents | GET | Lister les documents |
/v1/tenant/kyb/documents | POST | Uploader un document |
/v1/tenant/kyb/documents/{id} | GET | Detail d'un document |
/v1/tenant/kyb/documents/{id} | DELETE | Supprimer un document |
/v1/tenant/kyb/documents/{id}/download | GET | Telecharger |
API KYB (cote admin)
| Endpoint | Methode | Description |
|---|---|---|
/v1/admin/kyb/pending | GET | Documents en attente de revue |
/v1/admin/kyb/documents/{id}/review | POST | Approuver ou refuser |
/v1/admin/kyb/documents/{id}/download | GET | Telecharger |
Impact du KYB sur les operations
| KYB Status | Sandbox (sk_test_*, pk_test_*) | Production (sk_live_*, pk_live_*) |
|---|---|---|
pending | Acces complet | Acces refuse (403) |
documents_submitted | Acces complet | Acces refuse (403) |
under_review | Acces complet | Acces refuse (403) |
verified | Acces complet | Acces complet |
rejected | Acces complet | Acces 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 :
- Valide la cle API (format
sk_live_*ousk_test_*) - Charge le tenant depuis la base (hash de la cle)
- Verifie que le tenant est actif
- Verifie le KYB pour les cles de production
- Injecte le tenant dans la requete (
$request->attributes->set('tenant', $tenant))
// 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 :
// 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 :
// 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) :
// 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 :
$subTenant = SubTenant::where('id', $subTenantId)
->where('tenant_id', $tenant->id)
->firstOrFail();Middlewares appliques aux routes tenant
| Middleware | Route | Role |
|---|---|---|
tenant.key | Toutes les routes /tenant/* | Authentification par cle API |
tenant.rate-limit | Toutes les routes /tenant/* | Rate limiting (100 req/min) |
tenant.balance:invoice | Creation de factures | Verification du solde |
tenant.balance:credit_note | Creation d'avoirs | Verification du solde |
tenant.isolation | Routes critiques | RLS PostgreSQL |
sub-tenant | Routes /sub-tenants/{id}/* | Validation sub-tenant |
fiscal.kill-switch | Creation factures/avoirs | Kill switch fiscal |
fiscal.period-guard | Operations fiscales | Periode fiscale valide |
fiscal.scope | Routes fiscales | Verification des permissions |
6. Billing Tenant
Systeme de credits
Scell.io utilise un systeme de credits prepaye. Chaque tenant dispose d'un solde en EUR :
| Operation | Cout unitaire |
|---|---|
| Facture electronique | 0.04 EUR |
| Avoir electronique | 0.04 EUR |
| Signature electronique | 1.20 EUR |
| Facture entrante (reception) | Gratuit |
Remises volume
Le TenantBillingService applique des remises progressives basees sur la consommation mensuelle de factures :
| Seuil mensuel | Remise |
|---|---|
| > 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 :
php artisan billing:generate-monthly-invoicesLa facture inclut :
| Ligne | Calcul |
|---|---|
| Factures electroniques | nombre * 0.04 EUR |
| Signatures electroniques | nombre * 1.20 EUR |
| Avoirs electroniques | nombre * 0.04 EUR |
| Sous-total HT | Somme des lignes |
| Remise volume | -X% si seuil atteint |
| Total HT | Apres remise |
| TVA (20%) | total_ht * 0.20 |
| Total TTC | total_ht + tva |
Recharge de credits (Top-Up)
Les tenants peuvent recharger leur solde via Stripe :
# 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 :
| Type | Description |
|---|---|
invoice_created | Debit 0.04 EUR |
credit_note_created | Debit 0.04 EUR |
signature_created | Debit 1.20 EUR |
balance_topup | Credit N EUR |
balance_debit | Debit manuel (admin) |
API Billing
| Endpoint | Methode | Description |
|---|---|---|
/v1/tenant/billing/invoices | GET | Factures de facturation |
/v1/tenant/billing/invoices/{id} | GET | Detail d'une facture |
/v1/tenant/billing/invoices/{id}/download | GET | Telecharger PDF |
/v1/tenant/billing/usage | GET | Consommation du mois |
/v1/tenant/billing/top-up | POST | Demander une recharge |
/v1/tenant/billing/top-up/confirm | POST | Confirmer la recharge |
/v1/tenant/billing/transactions | GET | Historique des transactions |
7. API Tenant
Authentification
Deux systemes d'authentification existent :
1. Authentification User (Dashboard Sanctum)
Pour le dashboard administrateur :
curl -H "Authorization: Bearer {sanctum_token}" \
https://api.scell.io/api/v1/admin/companies2. 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 :
curl -H "X-API-Key: sk_live_YOUR_KEY" \
https://api.scell.io/api/v1/tenant/meFormat : 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 :
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 :
| Type | Format | Localisation | Securite | Usages |
|---|---|---|---|---|
| Secret Key | sk_* | Backend/serveur | Haute - confidentielle | Factures, avoirs, factures entrantes, admin |
| Publishable Key | pk_* | Frontend/widget | Publique - safe | Onboarding, signatures, widgets |
Gestion du profil
# 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-keyRate 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 :
{
"error": "Trop de requetes",
"code": "RATE_LIMIT_EXCEEDED",
"retry_after": 30
}Headers de rate limiting :
| Header | Description |
|---|---|
X-RateLimit-Limit | Limite par minute |
X-RateLimit-Remaining | Requetes restantes |
X-RateLimit-Reset | Timestamp 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
# 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 :
# 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/overview9. Factures et Avoirs Tenant
Creation de factures
Deux modes sont disponibles :
Mode sub-tenant (factures associees a un sous-tenant) :
POST /v1/tenant/sub-tenants/{subTenantId}/invoicesMode direct (factures sans sous-tenant) :
POST /v1/tenant/invoicesCorps de la requete
{
"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 :
# 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
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 :
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"| INVAPI Factures entrantes
| Endpoint | Methode | Description |
|---|---|---|
/v1/tenant/sub-tenants/{id}/invoices/incoming | POST | Enregistrer manuellement |
/v1/tenant/sub-tenants/{id}/invoices/incoming | GET | Lister |
/v1/tenant/invoices/incoming/{id} | GET | Detail |
/v1/tenant/invoices/incoming/{id}/accept | POST | Accepter |
/v1/tenant/invoices/incoming/{id}/reject | POST | Rejeter (raison requise) |
/v1/tenant/invoices/incoming/{id}/mark-paid | POST | Marquer comme payee |
Les factures entrantes sont gratuites (pas de debit du solde).
Statuts des factures entrantes
| Statut | Description |
|---|---|
received | Recue du SuperPDP |
accepted | Acceptee par le tenant |
rejected | Rejetee par le tenant |
disputed | En litige |
paid | Payee |