127 lines
5.7 KiB
Python
127 lines
5.7 KiB
Python
from job.job_post.enums import RequisitionStatus
|
|
|
|
|
|
def _status_label(value):
|
|
if value in (None, ""):
|
|
return None
|
|
parsed = RequisitionStatus.parse(value)
|
|
return parsed.label if parsed else value
|
|
|
|
|
|
def serialize_job_post(row) -> dict:
|
|
return {
|
|
"id": str(row.id),
|
|
"title": row.title,
|
|
# Talent Pool / candidate filters key off attached job_posts.department.
|
|
"department": row.department or None,
|
|
"employment_type": row.employment_type,
|
|
"location": row.location,
|
|
"experience_min": row.experience_min,
|
|
"experience_max": row.experience_max,
|
|
"requirements": list(row.requirements or []),
|
|
"optional_skills": list(row.optional_skills or []),
|
|
"salary": row.salary,
|
|
"description": row.description,
|
|
"post_text": row.post_text,
|
|
"channel_id": row.channel_id,
|
|
"platform": row.platform,
|
|
"is_active": row.is_active,
|
|
"is_deleted": row.is_deleted,
|
|
"buffer_post_id": row.buffer_post_id,
|
|
"buffer_external_link": row.buffer_external_link,
|
|
"buffer_sent_at": row.buffer_sent_at.isoformat() if row.buffer_sent_at else None,
|
|
"status": row.status,
|
|
"buffer_error": row.buffer_error,
|
|
"created_by": str(row.created_by) if row.created_by else None,
|
|
"created_by_name": row.user.name if getattr(row, "user", None) else None,
|
|
"created_at": row.created_at.isoformat() if row.created_at else None,
|
|
"updated_at": row.updated_at.isoformat() if row.updated_at else None,
|
|
"requisition_id": str(row.requisition_id) if getattr(row, "requisition_id", None) else None,
|
|
}
|
|
|
|
|
|
def serialize_job_row(row, *, recruiter_name=None, hiring_manager_name=None, applicant_count=0) -> dict:
|
|
"""Requisition view of a job post, for the Jobs screen.
|
|
|
|
Deliberately separate from serialize_job_post: that payload is shared by the
|
|
inbox, candidate and matching paths. department is the one shared field —
|
|
talent-pool filters key off it on attached job_posts.
|
|
"""
|
|
req = getattr(row, "requisition", None)
|
|
return {
|
|
"id": str(row.id),
|
|
"title": row.title,
|
|
"department": row.department or None,
|
|
"location": row.location,
|
|
"employment_type": row.employment_type,
|
|
"vacancies": row.vacancies,
|
|
"platform": row.platform or None,
|
|
# Two different lifecycles, never conflate: requisition_status is hiring
|
|
# (open/closed/on_hold), status is Buffer publishing (draft/scheduled/...).
|
|
"requisition_status": row.requisition_status,
|
|
"status": row.status,
|
|
"experience_min": row.experience_min,
|
|
"experience_max": row.experience_max,
|
|
"salary": row.salary,
|
|
"requirements": list(row.requirements or []),
|
|
"optional_skills": list(row.optional_skills or []),
|
|
"description": row.description,
|
|
"is_active": row.is_active,
|
|
"closed_at": row.closed_at.isoformat() if row.closed_at else None,
|
|
"current_recruiter_id": str(row.current_recruiter_id) if row.current_recruiter_id else None,
|
|
"recruiter_name": recruiter_name,
|
|
"hiring_manager_id": str(row.hiring_manager_id) if row.hiring_manager_id else None,
|
|
"hiring_manager_name": hiring_manager_name,
|
|
"requisition_id": str(row.requisition_id) if getattr(row, "requisition_id", None) else None,
|
|
"requisition_title": req.position_title if req else None,
|
|
"requisition_department": req.department if req else None,
|
|
"applicant_count": applicant_count,
|
|
"created_by": str(row.created_by) if row.created_by else None,
|
|
"created_by_name": row.user.name if getattr(row, "user", None) else None,
|
|
"created_at": row.created_at.isoformat() if row.created_at else None,
|
|
"updated_at": row.updated_at.isoformat() if row.updated_at else None,
|
|
}
|
|
|
|
|
|
def serialize_job_stats(row) -> dict:
|
|
"""One job post's live pipeline-stage counts. `row` is a mapping from fetch_job_stats."""
|
|
recruiter_id=row.get("current_recruiter_id")
|
|
created_at = row.get("created_at")
|
|
return {
|
|
"job_post_id": str(row["job_post_id"]),
|
|
"title": row["title"],
|
|
"department": row["department"] or None,
|
|
"location": row["location"],
|
|
"requisition_status": row["requisition_status"],
|
|
"current_recruiter_id": str(recruiter_id) if recruiter_id else None,
|
|
"recruiter_name": row.get("recruiter_name") or None,
|
|
# Frontend computes days-open vs client clock; no server days_open field.
|
|
"created_at": created_at.isoformat() if created_at else None,
|
|
"total_applicants": int(row["total_applicants"] or 0),
|
|
"shortlisting": int(row["shortlisting"] or 0),
|
|
"screened": int(row["screened"] or 0),
|
|
"assessment": int(row["assessment"] or 0),
|
|
"interviewed": int(row["interviewed"] or 0),
|
|
"offered": int(row["offered"] or 0),
|
|
"on_hold": int(row["on_hold"] or 0),
|
|
"rejected": int(row["rejected"] or 0),
|
|
"approved": int(row["approved"] or 0),
|
|
"hired": int(row["hired"] or 0),
|
|
"reapplied": int(row.get("reapplied") or 0),
|
|
}
|
|
|
|
|
|
def serialize_status_history(row, *, changed_by_name=None) -> dict:
|
|
return {
|
|
"id": str(row.id),
|
|
"job_post_id": str(row.job_post_id) if row.job_post_id else None,
|
|
"from_status": row.from_status,
|
|
"from_label": _status_label(row.from_status),
|
|
"to_status": row.to_status,
|
|
"to_label": _status_label(row.to_status),
|
|
"changed_by": str(row.changed_by) if row.changed_by else None,
|
|
"changed_by_name": changed_by_name,
|
|
"actor_kind": row.actor_kind,
|
|
"created_at": row.created_at.isoformat() if row.created_at else None,
|
|
}
|