Compare commits

...

1 Commits

Author SHA1 Message Date
ahmed.mujtaba 381d7a7bdd committed 2026-09-11 16:18:01 +05:00
8 changed files with 109 additions and 0 deletions

View File

@ -198,6 +198,11 @@ class FormDuplicateBody(BaseModel):
is_duplicate: bool is_duplicate: bool
class FormDataRatingBody(BaseModel):
favorite: bool | None = None
rating: float | None = None
@router.get("/sheet/form-data/sheets") @router.get("/sheet/form-data/sheets")
async def fetch_form_data_sheets( async def fetch_form_data_sheets(
current_user: dict = Depends(_FORM_DATA_READ), current_user: dict = Depends(_FORM_DATA_READ),
@ -364,6 +369,23 @@ async def set_form_duplicate(
raise HTTPException(status_code=500,detail=str(e)) raise HTTPException(status_code=500,detail=str(e))
@router.patch("/sheet/form-data/{record_id}/rating")
async def set_form_rating(
record_id: str,
payload: FormDataRatingBody,
current_user: dict = Depends(_FORM_DATA_EDIT),
session: AsyncSession = Depends(get_session),
):
try:
service=SheetFormData(session=session)
data=await service.set_rating(record_id,payload.model_dump(exclude_unset=True))
return JSONResponse(content={"data":data,"total":1,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.delete("/sheet/form-data/{tab}/delete") @router.delete("/sheet/form-data/{tab}/delete")
async def delete_form_data_sheet( async def delete_form_data_sheet(
tab: str, tab: str,

View File

@ -225,6 +225,8 @@ class FormDataColumn(str, Enum):
# Same vocabulary as inbox_messages — Import / Shortlist / Reject / Duplicate. # Same vocabulary as inbox_messages — Import / Shortlist / Reject / Duplicate.
PROCESSING_STATE = "processing_state" PROCESSING_STATE = "processing_state"
IS_DUPLICATE = "is_duplicate" IS_DUPLICATE = "is_duplicate"
FAVORITE = "favorite"
RATING = "rating"
REAPPLIED = "reapplied" REAPPLIED = "reapplied"
RAW_RECORD = "raw_record" RAW_RECORD = "raw_record"
IMPORTED_AT = "imported_at" IMPORTED_AT = "imported_at"

View File

@ -107,6 +107,10 @@ class FormData(SQLModel, table=True):
processing_state: str = Field(default="unread", sa_column_kwargs={"server_default": "unread"}) processing_state: str = Field(default="unread", sa_column_kwargs={"server_default": "unread"})
is_duplicate: bool = Field(default=False, sa_column_kwargs={"server_default": "false"}) is_duplicate: bool = Field(default=False, sa_column_kwargs={"server_default": "false"})
# Recruiter-set, same shape as inbox.favorite/inbox.rating.
favorite: bool | None = Field(default=False)
rating: float | None = Field(default=0.0)
raw_record: dict | None = Field(default=None, sa_column=Column(JSONB)) raw_record: dict | None = Field(default=None, sa_column=Column(JSONB))
imported_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True)) imported_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True))
created_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True)) created_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True))
@ -439,6 +443,21 @@ class FormData(SQLModel, table=True):
await session.refresh(row) await session.refresh(row)
return row return row
@classmethod
async def set_rating_favorite(cls, session: AsyncSession, record_id, favorite=None, rating=None):
row = await cls.get_form_data_by_id(session, record_id)
if not row:
return None
if favorite is not None:
row.favorite = bool(favorite)
if rating is not None:
row.rating = float(rating)
row.updated_at = _now()
session.add(row)
await session.commit()
await session.refresh(row)
return row
@classmethod @classmethod
async def link_manual_upload(cls, session: AsyncSession, record_id, manual_upload_candidate_id, *, commit: bool = True): async def link_manual_upload(cls, session: AsyncSession, record_id, manual_upload_candidate_id, *, commit: bool = True):
row = await cls.get_form_data_by_id(session, record_id) row = await cls.get_form_data_by_id(session, record_id)

View File

