Files
ERP/backend/app/serializers.py
T
MMD 934b74ebbd Squashed 'pm/' content from commit 91f3271
git-subtree-dir: pm
git-subtree-split: 91f3271d866fa8ee471cfb6793e334b83e6d2b6e
2026-10-07 15:14:20 +03:30

159 lines
6.0 KiB
Python

"""Response serializers — the {"data": ...} envelope lives in the routers."""
from datetime import date, datetime
from app.utils.jalali import to_jalali_str
def _iso(value: datetime | date | None) -> str | None:
if value is None:
return None
if isinstance(value, datetime):
return value.isoformat()
return value.isoformat()
def user_payload(ctx) -> dict:
"""The authenticated user as the frontend sees them: identity, role and the
permissions/scope the UI derives its navigation from (see frontend
src/lib/roles.ts). The API enforces the same values server-side."""
user = ctx.user
return {
"id": str(user.id),
"username": user.username,
"display_name": user.display_name,
**ctx.as_dict(),
}
def project_dict(project, member_count: int = 0, task_stats: dict | None = None) -> dict:
return {
"id": str(project.id),
"name": project.name,
"description": project.description or "",
"location": project.location or "",
"project_manager": project.project_manager or "",
"planned_start": _iso(project.planned_start),
"planned_end": _iso(project.planned_end),
"actual_start": _iso(project.actual_start),
"actual_end": _iso(project.actual_end),
"planned_start_jalali": to_jalali_str(project.planned_start),
"planned_end_jalali": to_jalali_str(project.planned_end),
"actual_start_jalali": to_jalali_str(project.actual_start),
"actual_end_jalali": to_jalali_str(project.actual_end),
"status": project.status,
"progress": round(project.progress or 0.0, 1),
"member_count": member_count,
"stats": task_stats or {"total": 0, "completed": 0, "in_progress": 0, "blocked": 0, "overdue": 0},
"created_at": _iso(project.created_at),
"updated_at": _iso(project.updated_at),
}
def project_member_dict(member) -> dict:
username = ""
display_name = ""
try:
from sqlalchemy.inspection import inspect
insp = inspect(member)
if "user" in insp.attrs and insp.attrs.user.loaded and member.user:
username = member.user.username
display_name = member.user.display_name
except Exception:
pass
return {
"id": str(member.id),
"project_id": str(member.project_id),
"user_id": str(member.user_id),
"username": username,
"display_name": display_name,
"role": member.role,
"created_at": _iso(member.created_at),
}
def native_task_dict(task, project_name: str | None = None, children_count: int = 0) -> dict:
assignee_name = task.assignee_name
if not assignee_name:
try:
from sqlalchemy.inspection import inspect
insp = inspect(task)
if "assignee" in insp.attrs and "assignee" not in insp.unloaded and task.assignee:
assignee_name = task.assignee.display_name
except Exception:
pass
progress_logs_data = []
try:
from sqlalchemy.inspection import inspect
insp = inspect(task)
if "progress_logs" in insp.attrs and "progress_logs" not in insp.unloaded and task.progress_logs:
progress_logs_data = [
{
"id": str(log.id),
"progress_before": log.progress_before,
"progress_after": log.progress_after,
"human_resources_used": log.human_resources_used or [],
"materials_used": log.materials_used or [],
"description": log.description or "",
"logged_by": log.logged_by or "",
"created_at": _iso(log.created_at),
}
for log in task.progress_logs
]
except Exception:
pass
return {
"id": str(task.id),
"project_id": str(task.project_id),
"parent_id": str(task.parent_id) if task.parent_id else None,
"title": task.title,
"description": task.description or "",
"status": task.status,
"priority": task.priority,
"assignee_id": str(task.assignee_id) if task.assignee_id else None,
"assignee_name": assignee_name,
"planned_start": _iso(task.planned_start),
"planned_end": _iso(task.planned_end),
"actual_start": _iso(task.actual_start),
"actual_end": _iso(task.actual_end),
"planned_start_jalali": to_jalali_str(task.planned_start),
"planned_end_jalali": to_jalali_str(task.planned_end),
"actual_start_jalali": to_jalali_str(task.actual_start),
"actual_end_jalali": to_jalali_str(task.actual_end),
"duration": task.duration or 1,
"progress": task.progress or 0,
"qc_status": task.qc_status or "none",
"project_name": project_name,
"human_resources": getattr(task, "human_resources", None) or [],
"materials": getattr(task, "materials", None) or [],
"progress_logs": progress_logs_data,
"created_at": _iso(task.created_at),
"updated_at": _iso(task.updated_at),
}
def task_relation_dict(rel, from_title: str = "", to_title: str = "") -> dict:
from_title_val = from_title
to_title_val = to_title
try:
from sqlalchemy.inspection import inspect
insp = inspect(rel)
if not from_title_val and "from_task" in insp.attrs and insp.attrs.from_task.loaded and rel.from_task:
from_title_val = rel.from_task.title
if not to_title_val and "to_task" in insp.attrs and insp.attrs.to_task.loaded and rel.to_task:
to_title_val = rel.to_task.title
except Exception:
pass
return {
"id": str(rel.id),
"project_id": str(rel.project_id),
"from_task_id": str(rel.from_task_id),
"to_task_id": str(rel.to_task_id),
"from_task_title": from_title_val,
"to_task_title": to_title_val,
"relation_type": rel.relation_type,
"lag_days": rel.lag_days,
"created_at": _iso(rel.created_at),
}