feat: clinic doc api

This commit is contained in:
deepvasoya 2025-05-26 12:11:55 +05:30
parent b2fb39ff87
commit 36c439ba0e
8 changed files with 273 additions and 72 deletions

View File

@ -3,23 +3,34 @@ from schemas.ApiResponse import ApiResponse
from schemas.CreateSchemas import ClinicDoctorCreate
from schemas.UpdateSchemas import ClinicDoctorUpdate
from services.clinicDoctorsServices import ClinicDoctorsServices
from fastapi import Request
from utils.constants import DEFAULT_ORDER, DEFAULT_ORDER_BY, DEFAULT_PAGE, DEFAULT_LIMIT
router = APIRouter()
@router.get("/")
async def get_clinic_doctors():
clinic_doctors = await ClinicDoctorsServices().get_clinic_doctors()
async def get_clinic_doctors(
limit:int= DEFAULT_LIMIT,
page:int = DEFAULT_PAGE,
search:str = "",
sort_by:str = DEFAULT_ORDER,
sort_order:str = DEFAULT_ORDER_BY
):
if page < 1:
page = 1
offset = (page - 1) * limit
clinic_doctors = await ClinicDoctorsServices().get_clinic_doctors(limit, offset, search, sort_by, sort_order)
return ApiResponse(data=clinic_doctors, message="Clinic doctors retrieved successfully")
@router.post("/")
async def create_clinic_doctor(clinic_doctor: ClinicDoctorCreate):
clinic_doctor = await ClinicDoctorsServices().create_clinic_doctor(clinic_doctor)
return ApiResponse(data=clinic_doctor, message="Clinic doctor created successfully")
async def create_clinic_doctor(req:Request, clinic_doctor: ClinicDoctorCreate):
await ClinicDoctorsServices().create_clinic_doctor(req.state.user, clinic_doctor)
return ApiResponse(data="OK", message="Clinic doctor created successfully")
@router.put("/{clinic_doctor_id}")
async def update_clinic_doctor(clinic_doctor_id: int, clinic_doctor: ClinicDoctorUpdate):
clinic_doctor = await ClinicDoctorsServices().update_clinic_doctor(clinic_doctor_id, clinic_doctor)
return ApiResponse(data=clinic_doctor, message="Clinic doctor updated successfully")
async def update_clinic_doctor(req:Request, clinic_doctor_id: int, clinic_doctor: ClinicDoctorUpdate):
await ClinicDoctorsServices().update_clinic_doctor(req.state.user, clinic_doctor_id, clinic_doctor)
return ApiResponse(data="OK", message="Clinic doctor updated successfully")
@router.delete("/{clinic_doctor_id}")
async def delete_clinic_doctor(clinic_doctor_id: int):

View File

@ -1,9 +1,10 @@
from sqlalchemy import Column, Enum, Integer, String, ForeignKey,Table
from sqlalchemy import Column, Enum, Integer, String, ForeignKey, Table
from database import Base
from enums.enums import ClinicDoctorType, ClinicDoctorStatus
from .CustomBase import CustomBase
from sqlalchemy.orm import relationship
class ClinicDoctors(Base, CustomBase):
__tablename__ = "clinic_doctors"
@ -12,7 +13,11 @@ class ClinicDoctors(Base, CustomBase):
role = Column(Enum(ClinicDoctorType))
status = Column(Enum(ClinicDoctorStatus))
appointmentRelations = relationship("AppointmentRelations", back_populates="clinicDoctors")
appointmentRelations = relationship(
"AppointmentRelations",
back_populates="clinicDoctors",
cascade="all, delete-orphan",
passive_deletes=True,
)
clinic_id = Column(Integer, ForeignKey("clinics.id"))
clinic = relationship("Clinics", back_populates="clinicDoctors")

View File

@ -116,7 +116,6 @@ class ClinicDoctorBase(BaseModel):
name: str
role: ClinicDoctorType
status: ClinicDoctorStatus
clinic_id: int
class CallTranscriptsBase(BaseModel):

