import uuid from datetime import datetime,timezone from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession from offer.models import Offers,OfferStatusHistory from offer.serializers import serialize_offer from offer.plugins import non_validation_values def _as_uuid(value): if value in (None,""): return None try: return uuid.UUID(str(value)) except (TypeError,ValueError): return None def _user_id(current_user): if not current_user or not current_user.get("id"): raise HTTPException(status_code=401,detail="Not authenticated") uid=_as_uuid(current_user["id"]) if uid is None: raise HTTPException(status_code=401,detail="Invalid user id") return uid class Offer: def __init__(self,session:AsyncSession): self.session=session async def get_offers(self,offer_id=None,status=None,inbox_id=None,top=None,skip=0): if offer_id is not None: row=await Offers.get_offer_by_id(self.session,offer_id) if not row: raise HTTPException(status_code=404,detail="Offer not found") return serialize_offer(row),1 rows,total=await Offers.fetch_offers( self.session, status=status, inbox_id=inbox_id, top=top, skip=skip or 0, ) return [serialize_offer(r) for r in rows],total async def create_offer(self,payload,current_user): if not payload.get("inbox_id"): raise HTTPException(status_code=422,detail="inbox_id is required") job_post_id=_as_uuid(payload.get("job_post_id")) if job_post_id is None: raise HTTPException(status_code=422,detail="job_post_id is required") candidate_user_id=_as_uuid(payload.get("candidate_user_id")) if candidate_user_id is None: raise HTTPException(status_code=422,detail="candidate_user_id is required") created_by=_user_id(current_user) status=payload.get("status") or "draft" fields={ "inbox_id": int(payload["inbox_id"]), "job_post_id": job_post_id, "candidate_user_id": candidate_user_id, "created_by": created_by, "status": status, } for key in non_validation_values(): if key in payload and payload[key] is not None: fields[key]=payload[key] row=await Offers.insert_offer(self.session,fields) history_data={ "offer_id": row.id, "from_status": None, "to_status": status, "changed_by": created_by, "actor_kind": "user", "change_reason": payload.get("change_reason"), } await OfferStatusHistory.insert_history(self.session,history_data) return serialize_offer(row) async def update_offer(self,offer_id,payload,current_user): row=await Offers.get_offer_by_id(self.session,offer_id) if not row: raise HTTPException(status_code=404,detail="Offer not found") changed_by=_user_id(current_user) fields={} for key in ( "status","base_salary","currency","salary_period","signing_bonus","annual_bonus_pct", "equity_units","equity_instrument","start_date","expiry_date","sent_at", "responded_at","closed_at","issued_by","inbox_id","job_post_id","candidate_user_id", ): if key not in payload: continue value=payload[key] if key in ("job_post_id","candidate_user_id","issued_by") and value is not None: value=_as_uuid(value) if value is None: raise HTTPException(status_code=422,detail=f"Invalid {key}") fields[key]=value if not fields: raise HTTPException(status_code=400,detail="No fields to update") new_status=fields.get("status") if new_status is not None and new_status!=row.status: await OfferStatusHistory.insert_history(self.session,{ "offer_id": row.id, "from_status": row.status, "to_status": new_status, "changed_by": changed_by, "actor_kind": "user", "change_reason": payload.get("change_reason"), },commit=False) updated=await Offers.update_offer(self.session,offer_id,fields) if not updated: raise HTTPException(status_code=404,detail="Offer not found") return serialize_offer(updated) async def issue_offer(self,offer_id,current_user): row=await Offers.get_offer_by_id(self.session,offer_id) if not row: raise HTTPException(status_code=404,detail="Offer not found") issued_by=_user_id(current_user) now=datetime.now(timezone.utc) from_status=row.status fields={"issued_by": issued_by,"sent_at": now} to_status=from_status if from_status=="draft": fields["status"]="sent" to_status="sent" updated=await Offers.update_offer(self.session,offer_id,fields) if not updated: raise HTTPException(status_code=404,detail="Offer not found") history_data={ "offer_id": updated.id, "from_status": from_status, "to_status": to_status, "changed_by": issued_by, "actor_kind": "user", "change_reason": "issued", } await OfferStatusHistory.insert_history(self.session,history_data) return serialize_offer(updated)