chore: clean up code by removing trailing whitespace and ensuring consistent formatting across various files = prettier
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { prisma } from '../lib/prisma';
|
||||
import { transactionService } from '../services/transaction.service';
|
||||
import { generateId } from '../lib/store-db';
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { prisma } from "../lib/prisma";
|
||||
import { transactionService } from "../services/transaction.service";
|
||||
import { generateId } from "../lib/store-db";
|
||||
|
||||
interface CSVTransaction {
|
||||
date: string;
|
||||
@@ -22,44 +22,44 @@ interface CSVTransaction {
|
||||
|
||||
function parseCSVLine(line: string): string[] {
|
||||
const result: string[] = [];
|
||||
let current = '';
|
||||
let current = "";
|
||||
let inQuotes = false;
|
||||
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
const char = line[i];
|
||||
|
||||
|
||||
if (char === '"') {
|
||||
inQuotes = !inQuotes;
|
||||
} else if (char === ',' && !inQuotes) {
|
||||
} else if (char === "," && !inQuotes) {
|
||||
result.push(current.trim());
|
||||
current = '';
|
||||
current = "";
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result.push(current.trim());
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseCSV(csvPath: string): CSVTransaction[] {
|
||||
const content = fs.readFileSync(csvPath, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
const content = fs.readFileSync(csvPath, "utf-8");
|
||||
const lines = content.split("\n");
|
||||
|
||||
// Skip header lines (first 8 lines)
|
||||
const dataLines = lines.slice(8);
|
||||
|
||||
|
||||
const transactions: CSVTransaction[] = [];
|
||||
|
||||
|
||||
for (const line of dataLines) {
|
||||
if (!line.trim()) continue;
|
||||
|
||||
|
||||
const fields = parseCSVLine(line);
|
||||
if (fields.length < 13) continue;
|
||||
|
||||
|
||||
// Skip if date or amount is missing
|
||||
if (!fields[0] || !fields[1] || !fields[2]) continue;
|
||||
|
||||
|
||||
transactions.push({
|
||||
date: fields[0],
|
||||
amount: fields[1],
|
||||
@@ -76,102 +76,114 @@ function parseCSV(csvPath: string): CSVTransaction[] {
|
||||
compte: fields[12],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
function parseDate(dateStr: string): string {
|
||||
// Format: DD/MM/YYYY -> YYYY-MM-DD
|
||||
const [day, month, year] = dateStr.split('/');
|
||||
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
||||
const [day, month, year] = dateStr.split("/");
|
||||
return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function parseAmount(amountStr: string): number {
|
||||
if (!amountStr || amountStr.trim() === '' || amountStr === '""') {
|
||||
if (!amountStr || amountStr.trim() === "" || amountStr === '""') {
|
||||
return 0;
|
||||
}
|
||||
// Remove quotes, spaces (including non-breaking spaces), and replace comma with dot
|
||||
const cleaned = amountStr.replace(/["\s\u00A0]/g, '').replace(',', '.');
|
||||
const cleaned = amountStr.replace(/["\s\u00A0]/g, "").replace(",", ".");
|
||||
const parsed = parseFloat(cleaned);
|
||||
return isNaN(parsed) ? 0 : parsed;
|
||||
}
|
||||
|
||||
function generateFITID(transaction: CSVTransaction, index: number): string {
|
||||
const date = parseDate(transaction.date);
|
||||
const dateStr = date.replace(/-/g, '');
|
||||
const amountStr = Math.abs(parseAmount(transaction.amount)).toFixed(2).replace('.', '');
|
||||
const libelleHash = transaction.libelle.substring(0, 20).replace(/[^A-Z0-9]/gi, '');
|
||||
const dateStr = date.replace(/-/g, "");
|
||||
const amountStr = Math.abs(parseAmount(transaction.amount))
|
||||
.toFixed(2)
|
||||
.replace(".", "");
|
||||
const libelleHash = transaction.libelle
|
||||
.substring(0, 20)
|
||||
.replace(/[^A-Z0-9]/gi, "");
|
||||
return `${dateStr}-${amountStr}-${libelleHash}-${index}`;
|
||||
}
|
||||
|
||||
function removeAccountPrefix(accountName: string): string {
|
||||
// Remove prefixes: LivretA, LDDS, CCP, PEL (case insensitive)
|
||||
const prefixes = ['LivretA', 'Livret A', 'LDDS', 'CCP', 'PEL'];
|
||||
const prefixes = ["LivretA", "Livret A", "LDDS", "CCP", "PEL"];
|
||||
let cleaned = accountName;
|
||||
|
||||
|
||||
for (const prefix of prefixes) {
|
||||
// Remove prefix followed by optional spaces and dashes
|
||||
const regex = new RegExp(`^${prefix}\\s*-?\\s*`, 'i');
|
||||
cleaned = cleaned.replace(regex, '');
|
||||
const regex = new RegExp(`^${prefix}\\s*-?\\s*`, "i");
|
||||
cleaned = cleaned.replace(regex, "");
|
||||
}
|
||||
|
||||
|
||||
return cleaned.trim();
|
||||
}
|
||||
|
||||
function determineAccountType(accountName: string): "CHECKING" | "SAVINGS" | "CREDIT_CARD" | "OTHER" {
|
||||
function determineAccountType(
|
||||
accountName: string,
|
||||
): "CHECKING" | "SAVINGS" | "CREDIT_CARD" | "OTHER" {
|
||||
const upper = accountName.toUpperCase();
|
||||
if (upper.includes('LIVRET') || upper.includes('LDDS') || upper.includes('PEL')) {
|
||||
return 'SAVINGS';
|
||||
if (
|
||||
upper.includes("LIVRET") ||
|
||||
upper.includes("LDDS") ||
|
||||
upper.includes("PEL")
|
||||
) {
|
||||
return "SAVINGS";
|
||||
}
|
||||
if (upper.includes('CCP') || upper.includes('COMPTE COURANT')) {
|
||||
return 'CHECKING';
|
||||
if (upper.includes("CCP") || upper.includes("COMPTE COURANT")) {
|
||||
return "CHECKING";
|
||||
}
|
||||
return 'OTHER';
|
||||
return "OTHER";
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const csvPath = path.join(__dirname, '../temp/all account.csv');
|
||||
|
||||
const csvPath = path.join(__dirname, "../temp/all account.csv");
|
||||
|
||||
if (!fs.existsSync(csvPath)) {
|
||||
console.error(`Fichier CSV introuvable: ${csvPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Lecture du fichier CSV...');
|
||||
|
||||
console.log("Lecture du fichier CSV...");
|
||||
const csvTransactions = parseCSV(csvPath);
|
||||
console.log(`✓ ${csvTransactions.length} transactions trouvées`);
|
||||
|
||||
|
||||
// Group by account
|
||||
const accountsMap = new Map<string, CSVTransaction[]>();
|
||||
for (const transaction of csvTransactions) {
|
||||
if (!transaction.compte) continue;
|
||||
const amount = parseAmount(transaction.amount);
|
||||
if (amount === 0) continue; // Skip zero-amount transactions
|
||||
|
||||
|
||||
if (!accountsMap.has(transaction.compte)) {
|
||||
accountsMap.set(transaction.compte, []);
|
||||
}
|
||||
accountsMap.get(transaction.compte)!.push(transaction);
|
||||
}
|
||||
|
||||
|
||||
console.log(`✓ ${accountsMap.size} comptes trouvés\n`);
|
||||
|
||||
|
||||
let totalTransactionsCreated = 0;
|
||||
let totalAccountsCreated = 0;
|
||||
let totalAccountsUpdated = 0;
|
||||
|
||||
|
||||
// Process each account
|
||||
for (const [accountName, transactions] of accountsMap.entries()) {
|
||||
console.log(`Traitement du compte: ${accountName}`);
|
||||
console.log(` ${transactions.length} transactions`);
|
||||
|
||||
|
||||
// Remove prefixes and extract account number from account name
|
||||
const cleanedAccountName = removeAccountPrefix(accountName);
|
||||
const accountNumber = cleanedAccountName.replace(/[^A-Z0-9]/gi, '').substring(0, 22);
|
||||
const bankId = transactions[0]?.codeBanque || 'FR';
|
||||
|
||||
const accountNumber = cleanedAccountName
|
||||
.replace(/[^A-Z0-9]/gi, "")
|
||||
.substring(0, 22);
|
||||
const bankId = transactions[0]?.codeBanque || "FR";
|
||||
|
||||
console.log(` Numéro de compte extrait: ${accountNumber}`);
|
||||
|
||||
|
||||
// Find account by account number (try multiple strategies)
|
||||
let account = await prisma.account.findFirst({
|
||||
where: {
|
||||
@@ -179,7 +191,7 @@ async function main() {
|
||||
bankId: bankId,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// If not found with bankId, try without bankId constraint
|
||||
if (!account) {
|
||||
account = await prisma.account.findFirst({
|
||||
@@ -188,7 +200,7 @@ async function main() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// If still not found, try to find by account number in existing account numbers
|
||||
// (some accounts might have been created with prefixes in accountNumber)
|
||||
if (!account) {
|
||||
@@ -199,18 +211,18 @@ async function main() {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// Try to find exact match in accountNumber (after cleaning)
|
||||
for (const acc of allAccounts) {
|
||||
const cleanedExisting = removeAccountPrefix(acc.accountNumber);
|
||||
const existingNumber = cleanedExisting.replace(/[^A-Z0-9]/gi, '');
|
||||
const existingNumber = cleanedExisting.replace(/[^A-Z0-9]/gi, "");
|
||||
if (existingNumber === accountNumber) {
|
||||
account = acc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!account) {
|
||||
console.log(` → Création du compte...`);
|
||||
account = await prisma.account.create({
|
||||
@@ -221,37 +233,39 @@ async function main() {
|
||||
type: determineAccountType(accountName),
|
||||
folderId: null,
|
||||
balance: 0,
|
||||
currency: 'EUR',
|
||||
currency: "EUR",
|
||||
lastImport: null,
|
||||
externalUrl: null,
|
||||
},
|
||||
});
|
||||
totalAccountsCreated++;
|
||||
} else {
|
||||
console.log(` → Compte existant trouvé: ${account.name} (${account.accountNumber})`);
|
||||
console.log(
|
||||
` → Compte existant trouvé: ${account.name} (${account.accountNumber})`,
|
||||
);
|
||||
totalAccountsUpdated++;
|
||||
}
|
||||
|
||||
|
||||
// Sort transactions by date
|
||||
transactions.sort((a, b) => {
|
||||
const dateA = parseDate(a.date);
|
||||
const dateB = parseDate(b.date);
|
||||
return dateA.localeCompare(dateB);
|
||||
});
|
||||
|
||||
|
||||
// Deduplicate transactions: same amount + same date + same libelle (description)
|
||||
const seenTransactions = new Map<string, CSVTransaction>();
|
||||
const uniqueTransactions: CSVTransaction[] = [];
|
||||
let duplicatesCount = 0;
|
||||
|
||||
|
||||
for (const transaction of transactions) {
|
||||
const amount = parseAmount(transaction.amount);
|
||||
const date = parseDate(transaction.date);
|
||||
const description = transaction.libelle.trim();
|
||||
|
||||
|
||||
// Create a unique key: date-amount-description
|
||||
const key = `${date}-${amount}-${description}`;
|
||||
|
||||
|
||||
if (!seenTransactions.has(key)) {
|
||||
seenTransactions.set(key, transaction);
|
||||
uniqueTransactions.push(transaction);
|
||||
@@ -259,19 +273,24 @@ async function main() {
|
||||
duplicatesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (duplicatesCount > 0) {
|
||||
console.log(` → ${duplicatesCount} doublons détectés et ignorés (même date, montant, libellé)`);
|
||||
console.log(
|
||||
` → ${duplicatesCount} doublons détectés et ignorés (même date, montant, libellé)`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Calculate balance from unique transactions
|
||||
const balance = uniqueTransactions.reduce((sum, t) => sum + parseAmount(t.amount), 0);
|
||||
|
||||
const balance = uniqueTransactions.reduce(
|
||||
(sum, t) => sum + parseAmount(t.amount),
|
||||
0,
|
||||
);
|
||||
|
||||
// Prepare transactions for insertion
|
||||
const dbTransactions = uniqueTransactions.map((transaction, index) => {
|
||||
const amount = parseAmount(transaction.amount);
|
||||
const date = parseDate(transaction.date);
|
||||
|
||||
|
||||
// Build memo from available fields
|
||||
let memo = transaction.libelle;
|
||||
if (transaction.beneficiaire) {
|
||||
@@ -283,14 +302,14 @@ async function main() {
|
||||
if (transaction.commentaire) {
|
||||
memo += ` (${transaction.commentaire})`;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
id: generateId(),
|
||||
accountId: account.id,
|
||||
date: date,
|
||||
amount: amount,
|
||||
description: transaction.libelle.substring(0, 255),
|
||||
type: amount >= 0 ? 'CREDIT' as const : 'DEBIT' as const,
|
||||
type: amount >= 0 ? ("CREDIT" as const) : ("DEBIT" as const),
|
||||
categoryId: null, // Will be auto-categorized later if needed
|
||||
isReconciled: false,
|
||||
fitId: generateFITID(transaction, index),
|
||||
@@ -298,12 +317,12 @@ async function main() {
|
||||
checkNum: transaction.numeroCheque || undefined,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// Insert transactions (will skip duplicates based on fitId)
|
||||
const result = await transactionService.createMany(dbTransactions);
|
||||
console.log(` → ${result.count} nouvelles transactions insérées`);
|
||||
totalTransactionsCreated += result.count;
|
||||
|
||||
|
||||
// Update account balance and lastImport
|
||||
await prisma.account.update({
|
||||
where: { id: account.id },
|
||||
@@ -312,21 +331,20 @@ async function main() {
|
||||
lastImport: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
console.log(` ✓ Solde mis à jour: ${balance.toFixed(2)} EUR\n`);
|
||||
}
|
||||
|
||||
console.log('\n=== Résumé ===');
|
||||
|
||||
console.log("\n=== Résumé ===");
|
||||
console.log(`Comptes créés: ${totalAccountsCreated}`);
|
||||
console.log(`Comptes mis à jour: ${totalAccountsUpdated}`);
|
||||
console.log(`Transactions insérées: ${totalTransactionsCreated}`);
|
||||
console.log('\n✓ Import terminé!');
|
||||
|
||||
console.log("\n✓ Import terminé!");
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Erreur:', error);
|
||||
console.error("Erreur:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user