import { PrismaClient } from "@prisma/client" import { defaultCategories, type CategoryDefinition } from "../lib/defaults" const prisma = new PrismaClient() async function main() { console.log("🏷️ Synchronisation des catΓ©gories hiΓ©rarchiques...") console.log(` ${defaultCategories.length} catΓ©gories Γ  synchroniser\n`) // ═══════════════════════════════════════════════════════════════════════════ // PHASE 0: Nettoyage des doublons existants // ═══════════════════════════════════════════════════════════════════════════ console.log("═".repeat(50)) console.log("PHASE 0: Nettoyage des doublons") console.log("═".repeat(50)) const allExisting = await prisma.category.findMany() const byNormalizedName = new Map() for (const cat of allExisting) { const normalized = normalizeName(cat.name) if (!byNormalizedName.has(normalized)) { byNormalizedName.set(normalized, []) } byNormalizedName.get(normalized)!.push(cat) } let merged = 0 for (const [_normalized, cats] of byNormalizedName) { if (cats.length > 1) { // Garder celui avec emoji let keeper = cats[0] for (const cat of cats) { if (/[\u{1F300}-\u{1F9FF}]/u.test(cat.name)) { keeper = cat break } } const toDelete: typeof cats = [] for (const cat of cats) { if (cat.id !== keeper.id) toDelete.push(cat) } for (const dup of toDelete) { // TransfΓ©rer transactions await prisma.transaction.updateMany({ where: { categoryId: dup.id }, data: { categoryId: keeper.id }, }) // TransfΓ©rer enfants await prisma.category.updateMany({ where: { parentId: dup.id }, data: { parentId: keeper.id }, }) // Supprimer doublon await prisma.category.delete({ where: { id: dup.id } }) console.log(`πŸ—‘οΈ FusionnΓ©: "${dup.name}" β†’ "${keeper.name}"`) merged++ } } } console.log(merged > 0 ? ` ${merged} doublons fusionnΓ©s` : " Aucun doublon βœ“") // SΓ©parer parents et enfants const parentCategories = defaultCategories.filter((c) => c.parentSlug === null) const childCategories = defaultCategories.filter((c) => c.parentSlug !== null) console.log(`\nπŸ“ ${parentCategories.length} catΓ©gories parentes`) console.log(` └─ ${childCategories.length} sous-catΓ©gories\n`) // Map slug -> id (pour rΓ©soudre les parentId) const slugToId = new Map() let created = 0 let updated = 0 let unchanged = 0 // ═══════════════════════════════════════════════════════════════════════════ // PHASE 1: CrΓ©er/MAJ les catΓ©gories parentes // ═══════════════════════════════════════════════════════════════════════════ console.log("═".repeat(50)) console.log("PHASE 1: CatΓ©gories parentes") console.log("═".repeat(50)) for (const category of parentCategories) { const result = await upsertCategory(category, null) slugToId.set(category.slug, result.id) if (result.action === "created") created++ else if (result.action === "updated") updated++ else unchanged++ } // ═══════════════════════════════════════════════════════════════════════════ // PHASE 2: CrΓ©er/MAJ les sous-catΓ©gories // ═══════════════════════════════════════════════════════════════════════════ console.log("\n" + "═".repeat(50)) console.log("PHASE 2: Sous-catΓ©gories") console.log("═".repeat(50)) for (const category of childCategories) { const parentId = slugToId.get(category.parentSlug!) if (!parentId) { console.log(`⚠️ Parent introuvable pour: ${category.name} (parentSlug: ${category.parentSlug})`) continue } const result = await upsertCategory(category, parentId) slugToId.set(category.slug, result.id) if (result.action === "created") created++ else if (result.action === "updated") updated++ else unchanged++ } // ═══════════════════════════════════════════════════════════════════════════ // RΓ‰SUMΓ‰ // ═══════════════════════════════════════════════════════════════════════════ console.log("\n" + "═".repeat(50)) console.log("πŸ“Š RΓ‰SUMΓ‰ CATΓ‰GORIES:") console.log("═".repeat(50)) console.log(` βœ… Créées: ${created}`) console.log(` ✏️ Mises Γ  jour: ${updated}`) console.log(` ⏭️ InchangΓ©es: ${unchanged}`) // Stats finales const totalCategories = await prisma.category.count() const parentCount = await prisma.category.count({ where: { parentId: null } }) const childCount = await prisma.category.count({ where: { NOT: { parentId: null } } }) const totalKeywords = defaultCategories.reduce((sum, c) => sum + c.keywords.length, 0) console.log("\nπŸ“ˆ Base de donnΓ©es:") console.log(` Total catΓ©gories: ${totalCategories} (${parentCount} parents, ${childCount} enfants)`) console.log(` Total keywords: ${totalKeywords}`) } // Normaliser un nom (enlever emojis, espaces multiples, lowercase) function normalizeName(name: string): string { return name .replace(/[\u{1F300}-\u{1F9FF}]/gu, "") // Remove emojis .replace(/[^\w\sΓ€-ΓΏ]/g, "") // Keep only alphanumeric and accents .replace(/\s+/g, " ") .toLowerCase() .trim() } async function upsertCategory( category: CategoryDefinition, parentId: string | null ): Promise<{ id: string; action: "created" | "updated" | "unchanged" }> { // Chercher par nom exact d'abord let existing = await prisma.category.findFirst({ where: { name: category.name }, }) // Si pas trouvΓ©, chercher par nom normalisΓ© (sans emoji) dans TOUTES les catΓ©gories if (!existing) { const allCategories = await prisma.category.findMany() const normalizedTarget = normalizeName(category.name) for (const cat of allCategories) { if (normalizeName(cat.name) === normalizedTarget) { existing = cat console.log(` πŸ”— Match normalisΓ©: "${cat.name}" β†’ "${category.name}"`) break } } } if (existing) { // Comparer pour voir si mise Γ  jour nΓ©cessaire const existingKeywords = JSON.parse(existing.keywords) as string[] const keywordsChanged = JSON.stringify(existingKeywords.sort()) !== JSON.stringify([...category.keywords].sort()) const nameChanged = existing.name !== category.name const colorChanged = existing.color !== category.color const iconChanged = existing.icon !== category.icon const parentChanged = existing.parentId !== parentId if (nameChanged || keywordsChanged || colorChanged || iconChanged || parentChanged) { await prisma.category.update({ where: { id: existing.id }, data: { name: category.name, // Met Γ  jour le nom aussi (ajout emoji) color: category.color, icon: category.icon, keywords: JSON.stringify(category.keywords), parentId: parentId, }, }) console.log(`✏️ MAJ: ${existing.name}${nameChanged ? ` β†’ ${category.name}` : ""}`) if (keywordsChanged) { console.log(` └─ Keywords: ${existingKeywords.length} β†’ ${category.keywords.length}`) } if (parentChanged) { console.log(` └─ Parent modifiΓ©`) } return { id: existing.id, action: "updated" } } return { id: existing.id, action: "unchanged" } } // CrΓ©er nouvelle catΓ©gorie const created = await prisma.category.create({ data: { name: category.name, color: category.color, icon: category.icon, keywords: JSON.stringify(category.keywords), parentId: parentId, }, }) console.log(`βœ… Créée: ${category.name}${category.keywords.length > 0 ? ` (${category.keywords.length} keywords)` : ""}`) return { id: created.id, action: "created" } } main() .catch((e) => { console.error("❌ Erreur:", e) process.exit(1) }) .finally(async () => { await prisma.$disconnect() })