Aller au contenu principal

Server Actions — Import

Source : src/app/actions/import.ts


processImportCSV(formData, accountId, format)

Parse et importe un fichier CSV dans un compte. Point d'entrée unique pour tous les formats supportés.

export type CsvFormat = "BOURSO_OPS" | "BOURSO_PEA" | "REVOLUT" | "CSV_TR";

export type ImportResult =
| { success: true; imported: number; ignored: number; snapshots: number; format: CsvFormat }
| { success: false; error: string };

export async function processImportCSV(
formData: FormData,
accountId: string,
format: CsvFormat
): Promise<ImportResult>

Pré-conditions :

  • Session valide (auth())
  • accountId appartient à l'utilisateur courant
  • formData contient un champ file (objet File)

Formats supportés

formatBanqueParser
BOURSO_OPSBoursoBank — opérations courantessrc/lib/parsers/bourso.ts
BOURSO_PEABoursoBank — PEAsrc/lib/parsers/bourso-pea.ts
REVOLUTRevolutsrc/lib/parsers/revolut.ts
CSV_TRTrade Republicsrc/lib/parsers/trade-republic.ts

Le format est détecté automatiquement si non spécifié via src/lib/parsers/detect.ts.


Pipeline d'import

L'action exécute séquentiellement :

1. Parsing CSV → transactions + snapshots

Le parser retourne { transactions[], snapshots[], format }. Chaque transaction reçoit un importHash déterministe calculé depuis ses champs significatifs.

2. Déduplication des transactions

await prisma.transaction.createMany({
data: transactions,
skipDuplicates: true, // ignoré si importHash déjà présent
});

ignored = transactions.length - imported indique le nombre de doublons.

3. Upsert des snapshots

Chaque snapshot est upsert sur (accountId, date) sauf si un snapshot MANUAL existe déjà pour ce jour — celui-ci n'est jamais écrasé. Voir BR-SNAP-003.

4. Détection des transferts internes

Si au moins une transaction a été importée, un scan de détection automatique est lancé :

  • Cherche dans les autres comptes de l'utilisateur une transaction de montant opposé dans une fenêtre de ±2 jours.
  • Si trouvée : les deux transactions sont marquées isInternal = true et liées via counterpartId (transaction atomique).

Voir BR-TX-003.

5. Revalidation

revalidatePath("/accounts"), revalidatePath("/accounts/<id>"), revalidatePath("/dashboard").


Retour

// Succès
{ success: true, imported: 42, ignored: 3, snapshots: 5, format: "BOURSO_OPS" }

// Échec
{ success: false, error: "Format CSV non reconnu" }
ChampDescription
importedNombre de nouvelles transactions créées
ignoredDoublons ignorés (importHash déjà présent)
snapshotsSnapshots créés ou mis à jour
formatFormat détecté par le parser

Appel client (exemple)

const formData = new FormData();
formData.append("file", file);

const result = await processImportCSV(formData, accountId, "BOURSO_OPS");
if (!result.success) {
toast.error(result.error);
} else {
toast.success(`${result.imported} transactions importées`);
}

Voir aussi