View File

@ -54,8 +54,10 @@ class UserCreate(BaseModel):
clinic: ClinicBase
class ClinicDoctorCreate(ClinicDoctorBase):
pass
class ClinicDoctorCreate(BaseModel):
name: str
role: ClinicDoctorType
appointmentTypes: list[int]
class CallTranscriptsCreate(CallTranscriptsBase):

View File

@ -5,6 +5,7 @@ from enums.enums import ClinicStatus
from .BaseSchemas import *
from pydantic import Field
# Response schemas (used for API responses)
class Clinic(ClinicBase):
id: int
@ -151,15 +152,6 @@ class AppointmentDetailed(AppointmentSchema):
patient: Patient
class ClinicDoctorResponse(ClinicDoctorBase):
id: int
create_time: datetime
update_time: datetime
class Config:
orm_mode = True
class CallTranscriptsResponse(CallTranscriptsBase):
id: int
create_time: datetime
@ -187,6 +179,18 @@ class MasterAppointmentTypeResponse(MasterAppointmentTypeBase):
orm_mode = True
class ClinicDoctorResponse(ClinicDoctorBase):
id: int
create_time: datetime
update_time: datetime
appointmentTypes: Optional[List[MasterAppointmentTypeResponse]] = []
class Config:
orm_mode = True
from_attributes = True
allow_population_by_field_name = True
class ClinicOfferResponse(ClinicOffersBase):
id: int
create_time: datetime
@ -194,4 +198,3 @@ class ClinicOfferResponse(ClinicOffersBase):
class Config:
orm_mode = True

View File

@ -75,5 +75,8 @@ class UserUpdate(BaseModel):
password: Optional[str] = None
class ClinicDoctorUpdate(ClinicDoctorBase):
pass
class ClinicDoctorUpdate(BaseModel):
name: Optional[str] = None
role: Optional[ClinicDoctorType] = None
status: Optional[ClinicDoctorStatus] = None
appointmentTypes: Optional[list[int]] = None

View File

