feat: carry overdue interval habits forward
This commit is contained in:
+30
-6
@@ -938,7 +938,14 @@ def require_active_habit(habit):
|
||||
|
||||
|
||||
async def require_positive_log_day(db, habit, day):
|
||||
if not scheduled(habit, day):
|
||||
completed_days = []
|
||||
if habit.schedule_type == "interval":
|
||||
completed_days = list((await db.scalars(select(HabitLog.day).where(
|
||||
HabitLog.habit_id == habit.id,
|
||||
HabitLog.day < day,
|
||||
HabitLog.value >= habit.target,
|
||||
).order_by(HabitLog.day))).all())
|
||||
if not scheduled(habit, day, completed_days):
|
||||
raise HTTPException(409, "非计划日不可记录正向进度")
|
||||
paused = await db.scalar(select(HabitPause.id).where(
|
||||
HabitPause.habit_id == habit.id,
|
||||
@@ -1128,12 +1135,20 @@ async def pause_habit(habit_id: UUID, payload: PauseInput, user: User = Depends(
|
||||
row = HabitPause(habit_id=habit.id, **payload.model_dump()); db.add(row); await db.commit(); await db.refresh(row); return {"id": row.id, **payload.model_dump()}
|
||||
|
||||
|
||||
def scheduled(h, day):
|
||||
def scheduled(h, day, completed_days=None):
|
||||
if day < h.start_date: return False
|
||||
if h.schedule_type == "daily": return True
|
||||
if h.schedule_type == "weekly": return day.weekday() in {int(x) for x in (h.weekdays or "").split(",") if x}
|
||||
if h.schedule_type == "monthly": return day.day in {int(x) for x in (h.month_days or "").split(",") if x}
|
||||
return (day - h.start_date).days % h.interval_days == 0
|
||||
completed_days = [
|
||||
completed_day
|
||||
for completed_day in (completed_days or [])
|
||||
if h.start_date <= completed_day < day
|
||||
]
|
||||
last_completed = max(completed_days, default=None)
|
||||
base = last_completed if last_completed is not None else h.start_date
|
||||
next_due = base + timedelta(days=h.interval_days) if last_completed is not None else h.start_date
|
||||
return day >= next_due
|
||||
|
||||
|
||||
@router.get("/habits/grid")
|
||||
@@ -1150,6 +1165,7 @@ async def habits_grid(week: date, user: User = Depends(current_user), db: AsyncS
|
||||
func.sum(HabitLog.value),
|
||||
func.count(HabitLog.id),
|
||||
func.sum(case((HabitLog.value >= Habit.target, 1), else_=0)),
|
||||
func.max(case((HabitLog.value >= Habit.target, HabitLog.day), else_=None)).filter(HabitLog.day < days[0]),
|
||||
)
|
||||
.join(Habit, Habit.id == HabitLog.habit_id)
|
||||
.where(HabitLog.habit_id.in_(habit_ids))
|
||||
@@ -1159,18 +1175,26 @@ async def habits_grid(week: date, user: User = Depends(current_user), db: AsyncS
|
||||
logs_by_habit = {}
|
||||
stats_by_habit = {}
|
||||
pauses_by_habit = {}
|
||||
completed_days_by_habit = {}
|
||||
habits_by_id = {habit.id: habit for habit in habits}
|
||||
for habit_id, total, logged_days, completed_days, previous_completed in stats_rows:
|
||||
stats_by_habit[habit_id] = {"total": total or 0, "completed_days": completed_days or 0, "logged_days": logged_days or 0}
|
||||
if previous_completed is not None:
|
||||
completed_days_by_habit[habit_id] = [previous_completed]
|
||||
for log in week_logs:
|
||||
logs_by_habit.setdefault(log.habit_id, {})[log.day] = log.value
|
||||
for habit_id, total, logged_days, completed_days in stats_rows:
|
||||
stats_by_habit[habit_id] = {"total": total or 0, "completed_days": completed_days or 0, "logged_days": logged_days or 0}
|
||||
habit = habits_by_id.get(log.habit_id)
|
||||
if habit is not None and habit.schedule_type == "interval" and log.value >= habit.target:
|
||||
completed_days_by_habit.setdefault(log.habit_id, []).append(log.day)
|
||||
for pause in pause_rows:
|
||||
pauses_by_habit.setdefault(pause.habit_id, []).append(pause)
|
||||
output = []
|
||||
for habit in habits:
|
||||
logs = logs_by_habit.get(habit.id, {})
|
||||
pauses = pauses_by_habit.get(habit.id, [])
|
||||
completed_days = completed_days_by_habit.get(habit.id, []) if habit.schedule_type == "interval" else []
|
||||
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["cells"] = [{"day": day, "scheduled": scheduled(habit, day, completed_days), "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