From d7374e4129d83df0647df328468a16d7d69be625 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Thu, 27 Nov 2025 10:13:16 +0100 Subject: [PATCH] feat: add new category synchronization script and expand default categories with detailed keywords for various sectors --- lib/defaults.ts | 846 +++++++++++++++++++++++++++++++++++-- package.json | 3 +- scripts/sync-categories.ts | 82 ++++ 3 files changed, 906 insertions(+), 25 deletions(-) create mode 100644 scripts/sync-categories.ts diff --git a/lib/defaults.ts b/lib/defaults.ts index 9f08499..e4552e4 100644 --- a/lib/defaults.ts +++ b/lib/defaults.ts @@ -1,62 +1,860 @@ -import type { Category, Folder } from "./types" +import type { Category, CategoryRule, Folder } from "./types" export const defaultCategories: Omit[] = [ + // ═══════════════════════════════════════════════════════════════════════════ + // ALIMENTATION & COURSES + // ═══════════════════════════════════════════════════════════════════════════ { name: "Alimentation", color: "#22c55e", icon: "shopping-cart", - keywords: ["carrefour", "leclerc", "auchan", "lidl", "supermarche", "boulangerie", "restaurant"], + keywords: [ + // Supermarchés & Hypermarchés + "carrefour", "leclerc", "auchan", "lidl", "intermarche", "super u", "hyper u", + "casino", "monoprix", "franprix", "simply market", "match", "cora", "geant", + "netto", "aldi", "leader price", "dia", "ed", "prisunic", "atac", + // Bio & Spécialisés + "biocoop", "naturalia", "la vie claire", "bio c bon", "natureo", "marcel et fils", + "grand frais", "picard", "thiriet", "maison thiriet", + // Proximité + "carrefour city", "carrefour express", "carrefour contact", "carrefour market", + "8 a huit", "proxy", "petit casino", "vival", "spar", "coccinelle", + "g20", "coccimarket", "utile", + // Générique + "supermarche", "hypermarche", "epicerie", "alimentation", "courses", + ], parentId: null, }, { - name: "Transport", + name: "Restaurants & Bars", + color: "#f97316", + icon: "utensils", + keywords: [ + // Fast food + "mcdonalds", "mcdonald", "burger king", "kfc", "quick", "subway", "five guys", + "dominos", "domino's", "pizza hut", "papa johns", + // Restauration rapide + "kebab", "tacos", "sushi", "wok", "poke bowl", "bagelstein", + "class croute", "brioche doree", "paul", "pomme de pain", "exki", + // Livraison + "uber eats", "ubereats", "deliveroo", "just eat", "frichti", + // Générique + "restaurant", "brasserie", "bistrot", "cafe", "bar", "pub", "snack", + "pizzeria", "traiteur", "cantine", + ], + parentId: null, + }, + { + name: "Boulangerie & Pâtisserie", + color: "#d97706", + icon: "croissant", + keywords: [ + "boulangerie", "patisserie", "pain", "viennoiserie", "farine", + "maison kayser", "eric kayser", "paul", "la mie caline", "marie blachere", + "ange", "feuillette", "le fournil", "au bon pain", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // TRANSPORT & MOBILITÉ + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Carburant", + color: "#64748b", + icon: "fuel", + keywords: [ + // Stations + "total", "totalenergies", "shell", "esso", "bp", "avia", "elf", + "intermarche station", "leclerc station", "carrefour station", + "auchan station", "super u station", + // Générique + "essence", "gasoil", "diesel", "carburant", "station service", "sp95", "sp98", + "sans plomb", "gazole", + ], + parentId: null, + }, + { + name: "Transports en commun", color: "#3b82f6", - icon: "car", - keywords: ["sncf", "ratp", "uber", "essence", "total", "parking", "peage"], + icon: "train", + keywords: [ + // Trains + "sncf", "ouigo", "tgv", "ter", "transilien", "intercites", "thalys", "eurostar", + "trenitalia", "db bahn", "renfe", + // Metro & Bus + "ratp", "navigo", "tisseo", "tcl", "rtm", "tan", "keolis", "transdev", + "tbc", "star", "tam", "cts", "tram", "tramway", "metro", "bus", + // Générique + "transport", "titre transport", "abonnement transport", + ], parentId: null, }, { - name: "Logement", + name: "VTC & Taxi", + color: "#1e3a8a", + icon: "car-taxi", + keywords: [ + "uber", "bolt", "kapten", "heetch", "freenow", "free now", + "taxi", "vtc", "chauffeur", "g7", "taxi bleu", "allocab", + ], + parentId: null, + }, + { + name: "Parking & Péages", + color: "#475569", + icon: "parking", + keywords: [ + // Parkings + "parking", "effia", "indigo", "q-park", "vinci park", "interparking", + "parcmetre", "parcmetr", "horodateur", + // Péages + "peage", "autoroute", "vinci autoroutes", "sanef", "aprr", "area", + "cofiroute", "escota", "sapn", "asf", "atmb", "sftrf", + "liber-t", "telepeage", "badge autoroute", + ], + parentId: null, + }, + { + name: "Location véhicule", + color: "#0891b2", + icon: "car-key", + keywords: [ + "hertz", "avis", "europcar", "enterprise", "sixt", "budget", "national", + "ada", "rent a car", "getaround", "ouicar", "drivy", + "location voiture", "location vehicule", + ], + parentId: null, + }, + { + name: "Mobilité douce", + color: "#10b981", + icon: "bike", + keywords: [ + // Vélos partagés + "velib", "velo'v", "bicloo", "v'lille", "velo bleu", + "lime", "dott", "tier", "bird", "circ", "voi", "wind", + // Trottinettes + "trottinette", "scooter electrique", + // Vélo + "decathlon cycle", "alltricks", "probikeshop", "velo", "cyclable", + ], + parentId: null, + }, + { + name: "Avion", + color: "#7c3aed", + icon: "plane", + keywords: [ + // Compagnies + "air france", "easyjet", "ryanair", "vueling", "transavia", "hop", + "lufthansa", "british airways", "klm", "iberia", "swiss", "tap", + "volotea", "norwegian", "wizz air", "emirates", "qatar airways", + // Aéroports + "aeroport", "cdg", "orly", "aeroports de paris", "adp", + // Réservation + "skyscanner", "kayak", "opodo", "liligo", "google flights", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // LOGEMENT & MAISON + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Loyer & Charges", color: "#f59e0b", icon: "home", - keywords: ["loyer", "edf", "engie", "eau", "assurance habitation"], + keywords: [ + "loyer", "charges locatives", "syndic", "copropriete", "asl", + "foncia", "nexity", "orpi", "century 21", "laforet", "guy hoquet", + "agence immobiliere", "bail", "quittance", + ], parentId: null, }, { - name: "Loisirs", - color: "#ec4899", - icon: "gamepad", - keywords: ["cinema", "netflix", "spotify", "fnac", "amazon"], + name: "Électricité & Gaz", + color: "#fbbf24", + icon: "zap", + keywords: [ + "edf", "engie", "total energie", "totalenergies", "eni", "enercoop", + "ilek", "mint energie", "planete oui", "ekwateur", "plum energie", + "electricite", "gaz", "energie", "compteur", "linky", "gazpar", + "direct energie", "cdiscount energie", "sowee", + ], parentId: null, }, { - name: "Santé", + name: "Eau", + color: "#06b6d4", + icon: "droplet", + keywords: [ + "veolia", "suez", "saur", "eau de paris", "sedif", + "eau", "facture eau", "compteur eau", "assainissement", + ], + parentId: null, + }, + { + name: "Bricolage & Jardinage", + color: "#84cc16", + icon: "hammer", + keywords: [ + // Bricolage + "leroy merlin", "castorama", "brico depot", "bricorama", "bricomarche", + "mr bricolage", "weldom", "bricoman", "point p", + // Jardinage + "jardiland", "gamm vert", "truffaut", "botanic", "villaverde", + // Générique + "bricolage", "outillage", "quincaillerie", "jardinage", "plante", + ], + parentId: null, + }, + { + name: "Ameublement & Déco", + color: "#a855f7", + icon: "sofa", + keywords: [ + "ikea", "conforama", "but", "alinea", "maisons du monde", + "la redoute", "am pm", "habitat", "roche bobois", "bo concept", + "fly", "mobiler de france", "monsieur meuble", + "meuble", "decoration", "deco", "literie", "matelas", + "emma matelas", "tediber", "simba", "hypnia", + ], + parentId: null, + }, + { + name: "Électroménager", + color: "#f43f5e", + icon: "refrigerator", + keywords: [ + "darty", "boulanger", "fnac", "electro depot", "ubaldi", + "electromenager", "lave linge", "lave vaisselle", "refrigerateur", + "aspirateur", "four", "micro onde", "cafetiere", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // SANTÉ & BIEN-ÊTRE + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Pharmacie", color: "#ef4444", - icon: "heart", - keywords: ["pharmacie", "medecin", "docteur", "hopital", "mutuelle"], + icon: "pill", + keywords: [ + "pharmacie", "parapharmacie", "pharma", "officine", + "docmorris", "shop pharmacie", "para", + "medicament", "ordonnance", + ], parentId: null, }, { - name: "Revenus", + name: "Médecins & Spécialistes", + color: "#dc2626", + icon: "stethoscope", + keywords: [ + "medecin", "docteur", "dr ", "cabinet medical", "consultation", + "generaliste", "specialiste", "dermatologue", "cardiologue", + "ophtalmologue", "ophtalmo", "dentiste", "orthodontiste", + "kine", "kinesitherapeute", "osteopathe", "psychologue", "psychiatre", + "doctolib", "maiia", "qare", "livi", + ], + parentId: null, + }, + { + name: "Hôpital & Clinique", + color: "#b91c1c", + icon: "hospital", + keywords: [ + "hopital", "clinique", "chu", "chru", "ap-hp", "aphp", + "urgences", "hospitalisation", "imagerie", "radiologie", + "scanner", "irm", "laboratoire", "labo analyse", "biologie", + ], + parentId: null, + }, + { + name: "Optique & Audition", + color: "#f87171", + icon: "glasses", + keywords: [ + "opticien", "lunette", "afflelou", "krys", "optical center", + "atol", "lissac", "generale optique", "grandoptical", "optic 2000", + "audioprothesiste", "audition", "appareil auditif", "audika", "amplifon", + "lentille", "verres", + ], + parentId: null, + }, + { + name: "Sport & Fitness", + color: "#14b8a6", + icon: "dumbbell", + keywords: [ + // Salles + "basic fit", "fitness park", "neoness", "keep cool", "magic form", + "club med gym", "cmg", "orange bleue", "gigafit", + // Sports + "piscine", "tennis", "golf", "escalade", "yoga", "pilates", + "crossfit", "cours collectif", "coach sportif", + ], + parentId: null, + }, + { + name: "Beauté & Soins", + color: "#ec4899", + icon: "sparkles", + keywords: [ + // Enseignes + "sephora", "nocibe", "marionnaud", "yves rocher", "lush", + "kiko", "mac cosmetics", "body shop", "rituals", + // Services + "coiffeur", "coiffure", "salon de coiffure", "barbier", + "estheticienne", "institut beaute", "manucure", "pedicure", + "spa", "massage", "epilation", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // LOISIRS & DIVERTISSEMENT + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Streaming & VOD", + color: "#e11d48", + icon: "tv", + keywords: [ + "netflix", "disney+", "disney plus", "amazon prime", "prime video", + "canal+", "canal plus", "ocs", "apple tv", "hbo", "paramount+", + "mycanal", "salto", "molotov", "adn", "crunchyroll", "wakanim", + "dazn", "rmc sport", "bein sport", + ], + parentId: null, + }, + { + name: "Musique & Podcasts", + color: "#22c55e", + icon: "music", + keywords: [ + "spotify", "deezer", "apple music", "amazon music", "tidal", + "soundcloud", "youtube music", "qobuz", "napster", + "audible", "podcast", "audiobook", + ], + parentId: null, + }, + { + name: "Cinéma & Spectacles", + color: "#8b5cf6", + icon: "film", + keywords: [ + // Cinéma + "ugc", "pathe", "gaumont", "mk2", "cgr", "kinepolis", "megarama", + "cinema", "cine", "allocine", "carte ugc", "carte pathe", + // Spectacles + "theatre", "opera", "concert", "spectacle", "comedie", + "zenith", "olympia", "bercy", "accor arena", "stade de france", + "fnac spectacles", "ticketmaster", "digitick", "billetreduc", + ], + parentId: null, + }, + { + name: "Jeux vidéo", + color: "#6366f1", + icon: "gamepad", + keywords: [ + "playstation", "psn", "xbox", "nintendo", "steam", "epic games", + "ea games", "ubisoft", "activision", "blizzard", + "micromania", "game", "jeux video", + ], + parentId: null, + }, + { + name: "Livres & Presse", + color: "#0ea5e9", + icon: "book", + keywords: [ + // Librairies + "fnac", "cultura", "gibert", "furet du nord", "decitre", "mollat", + "amazon kindle", "kobo", "kindle", + // Presse + "relay", "maison de la presse", "le monde", "le figaro", "liberation", + "cafeyn", "epresse", "kiosque", "magazine", "journal", "presse", + "librairie", "livre", "bouquin", + ], + parentId: null, + }, + { + name: "Sorties & Activités", + color: "#f472b6", + icon: "ticket", + keywords: [ + // Parcs + "disneyland", "parc asterix", "futuroscope", "puy du fou", "walibi", + // Musées + "musee", "museum", "exposition", "louvre", "orsay", + // Activités + "escape game", "bowling", "karting", "laser game", "paintball", + "accrobranche", "parc attractions", "zoo", "aquarium", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // SHOPPING & MODE + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Vêtements", + color: "#06b6d4", + icon: "shirt", + keywords: [ + // Fast fashion + "zara", "h&m", "hm", "primark", "kiabi", "c&a", "uniqlo", + "pull and bear", "bershka", "stradivarius", "mango", "new yorker", + // Moyenne gamme + "jules", "celio", "bonobo", "promod", "camaieu", "cache cache", + "pimkie", "jennyfer", "naf naf", "kookai", "etam", "morgan", + // Premium + "massimo dutti", "cos", "arket", "& other stories", "sandro", + "maje", "claudie pierlot", "ba&sh", "the kooples", "zadig voltaire", + // Sport + "decathlon", "go sport", "intersport", "sport 2000", "foot locker", + "courir", "jd sports", "nike", "adidas", "puma", + // Chaussures + "eram", "andre", "bocage", "minelli", "san marina", "geox", + "spartoo", "sarenza", "zalando", + // Générique + "vetement", "mode", "textile", "habillement", "chaussure", + ], + parentId: null, + }, + { + name: "High-Tech", + color: "#3b82f6", + icon: "smartphone", + keywords: [ + // Enseignes + "apple store", "apple", "samsung", "fnac", "darty", "boulanger", + "ldlc", "materiel.net", "grosbill", "top achat", "cdiscount", + // Marques + "iphone", "ipad", "macbook", "galaxy", "huawei", "xiaomi", "oneplus", + // Générique + "informatique", "ordinateur", "tablette", "telephone", "accessoire", + ], + parentId: null, + }, + { + name: "E-commerce", + color: "#f59e0b", + icon: "package", + keywords: [ + "amazon", "cdiscount", "rakuten", "aliexpress", "wish", "temu", "shein", + "veepee", "showroomprive", "privatesportshop", "bazarchic", + "leboncoin", "vinted", "vestiaire collective", "ebay", "etsy", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // ABONNEMENTS & TÉLÉCOMS + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Téléphonie & Internet", + color: "#8b5cf6", + icon: "wifi", + keywords: [ + // Opérateurs + "free", "free mobile", "orange", "sfr", "bouygues telecom", "sosh", + "red by sfr", "red sfr", "b&you", "nrj mobile", "prixtel", + "la poste mobile", "coriolis", "lebara", "lycamobile", + // Box + "freebox", "livebox", "bbox", "sfr box", + // Générique + "internet", "fibre", "adsl", "forfait mobile", "telephone mobile", "operateur", + ], + parentId: null, + }, + { + name: "Abonnements divers", + color: "#a78bfa", + icon: "repeat", + keywords: [ + // Cloud & stockage + "icloud", "google one", "dropbox", "onedrive", + // Productivité + "microsoft 365", "office 365", "adobe", "creative cloud", + // Autres + "abonnement", "mensualite", "prelevement", "cotisation", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // FINANCE & ASSURANCE + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Banque & Frais bancaires", + color: "#64748b", + icon: "landmark", + keywords: [ + // Banques traditionnelles + "bnp paribas", "societe generale", "credit agricole", "lcl", + "banque populaire", "caisse epargne", "credit mutuel", "cic", + "la banque postale", "hsbc", "axa banque", + // Banques en ligne + "boursorama", "fortuneo", "ing", "hello bank", "monabanq", + "bforbank", "revolut", "n26", "lydia", "nickel", + // Frais + "frais bancaire", "agios", "commission intervention", "cotisation carte", + "frais tenue compte", "frais virement", + ], + parentId: null, + }, + { + name: "Assurances", + color: "#0369a1", + icon: "shield", + keywords: [ + // Assureurs + "axa", "allianz", "maif", "macif", "maaf", "matmut", "gmf", + "groupama", "mma", "generali", "aviva", "swiss life", "ag2r", + "april", "afer", "direct assurance", "amv", + // Types + "assurance auto", "assurance habitation", "assurance vie", + "responsabilite civile", "garantie", "prime assurance", + ], + parentId: null, + }, + { + name: "Mutuelle & Prévoyance", + color: "#0891b2", + icon: "heart-pulse", + keywords: [ + // Mutuelles + "mutuelle", "harmonie mutuelle", "mgen", "malakoff humanis", + "alan", "luko", "april sante", "generali sante", + // Générique + "sante", "prevoyance", "complementaire sante", "remboursement soin", + ], + parentId: null, + }, + { + name: "Impôts & Taxes", + color: "#dc2626", + icon: "receipt", + keywords: [ + "impot", "dgfip", "tresor public", "fisc", "tax", "taxe", + "taxe habitation", "taxe fonciere", "impot revenu", + "prelevement source", "csg", "crds", "urssaf", + ], + parentId: null, + }, + { + name: "Épargne & Investissement", + color: "#16a34a", + icon: "piggy-bank", + keywords: [ + "livret a", "ldds", "lep", "pel", "cel", "epargne", + "assurance vie", "pea", "compte titre", "bourse", + "trade republic", "degiro", "boursorama bourse", "fortuneo bourse", + "etoro", "interactive brokers", "scalable capital", + "crypto", "bitcoin", "ethereum", "binance", "coinbase", "kraken", + ], + parentId: null, + }, + { + name: "Crédit & Emprunt", + color: "#991b1b", + icon: "banknote", + keywords: [ + "credit", "emprunt", "pret", "mensualite pret", "remboursement credit", + "sofinco", "cetelem", "cofidis", "oney", "floa", "younited", + "credit immobilier", "pret immo", "pret conso", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // REVENUS + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Salaire", color: "#10b981", icon: "wallet", - keywords: ["salaire", "virement recu", "remboursement"], + keywords: [ + "salaire", "paie", "paye", "virement salaire", "bulletin", + "net a payer", "remuneration", + ], parentId: null, }, { - name: "Abonnements", - color: "#8b5cf6", - icon: "repeat", - keywords: ["free", "orange", "sfr", "bouygues", "internet", "telephone"], + name: "Allocations & Aides", + color: "#34d399", + icon: "hand-coins", + keywords: [ + "caf", "allocations familiales", "apl", "rsa", "prime activite", + "pole emploi", "france travail", "are", "chomage", "indemnite", + "cpam", "secu", "securite sociale", "ameli", + ], parentId: null, }, { - name: "Shopping", - color: "#06b6d4", - icon: "bag", - keywords: ["zara", "h&m", "decathlon", "ikea"], + name: "Remboursements", + color: "#6ee7b7", + icon: "undo", + keywords: [ + "remboursement", "avoir", "retour", "rembourse", "credit note", + "annulation", "refund", + ], parentId: null, }, + { + name: "Revenus divers", + color: "#a7f3d0", + icon: "coins", + keywords: [ + "virement recu", "vir recu", "virement entrant", + "dividende", "interets", "loyer percu", "pension", + "retraite", "rente", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // VOYAGE & HÉBERGEMENT + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Hôtel & Hébergement", + color: "#a855f7", + icon: "bed", + keywords: [ + // Chaînes + "accor", "ibis", "novotel", "mercure", "sofitel", "pullman", + "b&b hotel", "premiere classe", "formule 1", "kyriad", "campanile", + "best western", "hilton", "marriott", "holiday inn", "radisson", + // Plateformes + "booking", "airbnb", "hotels.com", "expedia", "trivago", + "abritel", "vrbo", "gite", "chambre hote", + // Générique + "hotel", "hebergement", "nuitee", "reservation hotel", + ], + parentId: null, + }, + { + name: "Voyages & Séjours", + color: "#c084fc", + icon: "luggage", + keywords: [ + // Agences + "tui", "club med", "fram", "look voyage", "promovacances", + "voyage prive", "lastminute", "edreams", "go voyages", + // Générique + "voyage", "sejour", "vacances", "croisiere", "circuit", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // ÉDUCATION & ENFANTS + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Éducation & Formation", + color: "#0284c7", + icon: "graduation-cap", + keywords: [ + "ecole", "universite", "fac", "scolarite", "inscription", + "crous", "cantine scolaire", "periscolaire", + "formation", "cours", "udemy", "coursera", "openclassrooms", + "linkedin learning", "masterclass", + ], + parentId: null, + }, + { + name: "Enfants & Famille", + color: "#f472b6", + icon: "baby", + keywords: [ + // Puériculture + "aubert", "bebe 9", "orchestra", "vertbaudet", "dpam", "sergent major", + "jacadi", "petit bateau", "bonpoint", "cyrillus", + // Jouets + "toys r us", "king jouet", "joueclub", "la grande recre", "oxybul", + "lego", "playmobil", "jouet", "jeu enfant", + // Garde + "creche", "nounou", "assistante maternelle", "baby sitting", + "pajemploi", "cesu", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // ANIMAUX + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Animaux", + color: "#ea580c", + icon: "paw-print", + keywords: [ + // Animaleries + "animalis", "maxi zoo", "truffaut animalerie", "jardiland animalerie", + "zooplus", "bitiba", "croquetteland", + // Vétérinaire + "veterinaire", "veto", "clinique veterinaire", + // Générique + "croquettes", "alimentation animale", "accessoire animal", + "chien", "chat", "animal", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // AUTO & MOTO + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Entretien véhicule", + color: "#78716c", + icon: "wrench", + keywords: [ + // Centres auto + "norauto", "feu vert", "speedy", "midas", "euromaster", "point s", + "roady", "auto 5", "carter cash", + // Concessionnaires + "renault", "peugeot", "citroen", "volkswagen", "toyota", "ford", + "bmw", "mercedes", "audi", + // Générique + "garage", "reparation auto", "vidange", "revision", "controle technique", + "pneu", "freins", "entretien voiture", + ], + parentId: null, + }, + { + name: "Achat véhicule", + color: "#57534e", + icon: "car", + keywords: [ + "achat voiture", "achat vehicule", "leasing", "loa", "lld", + "arval", "alphabet", "ald automotive", + "la centrale", "leboncoin auto", "autoscout", "aramis", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // DONS & CADEAUX + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Dons & Charité", + color: "#fb7185", + icon: "heart-handshake", + keywords: [ + "don", "donation", "secours populaire", "restos du coeur", "emmaus", + "croix rouge", "medecins sans frontieres", "msf", "unicef", + "fondation", "association caritative", "solidarite", + ], + parentId: null, + }, + { + name: "Cadeaux", + color: "#f43f5e", + icon: "gift", + keywords: [ + "cadeau", "carte cadeau", "bon cadeau", "gift card", + "fleuriste", "interflora", "aquarelle", "florajet", + "bijouterie", "joaillerie", "pandora", "swarovski", "histoire d'or", + ], + parentId: null, + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // DIVERS + // ═══════════════════════════════════════════════════════════════════════════ + { + name: "Tabac & Jeux", + color: "#9ca3af", + icon: "cigarette", + keywords: [ + "tabac", "bureau tabac", "pmu", "fdj", "francaise des jeux", + "loto", "euromillions", "grattage", "pari sportif", "betclic", "winamax", + "cigarette", "vapoteuse", "ecig", + ], + parentId: null, + }, + { + name: "Retraits DAB", + color: "#71717a", + icon: "banknote", + keywords: [ + "retrait", "dab", "distributeur", "retrait especes", "retrait cb", + "cash", "liquide", + ], + parentId: null, + }, + { + name: "Virements & Transferts", + color: "#52525b", + icon: "arrow-right-left", + keywords: [ + "virement", "vir ", "transfert", "virement emis", "virement permanent", + "paypal", "wise", "western union", "moneygram", + ], + parentId: null, + }, + { + name: "Non catégorisé", + color: "#d4d4d8", + icon: "help-circle", + keywords: [], + parentId: null, + }, +] + +// ═══════════════════════════════════════════════════════════════════════════ +// RÈGLES DE CATÉGORISATION AVANCÉES +// ═══════════════════════════════════════════════════════════════════════════ +export const defaultCategoryRules: Omit[] = [ + // Ces règles seront associées aux catégories correspondantes lors du seeding + // Format: pattern à matcher, isRegex indique si c'est une expression régulière + + // Salaire - patterns typiques de virements salaire + { pattern: "^VIR(EMENT)? (RECU )?.*SALAIRE", isRegex: true }, + { pattern: "^VIR(EMENT)? (RECU )?.*PAIE", isRegex: true }, + + // Loyer - patterns de prélèvement loyer + { pattern: "^PRLV.*LOYER", isRegex: true }, + { pattern: "^PRLV.*FONCIA", isRegex: true }, + { pattern: "^PRLV.*NEXITY", isRegex: true }, + + // EDF/Engie + { pattern: "^PRLV.*EDF", isRegex: true }, + { pattern: "^PRLV.*ENGIE", isRegex: true }, + { pattern: "^PRLV.*TOTAL.?ENERGIE", isRegex: true }, + + // Télécom + { pattern: "^PRLV.*FREE( MOBILE)?", isRegex: true }, + { pattern: "^PRLV.*ORANGE", isRegex: true }, + { pattern: "^PRLV.*SFR", isRegex: true }, + { pattern: "^PRLV.*BOUYGUES", isRegex: true }, + + // Assurances + { pattern: "^PRLV.*AXA", isRegex: true }, + { pattern: "^PRLV.*MAIF", isRegex: true }, + { pattern: "^PRLV.*MACIF", isRegex: true }, + { pattern: "^PRLV.*MATMUT", isRegex: true }, + + // Impôts + { pattern: "^PRLV.*DGFIP", isRegex: true }, + { pattern: "^PRLV.*TRESOR PUBLIC", isRegex: true }, + { pattern: "IMPOT", isRegex: false }, + + // Remboursements + { pattern: "^VIR(EMENT)? (RECU )?.*CPAM", isRegex: true }, + { pattern: "^VIR(EMENT)? (RECU )?.*AMELI", isRegex: true }, + { pattern: "REMBOURSEMENT", isRegex: false }, + + // CAF + { pattern: "^VIR(EMENT)? (RECU )?.*CAF", isRegex: true }, + { pattern: "ALLOCATION", isRegex: false }, + + // Retraits + { pattern: "^RETRAIT DAB", isRegex: true }, + { pattern: "^RET DAB", isRegex: true }, ] export const defaultRootFolder: Folder = { diff --git a/package.json b/package.json index c86e0bd..7a2c93c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "db:push": "prisma db push", "db:migrate": "prisma migrate dev", "db:seed": "tsx prisma/seed.ts", - "db:studio": "prisma studio" + "db:studio": "prisma studio", + "db:sync-categories": "tsx scripts/sync-categories.ts" }, "prisma": { "seed": "tsx prisma/seed.ts" diff --git a/scripts/sync-categories.ts b/scripts/sync-categories.ts new file mode 100644 index 0000000..992542d --- /dev/null +++ b/scripts/sync-categories.ts @@ -0,0 +1,82 @@ +import { PrismaClient } from "@prisma/client" +import { defaultCategories } from "../lib/defaults" + +const prisma = new PrismaClient() + +async function main() { + console.log("🏷️ Synchronisation des catégories...") + console.log(` ${defaultCategories.length} catégories à synchroniser\n`) + + let created = 0 + let updated = 0 + let unchanged = 0 + + for (const category of defaultCategories) { + const existing = await prisma.category.findFirst({ + where: { name: category.name }, + }) + + 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 colorChanged = existing.color !== category.color + const iconChanged = existing.icon !== category.icon + + if (keywordsChanged || colorChanged || iconChanged) { + await prisma.category.update({ + where: { id: existing.id }, + data: { + color: category.color, + icon: category.icon, + keywords: JSON.stringify(category.keywords), + }, + }) + console.log(`✏️ Mise à jour: ${category.name}`) + if (keywordsChanged) { + console.log(` └─ Keywords: ${existingKeywords.length} → ${category.keywords.length}`) + } + updated++ + } else { + unchanged++ + } + } else { + await prisma.category.create({ + data: { + name: category.name, + color: category.color, + icon: category.icon, + keywords: JSON.stringify(category.keywords), + parentId: category.parentId, + }, + }) + console.log(`✅ Créée: ${category.name} (${category.keywords.length} keywords)`) + created++ + } + } + + console.log("\n" + "═".repeat(50)) + console.log("📊 Résumé:") + console.log(` ✅ Créées: ${created}`) + console.log(` ✏️ Mises à jour: ${updated}`) + console.log(` ⏭️ Inchangées: ${unchanged}`) + console.log("═".repeat(50)) + + // Stats finales + const totalCategories = await prisma.category.count() + const totalKeywords = defaultCategories.reduce((sum, c) => sum + c.keywords.length, 0) + console.log(`\n📈 Base de données:`) + console.log(` Total catégories: ${totalCategories}`) + console.log(` Total keywords: ${totalKeywords}`) +} + +main() + .catch((e) => { + console.error("❌ Erreur:", e) + process.exit(1) + }) + .finally(async () => { + await prisma.$disconnect() + }) +