Implement recurring tasks functionality

Adds automatic creation of next task instance when recurring tasks are completed.

- Add calculate_next_due_date() and create_next_recurrence() methods to Task model
- Update task completion handlers in views and sync API to create next recurrence
- Add hourly Celery task to process any missed recurring tasks
- Support daily, weekly, biweekly, monthly, yearly recurrence patterns
- Respect recurrence_end_date limits

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Keith Smith
2026-01-04 10:00:34 -07:00
co-authored by Claude Sonnet 4.5
parent c51fd846a2
commit 1d6b07bfed
5 changed files with 145 additions and 0 deletions
+48
View File
@@ -236,3 +236,51 @@ def schedule_task_reminder(task_id):
task=task,
remind_at=remind_at
)
@shared_task
def process_recurring_tasks():
"""
Process completed recurring tasks and create next instances.
This runs periodically as a safety net to catch any tasks that weren't
automatically handled when marked as completed.
Runs every hour via Celery Beat.
"""
from tasks.models import Task
# Find completed recurring tasks that don't have a next instance created yet
# We look for tasks completed in the last 24 hours to avoid reprocessing old tasks
yesterday = timezone.now() - timedelta(days=1)
completed_recurring_tasks = Task.objects.filter(
status='completed',
recurrence__in=['daily', 'weekly', 'biweekly', 'monthly', 'yearly', 'custom'],
completed_at__gte=yesterday
).exclude(recurrence='none')
tasks_created = 0
for task in completed_recurring_tasks:
# Check if a next recurrence already exists
# Look for pending tasks with the same title, user, and recurrence pattern
next_due_date = task.calculate_next_due_date()
if next_due_date:
# Check if we already created this recurrence
existing = Task.objects.filter(
user=task.user,
title=task.title,
due_date=next_due_date,
recurrence=task.recurrence,
status='pending'
).exists()
if not existing:
# Create the next recurrence
new_task = task.create_next_recurrence()
if new_task:
tasks_created += 1
logger.info(f"Created recurring task: {new_task.title} (due: {new_task.due_date})")
if tasks_created > 0:
logger.info(f"Created {tasks_created} recurring task instances")
return tasks_created