refactor: remove category rules and related logic from defaults, types, and services for cleaner codebase

This commit is contained in:
Julien Froidefond
2025-11-27 10:41:10 +01:00
parent 2aafce4df0
commit cf109984e5
6 changed files with 5 additions and 135 deletions

View File

@@ -1,4 +1,3 @@
import type { CategoryRule } from "./types"
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
// STRUCTURE HIÉRARCHIQUE DES CATÉGORIES // STRUCTURE HIÉRARCHIQUE DES CATÉGORIES
@@ -986,58 +985,6 @@ export const defaultCategories: CategoryDefinition[] = [
}, },
] ]
// ═══════════════════════════════════════════════════════════════════════════
// RÈGLES DE CATÉGORISATION AVANCÉES
// ═══════════════════════════════════════════════════════════════════════════
export interface CategoryRuleDefinition extends Omit<CategoryRule, "id" | "categoryId"> {
categorySlug: string // Référence au slug de la catégorie
}
export const defaultCategoryRules: CategoryRuleDefinition[] = [
// Salaire - patterns typiques de virements salaire
{ categorySlug: "revenus-salaire", pattern: "^VIR(EMENT)? (RECU )?.*SALAIRE", isRegex: true },
{ categorySlug: "revenus-salaire", pattern: "^VIR(EMENT)? (RECU )?.*PAIE", isRegex: true },
// Loyer - patterns de prélèvement loyer
{ categorySlug: "logement-loyer", pattern: "^PRLV.*LOYER", isRegex: true },
{ categorySlug: "logement-loyer", pattern: "^PRLV.*FONCIA", isRegex: true },
{ categorySlug: "logement-loyer", pattern: "^PRLV.*NEXITY", isRegex: true },
// EDF/Engie
{ categorySlug: "logement-electricite", pattern: "^PRLV.*EDF", isRegex: true },
{ categorySlug: "logement-electricite", pattern: "^PRLV.*ENGIE", isRegex: true },
{ categorySlug: "logement-electricite", pattern: "^PRLV.*TOTAL.?ENERGIE", isRegex: true },
// Télécom
{ categorySlug: "abonnements-telecom", pattern: "^PRLV.*FREE( MOBILE)?", isRegex: true },
{ categorySlug: "abonnements-telecom", pattern: "^PRLV.*ORANGE", isRegex: true },
{ categorySlug: "abonnements-telecom", pattern: "^PRLV.*SFR", isRegex: true },
{ categorySlug: "abonnements-telecom", pattern: "^PRLV.*BOUYGUES", isRegex: true },
// Assurances
{ categorySlug: "finance-assurance", pattern: "^PRLV.*AXA", isRegex: true },
{ categorySlug: "finance-assurance", pattern: "^PRLV.*MAIF", isRegex: true },
{ categorySlug: "finance-assurance", pattern: "^PRLV.*MACIF", isRegex: true },
{ categorySlug: "finance-assurance", pattern: "^PRLV.*MATMUT", isRegex: true },
// Impôts
{ categorySlug: "finance-impots", pattern: "^PRLV.*DGFIP", isRegex: true },
{ categorySlug: "finance-impots", pattern: "^PRLV.*TRESOR PUBLIC", isRegex: true },
{ categorySlug: "finance-impots", pattern: "IMPOT", isRegex: false },
// Remboursements
{ categorySlug: "revenus-remboursements", pattern: "^VIR(EMENT)? (RECU )?.*CPAM", isRegex: true },
{ categorySlug: "revenus-remboursements", pattern: "^VIR(EMENT)? (RECU )?.*AMELI", isRegex: true },
{ categorySlug: "revenus-remboursements", pattern: "REMBOURSEMENT", isRegex: false },
// CAF
{ categorySlug: "revenus-allocations", pattern: "^VIR(EMENT)? (RECU )?.*CAF", isRegex: true },
{ categorySlug: "revenus-allocations", pattern: "ALLOCATION", isRegex: false },
// Retraits
{ categorySlug: "divers-retraits", pattern: "^RETRAIT DAB", isRegex: true },
{ categorySlug: "divers-retraits", pattern: "^RET DAB", isRegex: true },
]
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
// DOSSIER RACINE // DOSSIER RACINE

View File