@ -603,6 +603,15 @@ class SheetFormData(Sheet):
raise HTTPException(status_code=404,detail="Form data not found") raise HTTPException(status_code=404,detail="Form data not found")
return await self.get_form_data_by_id(record_id) return await self.get_form_data_by_id(record_id)
async def set_rating(self,record_id,payload):
fields={k:v for k,v in (payload or {}).items() if k in ("favorite","rating") and v is not None}
if not fields:
raise HTTPException(status_code=400,detail="favorite or rating is required")
updated=await FormData.set_rating_favorite(self._require_session(),record_id,**fields)
if not updated:
raise HTTPException(status_code=404,detail="Form data not found")
return await self.get_form_data_by_id(record_id)
async def get_counts(self,sheet=None,search=None,has_linkedin=None,has_resume=None,city=None,source=None,assigned=None,job_post_ids=None): async def get_counts(self,sheet=None,search=None,has_linkedin=None,has_resume=None,city=None,source=None,assigned=None,job_post_ids=None):
return await FormData.count_processing( return await FormData.count_processing(
self._require_session(),sheet=sheet,search=search, self._require_session(),sheet=sheet,search=search,

View File

@ -57,6 +57,11 @@ class CandidateUpdate(BaseModel):
rating: float | None = None rating: float | None = None
class ManualUploadRatingUpdate(BaseModel):
favorite: bool | None = None
rating: float | None = None
class InterviewCreate(BaseModel): class InterviewCreate(BaseModel):
inbox_id: int inbox_id: int
interview_date: datetime | None = None interview_date: datetime | None = None
@ -1115,6 +1120,23 @@ async def update_candidate(
raise HTTPException(status_code=500,detail=str(e)) raise HTTPException(status_code=500,detail=str(e))
@router.patch("/candidate/manual-upload/rating")
async def update_manual_upload_rating(
id:str=Query(...),
payload:ManualUploadRatingUpdate=...,
current_user: dict = Depends(require_permission(PermissionTag.CANDIDATES_EDIT)),
session: AsyncSession = Depends(get_session),
):
try:
service=CandidateView(session=session)
data=await service.update_manual_upload_rating(id,payload.model_dump(exclude_unset=True))
return JSONResponse(content={"data":data,"total":1,"status_code":200})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@router.get("/candidate/history/fetch") @router.get("/candidate/history/fetch")
async def fetch_candidate_history( async def fetch_candidate_history(
user_id:str=Query(...), user_id:str=Query(...),

View File

@ -72,6 +72,10 @@ class Manual_UPLOAD_CANDIDATE(SQLModel, table=True):
referral_by: str = Field(default="", sa_column_kwargs={"server_default": ""}) referral_by: str = Field(default="", sa_column_kwargs={"server_default": ""})
file_name: str = Field(default="", sa_column_kwargs={"server_default": ""}) file_name: str = Field(default="", sa_column_kwargs={"server_default": ""})
file_path: str = Field(default="", sa_column_kwargs={"server_default": ""}) file_path: str = Field(default="", sa_column_kwargs={"server_default": ""})
# Recruiter-set, same shape as inbox.favorite/inbox.rating — this row's own
# copy, not derived from Inbox (manual_upload_candidate has no inbox link).
favorite: Optional[bool] = Field(default=False)
rating: Optional[float] = Field(default=0.0)
created_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True)) created_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True))
updated_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True)) updated_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True))
@ -395,6 +399,21 @@ class Manual_UPLOAD_CANDIDATE(SQLModel, table=True):
result = await session.execute(select(cls).where(cls.id == uid)) result = await session.execute(select(cls).where(cls.id == uid))
return result.scalars().first() return result.scalars().first()
@classmethod
async def set_rating_favorite(cls, session: AsyncSession, record_id, favorite=None, rating=None):
row=await cls.get_by_id(session,record_id)
if not row:
return None
if favorite is not None:
row.favorite=favorite
if rating is not None:
row.rating=rating
row.updated_at=_now()
session.add(row)
await session.commit()
await session.refresh(row)
return row
@classmethod @classmethod
async def set_full_text(cls, session: AsyncSession, record_id, text, *, only_if_empty=True): async def set_full_text(cls, session: AsyncSession, record_id, text, *, only_if_empty=True):
"""Keep extracted CV text on the bank/manual row (Inbox uses resume_text).""" """Keep extracted CV text on the bank/manual row (Inbox uses resume_text)."""

View File

@ -202,6 +202,8 @@ def serialize_manual_upload_candidate(row) -> Dict[str,Any]:
"referral_by":row.referral_by, "referral_by":row.referral_by,
"file_name":row.file_name, "file_name":row.file_name,
"file_path":row.file_path, "file_path":row.file_path,
"favorite":row.favorite,
"rating":row.rating,
"created_at":row.created_at.isoformat() if row.created_at 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, "updated_at":row.updated_at.isoformat() if row.updated_at else None,
} }

View File

@ -1605,6 +1605,20 @@ class CandidateView:
except Exception as e: except Exception as e:
raise HTTPException(status_code=500,detail=str(e)) raise HTTPException(status_code=500,detail=str(e))
async def update_manual_upload_rating(self,record_id,payload):
try:
fields={k:v for k,v in (payload or {}).items() if k in ("favorite","rating") and v is not None}
if not fields:
raise HTTPException(status_code=400,detail="favorite or rating is required")
row=await Manual_UPLOAD_CANDIDATE.set_rating_favorite(self.session,record_id,**fields)
if not row:
raise HTTPException(status_code=404,detail="Candidate not found")
return serialize_manual_upload_candidate(row)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500,detail=str(e))
@staticmethod @staticmethod
def _attach_job_post(data,payload,*,as_assigned=False): def _attach_job_post(data,payload,*,as_assigned=False):
"""Merge one serialized job post onto a candidate payload. """Merge one serialized job post onto a candidate payload.