Exports - Aaperture
Module D : Exports comptables, contacts, sessions/projets, GDPR export avec génération asynchrone et stockage R2.
Contexte
Exports comptables, contacts, sessions/projets, GDPR export, rapports. Génération lourde + stockage R2.
Bounded Context (DDD)
Exporting
- Agrégat :
ExportJob - Domain services :
ExportBuilder(par type),ExportPolicyService - Ports :
StoragePort,PdfRendererPort(si PDF),ZipPort
Schéma DB
export_jobs
CREATE TABLE export_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL,
requested_by UUID NOT NULL,
type TEXT NOT NULL, -- ACCOUNTING|CONTACTS|SESSIONS|GDPR|CUSTOM
params JSONB NOT NULL, -- Paramètres de l'export (filtres, champs, format, etc.)
status TEXT NOT NULL DEFAULT 'QUEUED', -- QUEUED|RUNNING|DONE|FAILED
file_object_id UUID REFERENCES file_objects(id),
format TEXT NOT NULL, -- CSV|EXCEL|PDF|ZIP|JSON
error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
finished_at TIMESTAMPTZ
);
CREATE INDEX idx_export_jobs_org ON export_jobs(org_id);
CREATE INDEX idx_export_jobs_status ON export_jobs(status);
CREATE INDEX idx_export_jobs_requested_by ON export_jobs(requested_by);
CREATE INDEX idx_export_jobs_type ON export_jobs(type);
CREATE INDEX idx_export_jobs_created ON export_jobs(created_at);
Jobs
export:build (worker-export)
Responsabilités :
- Générer export selon type et format
- Upload vers R2
- Créer
file_object - Mettre à jour
export_jobs
Payload :
{
v: 1,
orgId: "uuid",
userId: "uuid",
entityId: "uuid", // export_job.id
}
Processus :
- Charger
export_jobdepuis DB - Selon
type:- ACCOUNTING : Exporter factures, paiements, avoirs (CSV/Excel)
- CONTACTS : Exporter contacts avec champs sélectionnés (CSV/Excel/JSON)
- SESSIONS : Exporter sessions avec détails (CSV/Excel/JSON)
- GDPR : Export complet données utilisateur (JSON/XML)
- CUSTOM : Export personnalisé selon
params
- Générer fichier selon
format:- CSV : Génération CSV avec headers
- EXCEL : Génération XLSX avec feuilles multiples si nécessaire
- PDF : Génération PDF via
PdfRendererPort - ZIP : Génération ZIP si plusieurs fichiers (ex: GDPR avec fichiers joints)
- JSON : Génération JSON structuré
- Upload vers R2 via
StoragePort - Créer
file_objectavec métadonnées - Mettre à jour
export_jobs.file_object_id,status = 'DONE',finished_at = NOW()
Gestion erreurs :
- Erreur génération →
status = 'FAILED',error = ... - Erreur upload → retry (3 tentatives max)
- Timeout → retry (3 tentatives max)
export:cleanup (optionnel)
Responsabilités :
- Nettoyer exports anciens (> 30 jours)
- Supprimer fichiers R2 orphelins
Payload :
{
v: 1,
orgId?: string, // Optionnel : toutes les orgs
retentionDays?: number, // Par défaut 30
}
Processus :
- Trouver
export_jobsavecfinished_at < NOW() - retentionDays - Supprimer fichiers R2 via
StoragePort - Supprimer
file_objects - Supprimer
export_jobs(ou archiver)
Types d'exports
ACCOUNTING
Params :
{
dateFrom: string; // ISO date
dateTo: string;
includeInvoices: boolean;
includePayments: boolean;
includeCreditNotes: boolean;
currency?: "EUR" | "USD";
}
Format : CSV, Excel
Colonnes :
- Date, Type (Invoice/Payment/CreditNote), Numéro, Montant, Statut, Client, etc.
CONTACTS
Params :
{
filters?: {
tags?: string[];
hasSessions?: boolean;
createdAfter?: string;
};
fields?: string[]; // Champs à exporter (id, name, email, phone, ...)
}
Format : CSV, Excel, JSON
Colonnes : Selon fields sélectionnés
SESSIONS
Params :
{
filters?: {
type?: string[];
status?: string[];
dateFrom?: string;
dateTo?: string;
};
fields?: string[]; // Champs à exporter
includeFiles?: boolean; // Si true → génère ZIP avec fichiers
}
Format : CSV, Excel, JSON, ZIP (si fichiers)
Colonnes : Selon fields sélectionnés
GDPR
Params :
{
userId: string; // Utilisateur concerné
includeFiles?: boolean; // Inclure fichiers uploadés
}
Format : JSON, XML, ZIP (si fichiers)
Structure :
{
"user": { ... },
"sessions": [ ... ],
"contacts": [ ... ],
"quotes": [ ... ],
"invoices": [ ... ],
"files": [ ... ],
"metadata": {
"exportedAt": "2024-01-01T00:00:00Z",
"format": "JSON"
}
}
CUSTOM
Params :
{
entityType: "QUOTE" | "INVOICE" | "CONTRACT" | ...;
filters: Record<string, any>;
fields: string[];
format: "CSV" | "EXCEL" | "JSON";
}
Endpoints
POST /exports
Créer export job.
Request :
{
type: "ACCOUNTING" | "CONTACTS" | "SESSIONS" | "GDPR" | "CUSTOM";
format: "CSV" | "EXCEL" | "PDF" | "ZIP" | "JSON";
params: Record<string, any>; // Selon type
}
Response :
{
exportJobId: string;
status: "QUEUED";
jobId: string; // job export:build
}
GET /exports/:id
Statut + métadonnées export.
Response :
{
id: string;
type: string;
format: string;
status: "QUEUED" | "RUNNING" | "DONE" | "FAILED";
fileObjectId?: string;
error?: string;
createdAt: string;
finishedAt?: string;
}
GET /exports/:id/download
URL presignée pour téléchargement.
Response :
{
downloadUrl: string; // Presigned R2 URL
expiresAt: string;
filename: string;
sizeBytes: number;
}
GET /exports
Liste exports avec filtres.
Query params :
type?: Filtrer par typestatus?: Filtrer par statutdateFrom?,dateTo?: Plage de dates
Response :
{
exports: Array<ExportJob>;
total: number;
page: number;
pageSize: number;
}
DELETE /exports/:id
Supprimer export (et fichier R2).
Response :
{
deleted: boolean;
}
Frontend
Hooks
useCreateExport()
const { mutate: createExport } = useCreateExport();
createExport({
type: "ACCOUNTING",
format: "CSV",
params: {
dateFrom: "2024-01-01",
dateTo: "2024-12-31",
includeInvoices: true,
includePayments: true
}
}, {
onSuccess: ({ exportJobId }) => {
// Polling status
}
});
useExportJob(id) (polling)
const { data: exportJob } = useExportJob(exportJobId, {
refetchInterval: (query) => {
return query.state.data?.status === "QUEUED" || query.state.data?.status === "RUNNING"
? 2000
: false;
}
});
useDownloadExport(id)
const { mutate: download } = useDownloadExport(exportJobId);
download(undefined, {
onSuccess: ({ downloadUrl }) => {
window.open(downloadUrl, '_blank');
}
});
Composants
ExportDialog
Dialog pour créer export :
- Sélecteur type
- Sélecteur format
- Formulaire params selon type
- Bouton "Export"
ExportJobStatus
Affiche statut avec :
- Badge statut (QUEUED/RUNNING/DONE/FAILED)
- Spinner si en cours
- Bouton "Download" si DONE
- Message erreur si FAILED
ExportsList
Liste exports avec :
- Filtres (type, statut, date)
- Actions : download, delete
- Pagination
Tests
Unit
ExportBuilder: Génération selon type/formatExportPolicyService: Vérification permissions- Formats : CSV, Excel, PDF, JSON valides
Integration
export:build: Génération complète + upload R2- Presigned URLs : Génération et expiration correctes
- Cleanup : Suppression fichiers anciens
E2E
- Créer export → polling → download disponible
- GDPR export : Structure JSON complète
Edge Cases
Idempotence
- Vérifier si export avec mêmes params existe déjà (même jour)
- Retourner
file_object_idexistant si disponible
Retries
- Erreur génération → retry (3 tentatives max)
- Erreur upload → retry (3 tentatives max)
- Timeout → retry (3 tentatives max)
Performance
- Exports volumineux (> 10k lignes) :
- Génération par batch
- Streaming si possible
- Limiter taille max (ex: 100k lignes)
Stockage
- Nettoyage automatique exports > 30 jours
- Limiter nombre d'exports simultanés par organisation
Références
- Export System - Système d'export existant
- Storage Port - Port de stockage R2