HR-ATS-Portal/backend/candidate_forms/app.py

239 lines
8.4 KiB
Python

from datetime import datetime, date
from dis import Positions
from typing import Type
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import JSONResponse
import uuid
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Optional
from candidate_forms.plugins import definitions_payload
from candidate_forms.views import CandidateForm
from db_setup import get_session
from users.permissions import PermissionTag, require_permission
from candidate_forms.enums import EmploymentType, Position, ReplacementFor, InternalRecommendate
from candidate_forms.views import RequisitionForm
router = APIRouter()
class RequisitionFormCreate(BaseModel):
form_type: str = "requisition"
position:Position
replacement_for:Optional[ReplacementFor]
refferal_by:Optional[InternalRecommendate]
initiated_by:Optional[str]
initiated_date:Optional[date]
recommended_by:Optional[str]
recommended_date:Optional[date]
approved_by_hr:Optional[bool]
approved_by_date_hr:Optional[date]
approved_by_vp:Optional[bool]
approved_by_date_vp:Optional[date]
approved_by_svp:Optional[bool]
approved_by_date_svp:Optional[date]
class RequisitionFormUpdate(BaseModel):
position:Optional[Position]=None
replacement_for:Optional[ReplacementFor]=None
refferal_by:Optional[InternalRecommendate]=None
initiated_by:Optional[str]=None
initiated_date:Optional[date]=None
recommended_by:Optional[str]=None
recommended_date:Optional[date]=None
approved_by_hr:Optional[bool]=None
approved_by_date_hr:Optional[date]=None
approved_by_vp:Optional[bool]=None
approved_by_date_vp:Optional[date]=None
approved_by_svp:Optional[bool]=None
approved_by_date_svp:Optional[date]=None
class FormCreate(BaseModel):
form_type: str
inbox_id: int | None = None
manual_upload_candidate_id: str | None = None
job_post_id: str | None = None
interviewer_id: str | None = None
form_date: datetime | None = None
sections: list | None = None
fields: dict | None = None
recommendation: str | None = None
class FormUpdate(BaseModel):
interviewer_id: str | None = None
form_date: datetime | None = None
sections: list | None = None
fields: dict | None = None
recommendation: str | None = None
@router.get("/forms/requisition/search")
async def search_requisitions(
current_user: dict = Depends(
require_permission(
PermissionTag.REQUISITIONS_VIEW,
PermissionTag.JOB_BOARD_CREATE,
PermissionTag.JOBS_CREATE,
require_all=False,
)
),
q: str | None = Query(None),
top: int = Query(50, ge=1, le=100),
job_post_id: uuid.UUID | None = Query(None),
session: AsyncSession = Depends(get_session),
):
"""Searchable picker for job create: `{position_title} - {department}`.
`q` matches either field (ilike). Empty `q` returns recent rows.
Linked requisitions are omitted (1:1 with job posts). Pass `job_post_id`
on edit so the job's current requisition remains selectable until unlinked.
"""
try:
service = RequisitionForm(session=session)
data = await service.search(q, top=top, job_post_id=job_post_id)
return JSONResponse(content={"data": data, "status_code": 200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/forms/requisition/fetch")
async def fetch_requisition_form(
current_user:dict=Depends(require_permission(PermissionTag.REQUISITIONS_VIEW)),
form_id:str=Query(None),
session:AsyncSession=Depends(get_session),
):
"""Requisition table. Admins get every non-deleted row (job link does not
hide anything). Other roles stay scoped to created_by."""
try:
service=RequisitionForm(session=session)
data=await service.get_form_by_id(form_id,current_user)
return JSONResponse(content={"data":data,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/forms/requisition/create")
async def create_requisition_form(
payload: RequisitionFormCreate,
current_user:dict=Depends(require_permission(PermissionTag.REQUISITIONS_CREATE)),
session:AsyncSession=Depends(get_session),
):
try:
service = RequisitionForm(session=session)
data = await service.create_form(payload.model_dump(exclude_unset=True), current_user)
return JSONResponse(content={"data": data, "status_code": 200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.patch("/forms/requisition/update")
async def update_requisition_form(
payload: RequisitionFormUpdate,
current_user:dict=Depends(require_permission(PermissionTag.REQUISITIONS_EDIT)),
form_id:str=Query(...),
session:AsyncSession=Depends(get_session),
):
try:
service=RequisitionForm(session=session)
data=await service.update_form(form_id,payload.model_dump(exclude_unset=True),current_user)
return JSONResponse(content={"data":data,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.get("/forms/definitions")
async def fetch_form_definitions(
current_user: dict = Depends(require_permission(PermissionTag.INTERVIEWS_VIEW)),
):
try:
return JSONResponse(content={"data": definitions_payload(), "status_code": 200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/forms/fetch")
async def fetch_forms(
current_user: dict = Depends(require_permission(PermissionTag.INTERVIEWS_VIEW)),
form_id: str | None = Query(None),
inbox_id: int | None = Query(None),
manual_upload_candidate_id: str | None = Query(None),
job_post_id: str | None = Query(None),
form_type: str | None = Query(None),
top: int | None = Query(None),
skip: int = Query(0, ge=0),
session: AsyncSession = Depends(get_session),
):
try:
service = CandidateForm(session=session)
data, summary, total = await service.get_forms(
form_id, inbox_id, manual_upload_candidate_id, job_post_id, form_type, top, skip,
current_user=current_user,
)
return JSONResponse(
content={"data": data, "summary": summary, "total": total, "status_code": 200}
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/forms/create")
async def create_form(
payload: FormCreate,
current_user: dict = Depends(require_permission(PermissionTag.INTERVIEWS_CREATE)),
session: AsyncSession = Depends(get_session),
):
try:
service = CandidateForm(session=session)
data = await service.create_form(payload.model_dump(exclude_unset=True), current_user)
return JSONResponse(content={"data": data, "status_code": 200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.patch("/forms/update")
async def update_form(
payload: FormUpdate,
current_user: dict = Depends(require_permission(PermissionTag.INTERVIEWS_EDIT)),
form_id: str = Query(...),
session: AsyncSession = Depends(get_session),
):
try:
service = CandidateForm(session=session)
data = await service.update_form(
form_id, payload.model_dump(exclude_unset=True), current_user
)
return JSONResponse(content={"data": data, "status_code": 200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/forms/delete")
async def delete_form(
current_user: dict = Depends(require_permission(PermissionTag.INTERVIEWS_DELETE)),
form_id: str = Query(...),
session: AsyncSession = Depends(get_session),
):
try:
service = CandidateForm(session=session)
data = await service.delete_form(form_id, current_user)
return JSONResponse(content={"data": data, "status_code": 200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))