getStatus(); $isNowFinal = $currentStatus?->getIsFinal() ?? false; if (!$isNowFinal || $wasAlreadyFinal) { return; // No transition to final } $recurrence = $task->getRecurrence(); if (null === $recurrence) { return; // Not a recurring task } if ($this->calculator->hasReachedEnd($recurrence)) { return; // Recurrence is done } $nextStart = $this->calculator->getNextDate($task); if (null === $nextStart) { return; } // Archive current task, clear calendar UIDs $savedEventUid = $task->getCalendarEventUid(); $task->setArchived(true); $task->setCalendarEventUid(null); $task->setCalendarTodoUid(null); // Create new task with same fields $newTask = new Task(); $newTask->setProject($task->getProject()); $newTask->setTitle($task->getTitle()); $newTask->setDescription($task->getDescription()); $newTask->setAssignee($task->getAssignee()); $newTask->setEffort($task->getEffort()); $newTask->setPriority($task->getPriority()); $newTask->setGroup($task->getGroup()); $newTask->setRecurrence($recurrence); $newTask->setSyncToCalendar($task->isSyncToCalendar()); // Copy tags foreach ($task->getTags() as $tag) { $newTask->addTag($tag); } // Set first non-final status $firstStatus = $this->statusRepository->findFirstNonFinal(); $newTask->setStatus($firstStatus); // Set recalculated dates $newTask->setScheduledStart($nextStart); $newTask->setScheduledEnd($this->calculator->getNextEnd($task, $nextStart)); $newTask->setDeadline($this->calculator->getNextDeadline($task, $nextStart)); // Copy calendar event UID (recurring VEVENT is shared) $newTask->setCalendarEventUid($savedEventUid); // Generate task number in transaction $this->entityManager->wrapInTransaction(function () use ($newTask): void { $maxNumber = $this->taskRepository->findMaxNumberByProjectForUpdate($newTask->getProject()); $newTask->setNumber($maxNumber + 1); $this->entityManager->persist($newTask); $this->entityManager->flush(); }); // Increment occurrence count (with optimistic locking via @Version) $recurrence->incrementOccurrenceCount(); $this->entityManager->flush(); // Sync new task's VTODO (new deadline) to Zimbra if ($newTask->isSyncToCalendar() && $newTask->getDeadline()) { $uid = $this->calDavService->createTodo($newTask); if ($uid) { $newTask->setCalendarTodoUid($uid); $newTask->setCalendarSyncError(null); $this->entityManager->flush(); } } } }