@ -1,63 +1,180 @@
from loguru import logger
from schemas.CreateSchemas import ClinicDoctorCreate
from schemas.UpdateSchemas import ClinicDoctorUpdate
from schemas.ResponseSchemas import ClinicDoctorResponse
from schemas.ResponseSchemas import ClinicDoctorResponse, MasterAppointmentTypeResponse
from database import get_db
from models import ClinicDoctors
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, joinedload, selectinload
from services.clinicServices import ClinicServices
from exceptions import ResourceNotFoundException
from interface.common_response import CommonResponse
from sqlalchemy import func
from enums.enums import ClinicDoctorStatus
from sqlalchemy import func, or_, cast, String
from enums.enums import ClinicDoctorStatus, UserType
from models import MasterAppointmentTypes, AppointmentRelations
from utils.constants import DEFAULT_ORDER, DEFAULT_ORDER_BY
class ClinicDoctorsServices:
def __init__(self):
self.db: Session = next(get_db())
self.clinic_services = ClinicServices()
self.logger = logger
async def create_clinic_doctor(self, clinic_doctor: ClinicDoctorCreate) -> ClinicDoctorResponse:
async def create_clinic_doctor(
self, user, clinic_doctor: ClinicDoctorCreate
) -> ClinicDoctorResponse:
try:
if user["userType"] != UserType.CLINIC_ADMIN:
self.logger.error("user is not clinic admin")
raise ResourceNotFoundException(
"You are not authorized to perform this action"
)
if not user["created_clinics"][0]["id"]:
self.logger.error("user has no clinics")
raise ResourceNotFoundException(
"You are not authorized to perform this action"
)
# verify all appointment types exist in a single query
if clinic_doctor.appointmentTypes:
existing_types = {
t.id
for t in self.db.query(MasterAppointmentTypes.id)
.filter(
MasterAppointmentTypes.id.in_(clinic_doctor.appointmentTypes)
)
.all()
}
missing_types = set(clinic_doctor.appointmentTypes) - existing_types
if missing_types:
raise ResourceNotFoundException(
f"Appointment types not found: {', '.join(map(str, missing_types))}"
)
# check if clinic exists
self.clinic_services.get_clinic_by_id(clinic_doctor.clinic_id)
await self.clinic_services.get_clinic_by_id(user["created_clinics"][0]["id"])
# exclude appointmentTypes from clinic_doctor
clinic_doctor_db = ClinicDoctors(
name=clinic_doctor.name,
clinic_id=user["created_clinics"][0]["id"],
role=clinic_doctor.role,
status=ClinicDoctorStatus.ACTIVE,
)
self.db.add(clinic_doctor_db)
self.db.flush()
# create clinic doctor appointment types
for appointment_type_id in clinic_doctor.appointmentTypes:
clinic_doctor_appointment_type = AppointmentRelations(
clinic_doctor_id=clinic_doctor_db.id,
appointment_type_id=appointment_type_id,
)
self.db.add(clinic_doctor_appointment_type)
clinic_doctor = ClinicDoctors(**clinic_doctor.model_dump())
self.db.add(clinic_doctor)
self.db.commit()
self.db.refresh(clinic_doctor)
return ClinicDoctorResponse(**clinic_doctor.__dict__.copy())
async def update_clinic_doctor(self, clinic_doctor_id: int, clinic_doctor_data: ClinicDoctorUpdate) -> ClinicDoctorResponse:
return
except Exception as e:
self.logger.error(e)
self.db.rollback()
raise e
async def update_clinic_doctor(
self, user, clinic_doctor_id: int, clinic_doctor_data: ClinicDoctorUpdate
) -> ClinicDoctorResponse:
try:
if user["userType"] != UserType.CLINIC_ADMIN:
self.logger.error("user is not clinic admin")
raise ResourceNotFoundException(
"You are not authorized to perform this action"
)
if not user["created_clinics"][0]["id"]:
self.logger.error("user has no clinics")
raise ResourceNotFoundException(
"You are not authorized to perform this action"
)
# verify all appointment types exist in a single query
if clinic_doctor_data.appointmentTypes:
existing_types = {
t.id
for t in self.db.query(MasterAppointmentTypes.id)
.filter(
MasterAppointmentTypes.id.in_(
clinic_doctor_data.appointmentTypes
)
)
.all()
}
missing_types = set(clinic_doctor_data.appointmentTypes) - existing_types
if missing_types:
raise ResourceNotFoundException(
f"Appointment types not found: {', '.join(map(str, missing_types))}"
)
# check if clinic doctor exists
clinic_doctor = self.db.query(ClinicDoctors).filter(ClinicDoctors.id == clinic_doctor_id).first()
clinic_doctor = (
self.db.query(ClinicDoctors)
.filter(ClinicDoctors.id == clinic_doctor_id)
.first()
)
if clinic_doctor is None:
raise ResourceNotFoundException("Clinic doctor not found")
if clinic_doctor_data.clinic_id != clinic_doctor.clinic_id:
# check if clinic exists
self.clinic_services.get_clinic_by_id(clinic_doctor_data.clinic_id)
# Update the existing object with new values
update_data = clinic_doctor_data.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(clinic_doctor, key, value)
self.db.add(clinic_doctor)
# delete existing clinic doctor appointment types
self.db.query(AppointmentRelations).filter(
AppointmentRelations.clinic_doctor_id == clinic_doctor_id
).delete()
# create clinic doctor appointment types
for appointment_type_id in clinic_doctor_data.appointmentTypes:
clinic_doctor_appointment_type = AppointmentRelations(
clinic_doctor_id=clinic_doctor_id,
appointment_type_id=appointment_type_id,
)
self.db.add(clinic_doctor_appointment_type)
self.db.commit()
self.db.refresh(clinic_doctor)
return ClinicDoctorResponse(**clinic_doctor.__dict__.copy())
return
except Exception as e:
self.db.rollback()
raise e
async def delete_clinic_doctor(self, clinic_doctor_id: int):
clinic_doctor = self.db.query(ClinicDoctors).filter(ClinicDoctors.id == clinic_doctor_id).first()
clinic_doctor = (
self.db.query(ClinicDoctors)
.filter(ClinicDoctors.id == clinic_doctor_id)
.first()
)
self.db.delete(clinic_doctor)
self.db.commit()
async def get_doctor_status_count(self):
# Query to count doctors by status
status_counts = self.db.query(
ClinicDoctors.status,
func.count(ClinicDoctors.id).label('count')
).group_by(ClinicDoctors.status).all()
status_counts = (
self.db.query(
ClinicDoctors.status, func.count(ClinicDoctors.id).label("count")
)
.group_by(ClinicDoctors.status)
.all()
)
# Initialize result dictionary with all possible statuses set to 0
result = {status.value: 0 for status in ClinicDoctorStatus}
@ -68,12 +185,73 @@ class ClinicDoctorsServices:
return result
async def get_clinic_doctors(self):
clinic_doctors = self.db.query(ClinicDoctors).all()
async def get_clinic_doctors(self, limit: int, offset: int, search: str = "", sort_by: str = DEFAULT_ORDER, sort_order: str = DEFAULT_ORDER_BY):
try:
clinic_doctors_query = (
self.db.query(ClinicDoctors)
.options(
selectinload(ClinicDoctors.appointmentRelations)
.selectinload(AppointmentRelations.masterAppointmentTypes)
)
.order_by(
getattr(ClinicDoctors, sort_by).desc()
if sort_order == "desc"
else getattr(ClinicDoctors, sort_by).asc()
)
)
total = self.db.query(ClinicDoctors).count()
response = CommonResponse(data=[ClinicDoctorResponse(**clinic_doctor.__dict__.copy()) for clinic_doctor in clinic_doctors], total=total)
if search:
clinic_doctors_query = clinic_doctors_query.filter(
or_(
ClinicDoctors.name.ilike(f"%{search}%"),
cast(ClinicDoctors.role, String).ilike(f"%{search}%"),
ClinicDoctors.appointmentRelations.any(
AppointmentRelations.masterAppointmentTypes.has(
MasterAppointmentTypes.type.ilike(f"%{search}%")
)
)
)
)
total = clinic_doctors_query.count()
clinic_doctors = clinic_doctors_query.limit(limit).offset(offset).all()
# Build response data manually to include appointment types
response_data = []
for clinic_doctor in clinic_doctors:
# Extract appointment types from the relationships
appointment_types = []
for relation in clinic_doctor.appointmentRelations:
if relation.masterAppointmentTypes:
appointment_types.append(
MasterAppointmentTypeResponse(
id=relation.masterAppointmentTypes.id,
type=relation.masterAppointmentTypes.type,
create_time=relation.masterAppointmentTypes.create_time,
update_time=relation.masterAppointmentTypes.update_time
)
)
# Create the clinic doctor response
clinic_doctor_data = ClinicDoctorResponse(
id=clinic_doctor.id,
name=clinic_doctor.name,
role=clinic_doctor.role,
status=clinic_doctor.status,
create_time=clinic_doctor.create_time,
update_time=clinic_doctor.update_time,
appointmentTypes=appointment_types
)
response_data.append(clinic_doctor_data)
response = CommonResponse(
data=response_data,
total=total,
)
return response
except Exception as e:
self.logger.error(e)
raise e

View File

@ -6,8 +6,8 @@ dotenv.load_dotenv()
DEFAULT_SKIP = 0
DEFAULT_PAGE = 1
DEFAULT_LIMIT = 10
DEFAULT_ORDER_BY = "id"
DEFAULT_ORDER = "desc"
DEFAULT_ORDER = "id"
DEFAULT_ORDER_BY = "desc"
# jwt
JWT_ALGORITHM = os.getenv("JWT_ALGORITHM")