This commit is contained in:
+67
-16
@@ -149,20 +149,42 @@ async def calendar(start: date, end: date, user: User = Depends(current_user), d
|
||||
raise HTTPException(422, "日期范围无效或超过一年")
|
||||
start_dt = datetime.combine(start, time.min, tzinfo=UTC)
|
||||
end_dt = datetime.combine(end, time.max, tzinfo=UTC)
|
||||
rows = (await db.execute(select(RecurrenceTemplate, Task).join(Task).where(RecurrenceTemplate.user_id == user.id, Task.deleted_at.is_(None)))).all()
|
||||
output = []
|
||||
for template, task in rows:
|
||||
exception_rows = (await db.scalars(select(RecurrenceException).where(RecurrenceException.template_id == template.id))).all()
|
||||
exceptions: dict[datetime, RecurrenceException] = {}
|
||||
for exc in exception_rows:
|
||||
key = exc.occurrence_at.replace(tzinfo=UTC) if exc.occurrence_at.tzinfo is None else exc.occurrence_at
|
||||
exceptions[key] = exc
|
||||
recurrence_rows = (await db.execute(select(RecurrenceTemplate, Task).join(Task).where(RecurrenceTemplate.user_id == user.id, Task.deleted_at.is_(None)))).all()
|
||||
template_ids = [template.id for template, _ in recurrence_rows]
|
||||
exception_rows = [] if not template_ids else list((await db.scalars(select(RecurrenceException).where(RecurrenceException.template_id.in_(template_ids)))).all())
|
||||
exceptions_by_template = {}
|
||||
for exception in exception_rows:
|
||||
key = exception.occurrence_at.replace(tzinfo=UTC) if exception.occurrence_at.tzinfo is None else exception.occurrence_at
|
||||
exceptions_by_template.setdefault(exception.template_id, {})[key] = exception
|
||||
recurring_task_ids = {task.id for _, task in recurrence_rows}
|
||||
normal_query = select(Task).where(
|
||||
Task.user_id == user.id,
|
||||
Task.deleted_at.is_(None),
|
||||
Task.parent_id.is_(None),
|
||||
Task.due_at >= start_dt,
|
||||
Task.due_at <= end_dt,
|
||||
)
|
||||
if recurring_task_ids:
|
||||
normal_query = normal_query.where(Task.id.not_in(recurring_task_ids))
|
||||
normal_tasks = list((await db.scalars(normal_query)).all())
|
||||
output = [{
|
||||
"id": task.id,
|
||||
"recurrence_id": None,
|
||||
"task_id": task.id,
|
||||
"occurrence_at": task.due_at,
|
||||
"title": task.title,
|
||||
"due_at": task.due_at,
|
||||
"completed": task.completed,
|
||||
"version": task.version,
|
||||
} for task in normal_tasks]
|
||||
for template, task in recurrence_rows:
|
||||
exceptions = exceptions_by_template.get(template.id, {})
|
||||
for at in occurrences(template.rrule, template.starts_at, start_dt, end_dt, template.ends_at):
|
||||
exception = exceptions.get(at)
|
||||
if exception and exception.deleted:
|
||||
continue
|
||||
output.append({"recurrence_id": template.id, "task_id": task.id, "occurrence_at": at, "title": exception.title if exception and exception.title else task.title, "due_at": exception.due_at if exception and exception.due_at else at, "completed": bool(exception and exception.completed)})
|
||||
return sorted(output, key=lambda item: item["occurrence_at"])
|
||||
return sorted(output, key=lambda item: item["occurrence_at"].replace(tzinfo=UTC) if item["occurrence_at"].tzinfo is None else item["occurrence_at"])
|
||||
|
||||
|
||||
async def upsert_exception(db, template_id, at):
|
||||
@@ -310,9 +332,14 @@ async def edit_habit_log(habit_id: UUID, day: date, payload: HabitLogEdit, user:
|
||||
|
||||
|
||||
@router.get("/habits/{habit_id}/logs")
|
||||
async def habit_logs(habit_id: UUID, user: User = Depends(current_user), db: AsyncSession = Depends(get_db)):
|
||||
async def habit_logs(habit_id: UUID, from_date: date | None = Query(default=None, alias="from"), to_date: date | None = Query(default=None, alias="to"), user: User = Depends(current_user), db: AsyncSession = Depends(get_db)):
|
||||
habit = await owned_habit(db, user.id, habit_id)
|
||||
return [{"day": x.day, "value": x.value} for x in (await db.scalars(select(HabitLog).where(HabitLog.habit_id == habit.id).order_by(HabitLog.day.desc()))).all()]
|
||||
condition = HabitLog.habit_id == habit.id
|
||||
if from_date is not None:
|
||||
condition = condition & (HabitLog.day >= from_date)
|
||||
if to_date is not None:
|
||||
condition = condition & (HabitLog.day <= to_date)
|
||||
return [{"day": x.day, "value": x.value} for x in (await db.scalars(select(HabitLog).where(condition).order_by(HabitLog.day.desc()))).all()]
|
||||
|
||||
|
||||
@router.post("/habits/{habit_id}/pauses", status_code=201)
|
||||
@@ -331,12 +358,36 @@ def scheduled(h, day):
|
||||
@router.get("/habits/grid")
|
||||
async def habits_grid(week: date, user: User = Depends(current_user), db: AsyncSession = Depends(get_db)):
|
||||
start = week - timedelta(days=week.weekday()); days = [start + timedelta(days=i) for i in range(7)]
|
||||
habits = list((await db.scalars(select(Habit).where(Habit.user_id == user.id, Habit.archived_at.is_(None)))).all())
|
||||
habits = list((await db.scalars(select(Habit).where(Habit.user_id == user.id, Habit.archived_at.is_(None)).order_by(Habit.created_at))).all())
|
||||
if not habits:
|
||||
return {"days": days, "habits": []}
|
||||
habit_ids = [habit.id for habit in habits]
|
||||
week_logs = (await db.scalars(select(HabitLog).where(HabitLog.habit_id.in_(habit_ids), HabitLog.day.between(days[0], days[-1])))).all()
|
||||
all_logs = (await db.scalars(select(HabitLog).where(HabitLog.habit_id.in_(habit_ids)))).all()
|
||||
pause_rows = (await db.scalars(select(HabitPause).where(HabitPause.habit_id.in_(habit_ids), HabitPause.end_date >= days[0], HabitPause.start_date <= days[-1]))).all()
|
||||
logs_by_habit = {}
|
||||
stats_by_habit = {}
|
||||
pauses_by_habit = {}
|
||||
for log in week_logs:
|
||||
logs_by_habit.setdefault(log.habit_id, {})[log.day] = log.value
|
||||
for log in all_logs:
|
||||
stats = stats_by_habit.setdefault(log.habit_id, {"total": 0, "completed_days": 0, "logged_days": 0})
|
||||
stats["total"] += log.value
|
||||
stats["logged_days"] += 1
|
||||
targets = {habit.id: habit.target for habit in habits}
|
||||
for log in all_logs:
|
||||
if log.value >= targets[log.habit_id]:
|
||||
stats_by_habit[log.habit_id]["completed_days"] += 1
|
||||
for pause in pause_rows:
|
||||
pauses_by_habit.setdefault(pause.habit_id, []).append(pause)
|
||||
output = []
|
||||
for h in habits:
|
||||
logs = {x.day: x.value for x in (await db.scalars(select(HabitLog).where(HabitLog.habit_id == h.id, HabitLog.day.between(days[0], days[-1])))).all()}
|
||||
pauses = list((await db.scalars(select(HabitPause).where(HabitPause.habit_id == h.id))).all())
|
||||
output.append({"id": h.id, "name": h.name, "cells": [{"day": d, "scheduled": scheduled(h, d), "paused": any(p.start_date <= d <= p.end_date for p in pauses), "value": logs.get(d, 0)} for d in days]})
|
||||
for habit in habits:
|
||||
logs = logs_by_habit.get(habit.id, {})
|
||||
pauses = pauses_by_habit.get(habit.id, [])
|
||||
data = habit_dict(habit)
|
||||
data["cells"] = [{"day": day, "scheduled": scheduled(habit, day), "paused": any(pause.start_date <= day <= pause.end_date for pause in pauses), "value": logs.get(day, 0)} for day in days]
|
||||
data["stats"] = stats_by_habit.get(habit.id, {"total": 0, "completed_days": 0, "logged_days": 0})
|
||||
output.append(data)
|
||||
return {"days": days, "habits": output}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user