From addbc77d2b7b20861d74d6f932ff39c58f770b7f Mon Sep 17 00:00:00 2001 From: "ahmed.mujtaba" Date: Mon, 14 Sep 2026 14:55:01 +0500 Subject: [PATCH] . --- backend/department/app.py | 54 ++++++++++++++++++++++ backend/department/models.py | 74 +++++++++++++++++++++++++++++++ backend/department/serializers.py | 0 backend/department/views.py | 28 ++++++++++++ backend/users/permissions.py | 11 +++++ 5 files changed, 167 insertions(+) create mode 100644 backend/department/app.py create mode 100644 backend/department/models.py create mode 100644 backend/department/serializers.py create mode 100644 backend/department/views.py diff --git a/backend/department/app.py b/backend/department/app.py new file mode 100644 index 0000000..2aefe32 --- /dev/null +++ b/backend/department/app.py @@ -0,0 +1,54 @@ +from fastapi import APIRouter,Depends,Query,Response +from fastapi.responses import FileResponse,JSONResponse +from fastapi import HTTPException,Request +from db_setup import get_session +from department.models import Department +from department.views import DepartmentCreate,DepartmentGet +from users.permissions import PermissionTag, require_permission +from sqlalchemy.ext.asyncio import AsyncSession +import uuid +import json +from dotenv import load_dotenv +import logging +from typing import Optional,Annotated +from users.permissions import get_current_user +from pydantic import BaseModel +from uuid import UUID +from typing import Literal, Optional +import os +import uuid + +load_dotenv() +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +router = APIRouter() + +@router.post("/create") +async def create_department( + request: Request, + session: Annotated[AsyncSession, Depends(get_session)], + current_user: Annotated[dict, Depends(get_current_user)]): + try: + data=await request.json() + department_data=DepartmentCreate(data,session) + department=await department_data.create_department() + return JSONResponse(status_code=201, content={"message": "Department created successfully", "department": department.model_dump()}) + except Exception as e: + logger.error(f"Error creating department: {e}") + raise HTTPException(status_code=500, detail=str(e)) + +@router.get("/get/{department_id}") +async def get_department_by_id( + session: Annotated[AsyncSession, Depends(get_session)], + department_id:Optional[UUID] = None, + current_user: Annotated[dict, Depends(get_current_user)]): + try: + if not department_id: + # send back the list of all departments + department_data=DepartmentGet(department_id,session) + department=await department_data.get_department_by_id() + return JSONResponse(status_code=200, content={"message": "Department fetched successfully", "department": department.model_dump()}) + except Exception as e: + logger.error(f"Error fetching department: {e}") + raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file diff --git a/backend/department/models.py b/backend/department/models.py new file mode 100644 index 0000000..e01d72b --- /dev/null +++ b/backend/department/models.py @@ -0,0 +1,74 @@ +import uuid +from datetime import datetime, timezone +from typing import Optional,List +from sqlalchemy import DateTime, func +from sqlalchemy.ext.asyncio import AsyncSession +from sqlmodel import Field, SQLModel, select +from fastapi import HTTPException +from users.models import Users + +def _now() -> datetime: + return datetime.now(timezone.utc) + +class Department(SQLModel, table=True): + __tablename__ = "departments" + id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) + name: str = Field(index=True,unique=True) + description: Optional[str] = Field(default="") + short_code: Optional[str] = Field(default="",max_length=10) + is_active: bool = Field(default=True) + parent_department_id: Optional[uuid.UUID] = Field(foreign_key="departments.id") + department_head_id: Optional[uuid.UUID] = Field(foreign_key="users.id") + location:Optional[List[str]] = Field(default_factory=list) + subtitle:Optional[str] = Field(default="") + created_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True)) + updated_at: datetime = Field(default_factory=_now, sa_type=DateTime(timezone=True)) + created_by: Optional[uuid.UUID] = Field(foreign_key="users.id") + updated_by: Optional[uuid.UUID] = Field(foreign_key="users.id") + + @staticmethod + def _as_uuid(record_id) -> uuid.UUID | None: + if record_id in (None, ""): + return None + try: + return uuid.UUID(str(record_id)) + except ValueError: + return None + + @classmethod + async def create_department(cls,session:AsyncSession,data:dict): + try: + uid=cls._as_uuid(data.get("parent_department_id")) + if data.get("department_head_id"): + user=await Users.get_by_id(session,data.get("department_head_id")) + if not user: + raise HTTPException(status_code=404, detail="User not found") + department_head_id=user.id + else: + raise HTTPException(status_code=400, detail="Department head ID is required") + + department=cls( + name=data.get("name"), + description=data.get("description"), + short_code=data.get("short_code"), + parent_department_id=uid if data.get("parent_department_id") else None, + department_head_id=department_head_id, + location=data.get("location"), + subtitle=data.get("subtitle"), + created_by=data.get("created_by"), + updated_by=data.get("updated_by"), + ) + session.add(department) + await session.commit() + return department + except Exception as e: + session.rollback() + raise HTTPException(status_code=500, detail=str(e)) + + @classmethod + async def get_department_by_id(cls,session:AsyncSession,department_id:uuid.UUID): + uid=cls._as_uuid(department_id) + if uid is None: + raise ValueError("Invalid department ID") + result=await session.execute(select(cls).where(cls.id == uid)) + return result.scalars().first() \ No newline at end of file diff --git a/backend/department/serializers.py b/backend/department/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/department/views.py b/backend/department/views.py new file mode 100644 index 0000000..6d06f41 --- /dev/null +++ b/backend/department/views.py @@ -0,0 +1,28 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from department.models import Department +from fastapi import HTTPException + +class DepartmentCreate: + def __init__(self,data,db=AsyncSession) -> None: + self.data=data + self.db=db + + async def create_department(self): + try: + department=Department.create_department(self.db,self.data) + return department + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + +class DepartmentGet(DepartmentCreate): + def __init__(self,department_id,db=AsyncSession) -> None: + self.department_id=department_id + super().__init__(None,self.db) + + async def get_department_by_id(self): + try: + department=Department.get_department_by_id(self.db,self.department_id) + return department + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + diff --git a/backend/users/permissions.py b/backend/users/permissions.py index 728bc19..8263b56 100644 --- a/backend/users/permissions.py +++ b/backend/users/permissions.py @@ -30,6 +30,7 @@ class PermissionModule(str, Enum): JOBS = "jobs" CANDIDATES = "candidates" PIPELINE = "pipeline" + DEPARTMENT = "department" INTERVIEWS = "interviews" ASSESSMENTS = "assessments" OFFERS = "offers" @@ -71,6 +72,16 @@ class PermissionTag(str, Enum): DASHBOARD_EXPORT = "dashboard.export" DASHBOARD_MANAGE = "dashboard.manage" DASHBOARD_CONFIGURE = "dashboard.configure" + + DEPARTMENT_VIEW = "department.view" + DEPARTMENT_CREATE = "department.create" + DEPARTMENT_EDIT = "department.edit" + DEPARTMENT_DELETE = "department.delete" + DEPARTMENT_APPROVE = "department.approve" + DEPARTMENT_EXPORT = "department.export" + DEPARTMENT_MANAGE = "department.manage" + DEPARTMENT_CONFIGURE = "department.configure" + INBOX_VIEW = "inbox.view" INBOX_CREATE = "inbox.create" INBOX_EDIT = "inbox.edit"