csv-to-timemanager.py — Spécifications d'importation ==================================================== Objectif -------- Le script `csv-to-timemanager.py` lit des fichiers CSV configurés dans `config.json` et envoie des requêtes vers l'API Nextcloud TimeManager pour créer ou mettre à jour : - clients, - projets, - tâches, - entrées de temps. Il utilise aussi une base MySQL locale pour vérifier l'existence des objets et éviter les doublons. Sources des données ------------------- Le script traite les entrées de chaque CSV listé sous `config['calendars']`. Chaque entrée doit contenir au minimum : - `csv_name` : chemin du fichier CSV à importer, - `user_name` : login Nextcloud utilisé pour authentifier les requêtes, - `api_password` : mot de passe ou mot de passe d'application Nextcloud. > Il est important de générer la clé API en s'étant connecté avec l'identifiant et non l'email car le script user_name utilise le champ "user_name" pour faire des vérifications en bases de données avant d'ajouter des données. Colonnes CSV utilisées ---------------------- Pour chaque ligne du CSV, le script utilise précisément ces colonnes : - `Description` → nom du client TimeManager - `Categories` → nom du projet TimeManager - `Titre` → nom de la tâche TimeManager - `Date debut` → date de début de l'entrée de temps - `Heure debut` → heure de début de l'entrée de temps - `Date fin` → date de fin de l'entrée de temps - `Heure fin` → heure de fin de l'entrée de temps Correspondance vers TimeManager -------------------------------- | Colonne CSV | Entité TimeManager | Champ TimeManager / usage | |------------------|--------------------|---------------------------| | `Description` | client | `name` du client | | `Categories` | project | `name` du projet | | `Titre` | task | `name` de la tâche | | `Date debut` | time entry | `start` (date + heure) | | `Heure debut` | time entry | `start` (date + heure) | | `Date fin` | time entry | `end` (date + heure) | | `Heure fin` | time entry | `end` (date + heure) | Traitements appliqués ---------------------- 1. Lecture du CSV - Le fichier est lu avec `csv.reader(..., delimiter=';')`. - La première ligne est traitée comme en-tête et sert à associer chaque colonne à une clé de dictionnaire. 2. Nettoyage des chaînes de texte - `sanitize(chain)` : - retire tous les caractères sauf lettres, chiffres et espaces, - applique `unidecode` pour normaliser les accents, - `strip()` pour enlever les espaces en début/fin, - `capitalize()` pour mettre la première lettre en majuscule. - `sanitize_date(chain)` : - conserve aussi les caractères `-` et `:` supplémentaires (utile pour les dates/heures), - applique les mêmes normalisations que `sanitize`. 3. Création / existence d'objets - **Chaque entité est créée indépendamment** si elle n'existe pas : - Si le client `Description` n'existe pas, il est créé via `create_client()`. - Si le projet `Categories` n'existe pas (pour ce client), il est créé via `create_project()`. - Si la tâche `Titre` n'existe pas (pour ce projet), elle est créée via `create_task()`. - Les actions de création appellent l'API `updateObjects` avec un payload minimal contenant l'entité concernée. 4. Insertion d'une entrée de temps - Le script construit `start` et `end` en concaténant `Date debut + Heure debut` et `Date fin + Heure fin`. - Il envoie ensuite un objet `times.created` à l'API `updateObjects`. Validation et gestion des doublons --------------------------------- - Avant d'ajouter une entrée de temps, le script vérifie si une entrée identique existe déjà dans la base : - il cherche d'abord une entrée avec les mêmes `user_id`, `start` (date+heure début) et `end` (date+heure fin). - si une telle entrée existe, il valide que le `task`, le `project` et le `client` correspondent. - si tous les critères correspondent, l'import est ignoré pour éviter un doublon. - **Important** : deux entrées avec les mêmes client/projet/tâche mais à des **heures différentes** créent 2 entrées TimeManager distinctes (elles ne sont pas considérées comme des doublons). Gestion des données manquantes ----------------------------- - Si `user_name` ou `api_password` manque dans la configuration pour un calendrier, ce calendrier est ignoré. - Dans le CSV, si `client_name` (`Description`) et `project_name` (`Categories`) sont tous deux vides, la ligne est ignorée. - Si le client existe mais que son UUID ne peut pas être récupéré après création, le traitement de la ligne est abandonné avec un message d'erreur. - **Limitations actuelles (risque)** : Le script ne valide pas que `Titre`, `Date debut`, `Heure debut`, `Date fin` ou `Heure fin` sont présents : - Si `Titre` est vide, une tâche avec un nom vide peut être créée ou recherchée. - Si `Date debut`, `Heure debut`, `Date fin` ou `Heure fin` sont vides, une entrée de temps avec `start` ou `end` incomplet sera créée. - Ce comportement est **indésirable** et devrait être amélioré (voir `docs/todo.md`). Cas particuliers ---------------- - Pour l'existence d'un client/projet/tâche, la comparaison se fait avec les noms nettoyés (`sanitize`) et uniquement les objets dont `status != "deleted"`. - `create_client()` retourne maintenant l'UUID extrait de la réponse API si disponible. Si la réponse ne contient pas d'UUID, le script effectue une seconde récupération via la base de données. - `create_project()` et `create_task()` ne retournent pas d'UUID : ils comptent sur les recherches suivantes (`get_project_uuid_by_name`, `get_task_uuid_by_name`) pour retrouver l'objet créé. Notes d'implémentation ---------------------- - Les requêtes vers TimeManager utilisent toujours `POST /api/updateObjects`. - Les objets envoyés contiennent des champs vides (`uuid`, `commit`, `created`, `changed`) pour laisser le serveur les remplir si nécessaire. - Le script n'envoie pas de données de `deleted` ou `updated` : il se contente de créer des objets dans `created`. Résumé rapide ------------- Le script transforme chaque ligne CSV en un client/projet/tâche puis une entrée de temps, en appliquant un nettoyage de texte simple et en évitant le double-import des mêmes périodes. Champs obligatoires pour créer une entrée de temps ------------------------------------------------- Pour qu'une ligne CSV soit importée avec succès en tant qu'entrée de temps TimeManager, les champs suivants **doivent** être renseignés : - `Description` : non vide (nom du client) OU `Categories` : non vide (nom du projet) — au moins un des deux. - `Categories` : non vide (nom du projet). - `Titre` : non vide (nom de la tâche). - `Date debut` : non vide et valide. - `Heure debut` : non vide et valide. - `Date fin` : non vide et valide. - `Heure fin` : non vide et valide. **Note actuellement** : le script ne valide pas strictement ces conditions. Il est recommandé d'améliorer ce point (voir `docs/todo.md`). Le point clef est que seules les colonnes suivantes sont exploitées : `Description`, `Categories`, `Titre`, `Date debut`, `Heure debut`, `Date fin`, `Heure fin`.