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.recurrence→ManyToOneversTaskRecurrence(nullable)TaskRecurrence.tasks→OneToManyversTask
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'UIDcreateTodo(Task): string— crée un VTODO (deadline), retourne l'UIDupdateEvent(Task): void— met à jour le VEVENT existantupdateTodo(Task): void— met à jour le VTODO existantdeleteEvent(string $uid): void— supprime le VEVENT par UIDdeleteTodo(string $uid): void— supprime le VTODO par UIDtestConnection(): 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
nullsi la création a échoué
Tâches récurrentes
Comportement
- L'utilisateur crée une tâche avec récurrence dans Lesstime
- Zimbra : un seul VEVENT avec
RRULEest créé — Zimbra génère toutes les occurrences dans le calendrier automatiquement - Lesstime : une seule tâche existe à la fois
- 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)
calendarEventUidpointant vers le même VEVENT récurrent- Nouveau
calendarTodoUid(nouvelle deadline) occurrenceCountincrémenté
- La tâche est archivée automatiquement (
- Si
maxOccurrencesouendDateatteint, 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 descheduledStart + interval semaines - Monthly : même
dayOfMonthou mêmeweekOfMonth+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-localpicker) - Date planifiée fin (
datetime-localpicker) - Deadline (
datepicker)
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
syncToCalendaractivé - 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 optionnelsdelete-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)