Files
Lesstime/docs/superpowers/specs/2026-03-19-zimbra-calendar-design.md
Matthieu df58b09c2e docs : add Zimbra CalDAV calendar integration design spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 18:10:34 +01:00

8.8 KiB

Intégration Calendrier Zimbra CalDAV

Date : 2026-03-19 Statut : Validé

Objectif

Permettre de synchroniser les tâches Lesstime vers un calendrier Zimbra OVH via CalDAV. Sync one-way (push uniquement), avec support des tâches récurrentes.

Principes

  • Push uniquement : Lesstime pousse vers Zimbra, ne récupère jamais les événements existants
  • Opt-in : les tâches ne sont pas envoyées au calendrier par défaut (checkbox décochée)
  • Sync synchrone : les appels CalDAV se font au moment de l'action, timeout 5s
  • Configuration globale : un seul compte Zimbra admin pour toute l'instance
  • Calendrier d'équipe : toutes les tâches sync vont dans le même calendrier

Modèle de données

Nouveaux champs sur Task

Champ Type Nullable Default Description
scheduledStart DateTimeImmutable oui null Début du créneau planifié
scheduledEnd DateTimeImmutable oui null Fin du créneau planifié
deadline DateTimeImmutable oui null Date d'échéance
syncToCalendar bool non false Opt-in pour la sync Zimbra
calendarEventUid string oui null UID du VEVENT dans Zimbra
calendarTodoUid string oui null UID du VTODO dans Zimbra

Nouvelle entité TaskRecurrence

Champ Type Nullable Description
id int non PK auto-increment
type string non Enum : daily, weekly, monthly, yearly
interval int non Tous les X (jours/semaines/mois/ans)
daysOfWeek json oui Jours de la semaine pour hebdo, ex: ["monday","wednesday"]
dayOfMonth int oui Jour du mois pour mensuel, ex: 15
weekOfMonth int oui Semaine du mois, ex: 1 pour "le 1er X du mois"
endDate Date oui Fin de la récurrence (null = infini)
maxOccurrences int oui Nombre max d'occurrences (alternatif à endDate)
occurrenceCount int non Compteur d'occurrences créées (default 0)

Relations

  • Task.recurrenceManyToOne vers TaskRecurrence (nullable)
  • TaskRecurrence.tasksOneToMany vers Task

Nouvelle entité ZimbraConfiguration

Champ Type Nullable Description
id int non PK auto-increment
serverUrl string non URL CalDAV Zimbra
username string non Compte Zimbra
password string non Mot de passe (chiffré en BDD via sodium)
calendarPath string non Chemin du calendrier, ex: /Calendar/
enabled bool non Activer/désactiver la sync (default false)

Service CalDAV

CalDavService

Dépendances : sabre/vobject pour la génération ICS, requêtes HTTP via Symfony\Contracts\HttpClient.

Méthodes

  • createEvent(Task): string — crée un VEVENT (créneau planifié), retourne l'UID
  • createTodo(Task): string — crée un VTODO (deadline), retourne l'UID
  • updateEvent(Task): void — met à jour le VEVENT existant
  • updateTodo(Task): void — met à jour le VTODO existant
  • deleteEvent(string $uid): void — supprime le VEVENT par UID
  • deleteTodo(string $uid): void — supprime le VTODO par UID
  • testConnection(): bool — teste la connexion CalDAV

Format VEVENT (créneau planifié)

BEGIN:VEVENT
UID:{calendarEventUid}
SUMMARY:[PROJET-NUM] Titre de la tâche
DTSTART:{scheduledStart}
DTEND:{scheduledEnd}
DESCRIPTION:{description}\n\nLesstime: {url}
RRULE:{rrule si récurrence}
END:VEVENT

Format VTODO (deadline)

BEGIN:VTODO
UID:{calendarTodoUid}
SUMMARY:[PROJET-NUM] Titre de la tâche (deadline)
DUE:{deadline}
DESCRIPTION:{description}\n\nLesstime: {url}
END:VTODO

Pas de RRULE sur le VTODO — il suit la tâche courante uniquement.

Logique de sync

Déclenchement

Un Doctrine Entity Listener sur Task (postPersist, postUpdate, preRemove) appelle le CalDavService.

Matrice d'actions

Action Lesstime Effet CalDAV
Tâche créée/modifiée avec syncToCalendar=true et dates renseignées Crée ou met à jour VEVENT + VTODO
syncToCalendar décoché Supprime VEVENT + VTODO si existants
Tâche supprimée Supprime VEVENT + VTODO si existants
Tâche récurrente passe en isFinal Tâche archivée (archived=true), événements conservés dans Zimbra. Nouvelle tâche créée pointant vers le même VEVENT récurrent
Dates retirées Supprime les events correspondants

