feat: add shared task assignments and quick win sorting
This commit is contained in:
@@ -4,7 +4,7 @@ import json
|
||||
from collections import defaultdict
|
||||
from datetime import date, datetime, time, timedelta
|
||||
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy import and_, or_
|
||||
|
||||
from ..extensions import db
|
||||
from ..models import BadgeDefinition, MonthlyScoreSnapshot, TaskInstance, User, UserBadge
|
||||
@@ -92,7 +92,7 @@ def _completion_metrics(user: User) -> dict[str, int]:
|
||||
metrics["on_time_tasks_completed"] += 1
|
||||
if completion_day <= task.due_date - timedelta(days=1):
|
||||
metrics["early_tasks_completed"] += 1
|
||||
if task.assigned_user_id and task.assigned_user_id != user.id:
|
||||
if task.assigned_user_ids and user.id not in task.assigned_user_ids:
|
||||
metrics["foreign_tasks_completed"] += 1
|
||||
max_points = max(max_points, task.points_awarded)
|
||||
|
||||
@@ -127,7 +127,10 @@ def _user_had_clean_month(user_id: int, year: int, month: int) -> bool:
|
||||
start_date = date(year, month, 1)
|
||||
end_date = (month_bounds(year, month)[1] - timedelta(days=1)).date()
|
||||
tasks = TaskInstance.query.filter(
|
||||
TaskInstance.assigned_user_id == user_id,
|
||||
or_(
|
||||
TaskInstance.assigned_user_id == user_id,
|
||||
TaskInstance.assigned_user_secondary_id == user_id,
|
||||
),
|
||||
TaskInstance.due_date >= start_date,
|
||||
TaskInstance.due_date <= end_date,
|
||||
).all()
|
||||
|
||||
@@ -21,6 +21,21 @@ def ensure_schema_and_admins() -> None:
|
||||
db.session.execute(text("ALTER TABLE user ADD COLUMN calendar_feed_token VARCHAR(255)"))
|
||||
db.session.commit()
|
||||
|
||||
task_template_columns = {column["name"] for column in inspector.get_columns("task_template")}
|
||||
if "default_assigned_user_secondary_id" not in task_template_columns:
|
||||
db.session.execute(text("ALTER TABLE task_template ADD COLUMN default_assigned_user_secondary_id INTEGER"))
|
||||
db.session.commit()
|
||||
|
||||
task_instance_columns = {column["name"] for column in inspector.get_columns("task_instance")}
|
||||
if "assigned_user_secondary_id" not in task_instance_columns:
|
||||
db.session.execute(text("ALTER TABLE task_instance ADD COLUMN assigned_user_secondary_id INTEGER"))
|
||||
db.session.commit()
|
||||
|
||||
quick_win_columns = {column["name"] for column in inspector.get_columns("quick_win")}
|
||||
if "sort_order" not in quick_win_columns:
|
||||
db.session.execute(text("ALTER TABLE quick_win ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"))
|
||||
db.session.commit()
|
||||
|
||||
ensure_app_settings()
|
||||
|
||||
users_without_feed = User.query.filter(User.calendar_feed_token.is_(None)).all()
|
||||
@@ -46,6 +61,7 @@ def ensure_schema_and_admins() -> None:
|
||||
default_quick_win_user = first_user
|
||||
|
||||
_ensure_default_quick_wins(default_quick_win_user or User.query.order_by(User.id.asc()).first())
|
||||
_ensure_quick_win_ordering()
|
||||
|
||||
|
||||
def _ensure_default_quick_wins(default_user: User | None) -> None:
|
||||
@@ -62,6 +78,7 @@ def _ensure_default_quick_wins(default_user: User | None) -> None:
|
||||
|
||||
existing_titles = {quick_win.title for quick_win in QuickWin.query.all()}
|
||||
created = False
|
||||
next_sort_order = QuickWin.query.count()
|
||||
for title, effort in defaults:
|
||||
if title not in existing_titles:
|
||||
db.session.add(
|
||||
@@ -70,8 +87,21 @@ def _ensure_default_quick_wins(default_user: User | None) -> None:
|
||||
effort=effort,
|
||||
active=True,
|
||||
created_by_user_id=default_user.id,
|
||||
sort_order=next_sort_order,
|
||||
)
|
||||
)
|
||||
next_sort_order += 1
|
||||
created = True
|
||||
if created:
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def _ensure_quick_win_ordering() -> None:
|
||||
quick_wins = QuickWin.query.order_by(QuickWin.sort_order.asc(), QuickWin.id.asc()).all()
|
||||
dirty = False
|
||||
for index, quick_win in enumerate(quick_wins):
|
||||
if quick_win.sort_order != index:
|
||||
quick_win.sort_order = index
|
||||
dirty = True
|
||||
if dirty:
|
||||
db.session.commit()
|
||||
|
||||
@@ -2,6 +2,8 @@ from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, time, timedelta
|
||||
|
||||
from sqlalchemy import or_
|
||||
|
||||
from ..models import TaskInstance, User
|
||||
|
||||
|
||||
@@ -23,7 +25,12 @@ def _format_timestamp(value: datetime | None) -> str:
|
||||
|
||||
def build_calendar_feed(user: User, base_url: str) -> str:
|
||||
tasks = (
|
||||
TaskInstance.query.filter_by(assigned_user_id=user.id)
|
||||
TaskInstance.query.filter(
|
||||
or_(
|
||||
TaskInstance.assigned_user_id == user.id,
|
||||
TaskInstance.assigned_user_secondary_id == user.id,
|
||||
)
|
||||
)
|
||||
.order_by(TaskInstance.due_date.asc(), TaskInstance.id.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
@@ -25,12 +25,19 @@ def refresh_task_statuses(tasks: list[TaskInstance]) -> None:
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def effective_points(base_points: int, assigned_user_secondary_id: int | None) -> int:
|
||||
if assigned_user_secondary_id:
|
||||
return max(1, base_points // 2)
|
||||
return base_points
|
||||
|
||||
|
||||
def create_task_template_and_instance(form) -> TaskInstance:
|
||||
template = TaskTemplate(
|
||||
title=form.title.data.strip(),
|
||||
description=(form.description.data or "").strip(),
|
||||
default_points=form.default_points.data,
|
||||
default_assigned_user_id=form.assigned_user_id.data,
|
||||
default_assigned_user_secondary_id=form.assigned_user_secondary_id.data or None,
|
||||
recurrence_interval_value=form.recurrence_interval_value.data if form.recurrence_interval_unit.data != "none" else None,
|
||||
recurrence_interval_unit=form.recurrence_interval_unit.data,
|
||||
active=form.active.data,
|
||||
@@ -43,8 +50,9 @@ def create_task_template_and_instance(form) -> TaskInstance:
|
||||
title=template.title,
|
||||
description=template.description,
|
||||
assigned_user_id=template.default_assigned_user_id,
|
||||
assigned_user_secondary_id=template.default_assigned_user_secondary_id,
|
||||
due_date=form.due_date.data,
|
||||
points_awarded=template.default_points,
|
||||
points_awarded=effective_points(template.default_points, template.default_assigned_user_secondary_id),
|
||||
status="open",
|
||||
)
|
||||
refresh_task_status(task, form.due_date.data)
|
||||
@@ -59,6 +67,7 @@ def update_template_and_instance(task: TaskInstance, form) -> TaskInstance:
|
||||
template.description = (form.description.data or "").strip()
|
||||
template.default_points = form.default_points.data
|
||||
template.default_assigned_user_id = form.assigned_user_id.data
|
||||
template.default_assigned_user_secondary_id = form.assigned_user_secondary_id.data or None
|
||||
template.recurrence_interval_unit = form.recurrence_interval_unit.data
|
||||
template.recurrence_interval_value = (
|
||||
form.recurrence_interval_value.data if form.recurrence_interval_unit.data != "none" else None
|
||||
@@ -68,7 +77,8 @@ def update_template_and_instance(task: TaskInstance, form) -> TaskInstance:
|
||||
task.title = template.title
|
||||
task.description = template.description
|
||||
task.assigned_user_id = template.default_assigned_user_id
|
||||
task.points_awarded = template.default_points
|
||||
task.assigned_user_secondary_id = template.default_assigned_user_secondary_id
|
||||
task.points_awarded = effective_points(template.default_points, template.default_assigned_user_secondary_id)
|
||||
task.due_date = form.due_date.data
|
||||
refresh_task_status(task, form.due_date.data)
|
||||
db.session.commit()
|
||||
@@ -108,8 +118,12 @@ def ensure_next_recurring_task(task: TaskInstance) -> TaskInstance | None:
|
||||
title=task.task_template.title,
|
||||
description=task.task_template.description,
|
||||
assigned_user_id=task.task_template.default_assigned_user_id,
|
||||
assigned_user_secondary_id=task.task_template.default_assigned_user_secondary_id,
|
||||
due_date=next_due,
|
||||
points_awarded=task.task_template.default_points,
|
||||
points_awarded=effective_points(
|
||||
task.task_template.default_points,
|
||||
task.task_template.default_assigned_user_secondary_id,
|
||||
),
|
||||
status="open",
|
||||
)
|
||||
refresh_task_status(next_task, today_local())
|
||||
@@ -149,6 +163,7 @@ def create_quick_task(title: str, effort: str, creator: User, description: str =
|
||||
title=template.title,
|
||||
description=description,
|
||||
assigned_user_id=creator.id,
|
||||
assigned_user_secondary_id=None,
|
||||
due_date=today_local(),
|
||||
points_awarded=template.default_points,
|
||||
status="open",
|
||||
|
||||
Reference in New Issue
Block a user