@@ -48,7 +48,6 @@ const defaultData: BankingData = {
transactions: [], transactions: [],
folders: [defaultRootFolder], folders: [defaultRootFolder],
categories: buildCategoriesFromDefaults(), categories: buildCategoriesFromDefaults(),
categoryRules: [],
} }
export function loadData(): BankingData { export function loadData(): BankingData {

View File

@@ -43,19 +43,11 @@ export interface Category {
parentId: string | null parentId: string | null
} }
export interface CategoryRule {
id: string
categoryId: string
pattern: string
isRegex: boolean
}
export interface BankingData { export interface BankingData {
accounts: Account[] accounts: Account[]
transactions: Transaction[] transactions: Transaction[]
folders: Folder[] folders: Folder[]
categories: Category[] categories: Category[]
categoryRules: CategoryRule[]
} }
// OFX Parsed types // OFX Parsed types

View File

@@ -79,23 +79,9 @@ model Category {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
parent Category? @relation("CategoryHierarchy", fields: [parentId], references: [id], onDelete: Cascade) parent Category? @relation("CategoryHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
children Category[] @relation("CategoryHierarchy") children Category[] @relation("CategoryHierarchy")
transactions Transaction[] transactions Transaction[]
rules CategoryRule[]
@@index([parentId]) @@index([parentId])
} }
model CategoryRule {
id String @id @default(cuid())
categoryId String
pattern String
isRegex Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
@@index([categoryId])
}

View File

@@ -1,5 +1,5 @@
import { PrismaClient } from "@prisma/client" import { PrismaClient } from "@prisma/client"
import { defaultCategories, defaultCategoryRules, type CategoryDefinition } from "../lib/defaults" import { defaultCategories, type CategoryDefinition } from "../lib/defaults"
const prisma = new PrismaClient() const prisma = new PrismaClient()
@@ -113,51 +113,6 @@ async function main() {
else unchanged++ else unchanged++
} }
// ═══════════════════════════════════════════════════════════════════════════
// PHASE 3: Sync des règles (optionnel)
// ═══════════════════════════════════════════════════════════════════════════
if (defaultCategoryRules.length > 0) {
console.log("\n" + "═".repeat(50))
console.log("PHASE 3: Règles de catégorisation")
console.log("═".repeat(50))
let rulesCreated = 0
let rulesSkipped = 0
for (const rule of defaultCategoryRules) {
const categoryId = slugToId.get(rule.categorySlug)
if (!categoryId) {
console.log(`⚠️ Catégorie introuvable pour règle: ${rule.categorySlug}`)
rulesSkipped++
continue
}
// Vérifier si la règle existe déjà
const existing = await prisma.categoryRule.findFirst({
where: {
categoryId,
pattern: rule.pattern,
},
})
if (!existing) {
await prisma.categoryRule.create({
data: {
categoryId,
pattern: rule.pattern,
isRegex: rule.isRegex,
},
})
console.log(`✅ Règle créée: ${rule.pattern.substring(0, 40)}...`)
rulesCreated++
} else {
rulesSkipped++
}
}
console.log(`\n📊 Règles: ${rulesCreated} créées, ${rulesSkipped} existantes`)
}
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
// RÉSUMÉ // RÉSUMÉ
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════
@@ -172,12 +127,10 @@ async function main() {
const totalCategories = await prisma.category.count() const totalCategories = await prisma.category.count()
const parentCount = await prisma.category.count({ where: { parentId: null } }) const parentCount = await prisma.category.count({ where: { parentId: null } })
const childCount = await prisma.category.count({ where: { NOT: { parentId: null } } }) const childCount = await prisma.category.count({ where: { NOT: { parentId: null } } })
const totalRules = await prisma.categoryRule.count()
const totalKeywords = defaultCategories.reduce((sum, c) => sum + c.keywords.length, 0) const totalKeywords = defaultCategories.reduce((sum, c) => sum + c.keywords.length, 0)
console.log("\n📈 Base de données:") console.log("\n📈 Base de données:")
console.log(` Total catégories: ${totalCategories} (${parentCount} parents, ${childCount} enfants)`) console.log(` Total catégories: ${totalCategories} (${parentCount} parents, ${childCount} enfants)`)
console.log(` Total règles: ${totalRules}`)
console.log(` Total keywords: ${totalKeywords}`) console.log(` Total keywords: ${totalKeywords}`)
} }

View File

@@ -1,9 +1,9 @@
import { prisma } from "@/lib/prisma" import { prisma } from "@/lib/prisma"
import type { BankingData, Account, Transaction, Folder, Category, CategoryRule } from "@/lib/types" import type { BankingData, Account, Transaction, Folder, Category } from "@/lib/types"
export const bankingService = { export const bankingService = {
async getAllData(): Promise<BankingData> { async getAllData(): Promise<BankingData> {
const [accounts, transactions, folders, categories, categoryRules] = await Promise.all([ const [accounts, transactions, folders, categories] = await Promise.all([
prisma.account.findMany({ prisma.account.findMany({
include: { include: {
folder: true, folder: true,
@@ -17,7 +17,6 @@ export const bankingService = {
}), }),
prisma.folder.findMany(), prisma.folder.findMany(),
prisma.category.findMany(), prisma.category.findMany(),
prisma.categoryRule.findMany(),
]) ])
// Transform Prisma models to match our types // Transform Prisma models to match our types
@@ -61,12 +60,6 @@ export const bankingService = {
keywords: JSON.parse(c.keywords) as string[], keywords: JSON.parse(c.keywords) as string[],
parentId: c.parentId, parentId: c.parentId,
})), })),
categoryRules: categoryRules.map((r): CategoryRule => ({
id: r.id,
categoryId: r.categoryId,
pattern: r.pattern,
isRegex: r.isRegex,
})),
} }
}, },
} }