Gestion des erreurs

  • Timeout CalDAV : 5 secondes
  • En cas d'échec : la tâche est quand même sauvegardée en BDD, un toast d'erreur est affiché côté frontend
  • Les UIDs CalDAV restent null si la création a échoué

Tâches récurrentes

Comportement

  1. L'utilisateur crée une tâche avec récurrence dans Lesstime
  2. Zimbra : un seul VEVENT avec RRULE est créé — Zimbra génère toutes les occurrences dans le calendrier automatiquement
  3. Lesstime : une seule tâche existe à la fois
  4. Quand la tâche passe en statut isFinal :
    • La tâche est archivée automatiquement (archived = true)
    • Les événements Zimbra sont conservés (historique)
    • Une nouvelle tâche est créée avec :
      • Même titre, description, assigné, tags, projet, groupe, effort, priorité
      • Statut réinitialisé au premier statut (position la plus basse)
      • Dates recalculées selon le pattern de récurrence (prochaine date selon le pattern, indépendamment de quand la tâche a été terminée)
      • calendarEventUid pointant vers le même VEVENT récurrent
      • Nouveau calendarTodoUid (nouvelle deadline)
      • occurrenceCount incrémenté
  5. Si maxOccurrences ou endDate atteint, la récurrence s'arrête (pas de nouvelle tâche créée)

Calcul de la prochaine date

La prochaine date est calculée à partir de la date planifiée de la tâche courante (pas de la date de complétion) :

  • Daily : scheduledStart + interval jours
  • Weekly : prochain jour de daysOfWeek à partir de scheduledStart + interval semaines
  • Monthly : même dayOfMonth ou même weekOfMonth+jour, mois + interval
  • Yearly : même date, année + interval

La durée du créneau (scheduledEnd - scheduledStart) est conservée.

Frontend

Onglet "Planification" dans TaskModal

La modale tâche existante aura 2 onglets :

Onglet "Détails" (existant) : titre, description, statut, priorité, effort, assigné, tags, groupe

Onglet "Planification" (nouveau) :

Bloc Dates

  • Date planifiée début (datetime-local picker)
  • Date planifiée fin (datetime-local picker)
  • Deadline (date picker)

Bloc Calendrier

  • Checkbox "Envoyer au calendrier" (décoché par défaut)
  • Indicateur de statut sync (icône verte si sync OK, rouge si erreur, gris si non configuré)

Bloc Récurrence

  • Toggle "Tâche récurrente"
  • Si activé :
    • Type : Quotidien / Hebdomadaire / Mensuel / Annuel (select)
    • Intervalle : "Tous les X ..." (input number)
    • Conditionnel selon le type :
      • Hebdomadaire → checkboxes jours de la semaine (Lu, Ma, Me, Je, Ve, Sa, Di)
      • Mensuel → radio "Le X du mois" (input) ou "Le Xème [jour] du mois" (2 selects)
    • Fin de récurrence : radio Jamais / Après X occurrences (input) / À une date (date picker)

Affichage des dates

Cartes Kanban (TaskCard) :

  • Badge deadline coloré : rouge si dépassée, orange si < 2 jours, gris sinon
  • Icône calendrier si syncToCalendar activé
  • Icône récurrence si tâche récurrente

Vue liste (TaskListItem) :

  • Colonne "Planifié" (date début)
  • Colonne "Deadline"
  • Icône récurrence si tâche récurrente

Page "Mes tâches" :

  • Même affichage que la vue liste
  • Tri possible par deadline ou date planifiée

Page Admin — Configuration Zimbra

Nouveau bloc dans la page admin existante :

  • URL du serveur CalDAV (input text)
  • Nom d'utilisateur (input text)
  • Mot de passe (input password)
  • Chemin du calendrier (input text)
  • Toggle activer/désactiver
  • Bouton "Tester la connexion" (toast succès/erreur)

Accessible uniquement ROLE_ADMIN.

MCP Tools

Mise à jour des tools existants

create-task et update-task : nouveaux paramètres optionnels :

  • scheduledStart (string datetime ISO)
  • scheduledEnd (string datetime ISO)
  • deadline (string datetime ISO)
  • syncToCalendar (bool)

Nouveaux tools

  • create-task-recurrence — paramètres : taskId, type, interval, daysOfWeek?, dayOfMonth?, weekOfMonth?, endDate?, maxOccurrences?
  • update-task-recurrence — paramètres : recurrenceId, + mêmes champs optionnels
  • delete-task-recurrence — paramètres : recurrenceId — supprime la récurrence et l'événement récurrent Zimbra si existant

Dépendances PHP

  • sabre/vobject — génération/parsing ICS (VEVENT, VTODO, RRULE)
  • symfony/http-client — requêtes HTTP CalDAV (PUT, DELETE, PROPFIND)