137 lines
4.7 KiB
Python
137 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
|
|
class ContextCacheUserSnapshotMixin:
|
|
async def _build_user_snapshot(
|
|
self,
|
|
*,
|
|
db: AsyncSession,
|
|
tenant_id: int,
|
|
user_id: int,
|
|
) -> dict[str, Any]:
|
|
owned_rows = (
|
|
await db.execute(
|
|
select(Project.id).where(
|
|
and_(
|
|
Project.tenant_id == tenant_id,
|
|
Project.owner_id == user_id,
|
|
)
|
|
)
|
|
)
|
|
).scalars().all()
|
|
member_rows = (
|
|
await db.execute(
|
|
select(UserProject.project_id).where(
|
|
and_(
|
|
UserProject.tenant_id == tenant_id,
|
|
UserProject.user_id == user_id,
|
|
)
|
|
)
|
|
)
|
|
).scalars().all()
|
|
project_ids = sorted({_safe_int(pid, 0) for pid in [*owned_rows, *member_rows] if _safe_int(pid, 0) > 0})
|
|
|
|
project_rows: list[Any] = []
|
|
if project_ids:
|
|
project_rows = (
|
|
await db.execute(
|
|
select(Project.id, Project.name, Project.title, Project.status).where(
|
|
and_(
|
|
Project.tenant_id == tenant_id,
|
|
Project.id.in_(project_ids),
|
|
)
|
|
)
|
|
)
|
|
).all()
|
|
|
|
open_tasks = (
|
|
await db.execute(
|
|
select(Task.id, Task.project_id, Task.title, Task.status, Task.due_date).where(
|
|
and_(
|
|
Task.tenant_id == tenant_id,
|
|
Task.assignee_id == user_id,
|
|
func.lower(Task.status).notin_(DONE_TASK_STATUSES),
|
|
)
|
|
)
|
|
)
|
|
).all()
|
|
|
|
pending_signals: list[dict[str, Any]] = []
|
|
if project_ids:
|
|
pending_rows = (
|
|
await db.execute(
|
|
select(Signal.id, Signal.title, Signal.priority, Signal.status, Signal.matched_project_id).where(
|
|
and_(
|
|
Signal.tenant_id == tenant_id,
|
|
Signal.matched_project_id.in_(project_ids),
|
|
or_(
|
|
Signal.processed.is_(False),
|
|
func.lower(Signal.status).in_(PENDING_SIGNAL_STATUSES),
|
|
),
|
|
)
|
|
).limit(60)
|
|
)
|
|
).all()
|
|
pending_signals = [
|
|
{
|
|
"id": int(row.id),
|
|
"title": str(row.title or ""),
|
|
"priority": str(row.priority or ""),
|
|
"status": str(row.status or ""),
|
|
"project_id": _safe_int(row.matched_project_id, 0) or None,
|
|
}
|
|
for row in pending_rows
|
|
]
|
|
|
|
workload_row = (
|
|
await db.execute(
|
|
select(UserWorkload.workload_ratio).where(
|
|
and_(
|
|
UserWorkload.tenant_id == tenant_id,
|
|
UserWorkload.user_id == user_id,
|
|
)
|
|
)
|
|
)
|
|
).scalar_one_or_none()
|
|
if workload_row is None:
|
|
fallback = (
|
|
await db.execute(
|
|
select(User.workload).where(
|
|
and_(
|
|
User.tenant_id == tenant_id,
|
|
User.id == user_id,
|
|
)
|
|
)
|
|
)
|
|
).scalar_one_or_none()
|
|
workload_ratio = round(float(fallback or 0) / 100.0, 2)
|
|
else:
|
|
workload_ratio = float(workload_row or 0.0)
|
|
|
|
return {
|
|
"user_id": user_id,
|
|
"projects": [
|
|
{
|
|
"id": int(row.id),
|
|
"name": str(row.title or row.name or f"Project#{int(row.id)}"),
|
|
"status": str(row.status or "unknown"),
|
|
}
|
|
for row in project_rows
|
|
],
|
|
"assigned_tasks": [
|
|
{
|
|
"id": int(row.id),
|
|
"project_id": int(row.project_id),
|
|
"title": str(row.title or ""),
|
|
"status": str(row.status or ""),
|
|
"due_date": row.due_date.isoformat() if row.due_date else None,
|
|
}
|
|
for row in open_tasks
|
|
],
|
|
"pending_signals": pending_signals,
|
|
"workload_ratio": workload_ratio,
|
|
"snapshot_at": now_cn_aware().isoformat(),
|
|
}
|
|
|