diff --git a/apis/endpoints/clinicDoctor.py b/apis/endpoints/clinicDoctor.py index 4b3e491..132ca41 100644 --- a/apis/endpoints/clinicDoctor.py +++ b/apis/endpoints/clinicDoctor.py @@ -7,17 +7,17 @@ from services.clinicDoctorsServices import ClinicDoctorsServices router = APIRouter() -@router.post("/clinic-doctor") +@router.post("/") def create_clinic_doctor(clinic_doctor: ClinicDoctorCreate): clinic_doctor = ClinicDoctorsServices().create_clinic_doctor(clinic_doctor) return ApiResponse(data=clinic_doctor, message="Clinic doctor created successfully") -@router.put("/clinic-doctor/{clinic_doctor_id}") +@router.put("/{clinic_doctor_id}") def update_clinic_doctor(clinic_doctor_id: int, clinic_doctor: ClinicDoctorUpdate): - clinic_doctor = ClinicDoctorsServices().update_clinic_doctor(clinic_doctor) + clinic_doctor = ClinicDoctorsServices().update_clinic_doctor(clinic_doctor_id, clinic_doctor) return ApiResponse(data=clinic_doctor, message="Clinic doctor updated successfully") -@router.delete("/clinic-doctor/{clinic_doctor_id}") +@router.delete("/{clinic_doctor_id}") def delete_clinic_doctor(clinic_doctor_id: int): ClinicDoctorsServices().delete_clinic_doctor(clinic_doctor_id) return ApiResponse(data="OK", message="Clinic doctor deleted successfully") diff --git a/apis/endpoints/clinics.py b/apis/endpoints/clinics.py index 1ab2f0f..4230a40 100644 --- a/apis/endpoints/clinics.py +++ b/apis/endpoints/clinics.py @@ -12,7 +12,11 @@ from schemas.CreateSchemas import ClinicCreate from schemas.UpdateSchemas import ClinicUpdate from models.Clinics import Clinics +# services +from services.clinicServices import ClinicServices + # Constants +from schemas.ApiResponse import ApiResponse from utils.constants import DEFAULT_SKIP, DEFAULT_LIMIT router = APIRouter() @@ -20,56 +24,28 @@ router = APIRouter() @router.get("/", response_model=List[Clinic]) async def get_clinics( - skip: int = DEFAULT_SKIP, limit: int = DEFAULT_LIMIT, db: Session = Depends(get_db) + skip: int = DEFAULT_SKIP, limit: int = DEFAULT_LIMIT ): - clinics = db.query(Clinics).offset(skip).limit(limit).all() - return clinics + clinics = ClinicServices().get_clinics(skip, limit) + return ApiResponse(data=clinics, message="Clinics retrieved successfully", status_code=status.HTTP_200_OK) -@router.get("/{clinic_id}", response_model=ClinicWithDoctors) -async def get_clinic(clinic_id: int, db: Session = Depends(get_db)): - db_clinic = db.query(Clinics).where(Clinics.id == clinic_id).first() - if db_clinic is None: - raise HTTPException(status_code=404, detail="Clinic not found") - return db_clinic - - -@router.post("/", response_model=Clinic, status_code=status.HTTP_201_CREATED) -async def create_clinic(clinic: ClinicCreate, db: Session = Depends(get_db)): - try: - db_clinic = Clinics(**clinic.model_dump()) - db.add(db_clinic) - db.commit() - db.refresh(db_clinic) - return db_clinic - except Exception as e: - db.rollback() - - raise HTTPException( - status_code=500, - detail=str(e.__cause__), - ) from e +@router.get("/{clinic_id}") +async def get_clinic(clinic_id: int): + clinic = ClinicServices().get_clinic_by_id(clinic_id) + return ApiResponse(data=clinic, message="Clinic retrieved successfully", status_code=status.HTTP_200_OK) @router.put("/{clinic_id}", response_model=Clinic) async def update_clinic( - clinic_id: int, clinic: ClinicUpdate, db: Session = Depends(get_db) + clinic_id: int, clinic: ClinicUpdate ): - db_clinic = db.query(Clinics).filter(Clinics.id == clinic_id).first() - if db_clinic is None: - raise HTTPException(status_code=404, detail="Clinic not found") - - update_data = clinic.model_dump(exclude_unset=True) - for key, value in update_data.items(): - setattr(db_clinic, key, value) - - db.commit() - db.refresh(db_clinic) - return db_clinic + clinic = ClinicServices().update_clinic(clinic_id, clinic) + return ApiResponse(data=clinic, message="Clinic updated successfully", status_code=status.HTTP_200_OK) @router.delete("/{clinic_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_clinic(clinic_id: int, db: Session = Depends(get_db)): +async def delete_clinic(clinic_id: int): db_clinic = db.query(Clinics).where(Clinics.id == clinic_id).first() if db_clinic is None: raise HTTPException(status_code=404, detail="Clinic not found") diff --git a/migrations/versions/0ce7107c1910_fix_enum_clinic_doctor.py b/migrations/versions/0ce7107c1910_fix_enum_clinic_doctor.py new file mode 100644 index 0000000..0884e1b --- /dev/null +++ b/migrations/versions/0ce7107c1910_fix_enum_clinic_doctor.py @@ -0,0 +1,127 @@ +"""fix enum clinic doctor + +Revision ID: 0ce7107c1910 +Revises: a3a9b7d17bdd +Create Date: 2025-05-13 12:20:49.384154 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql +from sqlalchemy import text + +# revision identifiers, used by Alembic. +revision: str = '0ce7107c1910' +down_revision: Union[str, None] = 'a3a9b7d17bdd' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + + # Check if the enum type exists before creating it + conn = op.get_bind() + result = conn.execute(text("SELECT EXISTS(SELECT 1 FROM pg_type WHERE typname = 'clinicdoctortype')")) + enum_exists = result.scalar() + + if not enum_exists: + # Create the new enum type if it doesn't exist + op.execute(text("CREATE TYPE clinicdoctortype AS ENUM ('DOCTOR', 'NURSE')")) + + # Check if the new_role column already exists + inspector = sa.inspect(conn) + columns = inspector.get_columns('clinic_doctors') + column_names = [column['name'] for column in columns] + + if 'new_role' not in column_names: + # Add a temporary column with the new type + op.add_column('clinic_doctors', sa.Column('new_role', postgresql.ENUM('DOCTOR', 'NURSE', name='clinicdoctortype'), nullable=True)) + + # Update the temporary column with values based on the old column + op.execute(text("UPDATE clinic_doctors SET new_role = 'DOCTOR' WHERE role = 'DIRECTOR'")) + op.execute(text("UPDATE clinic_doctors SET new_role = 'NURSE' WHERE role = 'PRACTICE_MANAGER'")) + + # Drop the old column and rename the new one + op.drop_column('clinic_doctors', 'role') + op.alter_column('clinic_doctors', 'new_role', new_column_name='role') + + # We need to handle the users table that depends on the clinicuserroles enum + # First check if the users table has a clinicRole column that uses the enum + has_clinic_role = False + try: + user_columns = inspector.get_columns('users') + for column in user_columns: + if column['name'] == 'clinicRole': + has_clinic_role = True + break + except: + # Table might not exist + pass + + if has_clinic_role: + # We need to update the users table to not use the enum before dropping it + # First, create a temporary column with a string type + op.add_column('users', sa.Column('temp_clinic_role', sa.String(), nullable=True)) + + # Copy the values - use double quotes to preserve case sensitivity in PostgreSQL + op.execute(text('UPDATE users SET temp_clinic_role = "clinicRole"::text')) + + # Drop the old column and rename the new one + op.drop_column('users', 'clinicRole') + op.alter_column('users', 'temp_clinic_role', new_column_name='clinicRole') + + # Now we can safely drop the old enum type + op.execute(text("DROP TYPE IF EXISTS clinicuserroles")) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + + # Create the old enum type + op.execute(text("CREATE TYPE clinicuserroles AS ENUM ('DIRECTOR', 'PRACTICE_MANAGER')")) + + # Add a temporary column with the old type + op.add_column('clinic_doctors', sa.Column('old_role', postgresql.ENUM('DIRECTOR', 'PRACTICE_MANAGER', name='clinicuserroles'), nullable=True)) + + # Update the temporary column with values based on the new column + op.execute(text("UPDATE clinic_doctors SET old_role = 'DIRECTOR' WHERE role = 'DOCTOR'")) + op.execute(text("UPDATE clinic_doctors SET old_role = 'PRACTICE_MANAGER' WHERE role = 'NURSE'")) + + # Drop the new column and rename the old one + op.drop_column('clinic_doctors', 'role') + op.alter_column('clinic_doctors', 'old_role', new_column_name='role') + + # If we modified the users table in the upgrade, we need to restore it + conn = op.get_bind() + inspector = sa.inspect(conn) + has_clinic_role = False + try: + user_columns = inspector.get_columns('users') + for column in user_columns: + if column['name'] == 'clinicRole': + has_clinic_role = True + break + except: + pass + + if has_clinic_role: + # Create a temporary column with the enum type + op.add_column('users', sa.Column('temp_clinic_role', postgresql.ENUM('DIRECTOR', 'PRACTICE_MANAGER', name='clinicuserroles'), nullable=True)) + + # Copy the values (with appropriate conversion) - use double quotes to preserve case sensitivity + op.execute(text('UPDATE users SET temp_clinic_role = "clinicRole"::clinicuserroles')) + + # Drop the old column and rename the new one + op.drop_column('users', 'clinicRole') + op.alter_column('users', 'temp_clinic_role', new_column_name='clinicRole') + + # Drop the new enum type + op.execute(text("DROP TYPE IF EXISTS clinicdoctortype")) + # ### end Alembic commands ### + # ### end Alembic commands ### diff --git a/models/ClinicDoctors.py b/models/ClinicDoctors.py index 138513b..fe58273 100644 --- a/models/ClinicDoctors.py +++ b/models/ClinicDoctors.py @@ -1,6 +1,6 @@ from sqlalchemy import Column, Enum, Integer, String, ForeignKey,Table from database import Base -from enums.enums import ClinicUserRoles, ClinicDoctorStatus +from enums.enums import ClinicDoctorType, ClinicDoctorStatus from .CustomBase import CustomBase from sqlalchemy.orm import relationship @@ -9,9 +9,10 @@ class ClinicDoctors(Base, CustomBase): id = Column(Integer, primary_key=True, index=True) name = Column(String) - role = Column(Enum(ClinicUserRoles)) + role = Column(Enum(ClinicDoctorType)) status = Column(Enum(ClinicDoctorStatus)) appointmentRelations = relationship("AppointmentRelations", back_populates="clinicDoctors") clinic_id = Column(Integer, ForeignKey("clinics.id")) - clinic = relationship("Clinics", back_populates="clinicDoctors") \ No newline at end of file + clinic = relationship("Clinics", back_populates="clinicDoctors") + \ No newline at end of file diff --git a/models/MasterAppointmentTypes.py b/models/MasterAppointmentTypes.py index 241836c..7f53937 100644 --- a/models/MasterAppointmentTypes.py +++ b/models/MasterAppointmentTypes.py @@ -10,4 +10,3 @@ class MasterAppointmentTypes(Base, CustomBase): type = Column(String) appointmentRelations = relationship("AppointmentRelations", back_populates="masterAppointmentTypes") - clinicDoctors = relationship("ClinicDoctors", back_populates="masterAppointmentTypes") \ No newline at end of file diff --git a/schemas/BaseSchemas.py b/schemas/BaseSchemas.py index df1e356..5209c69 100644 --- a/schemas/BaseSchemas.py +++ b/schemas/BaseSchemas.py @@ -75,4 +75,5 @@ class UserBase(BaseModel): class ClinicDoctorBase(BaseModel): name: str role: ClinicDoctorType - status: ClinicDoctorStatus \ No newline at end of file + status: ClinicDoctorStatus + clinic_id: int \ No newline at end of file diff --git a/schemas/ResponseSchemas.py b/schemas/ResponseSchemas.py index 866c2b4..90097a7 100644 --- a/schemas/ResponseSchemas.py +++ b/schemas/ResponseSchemas.py @@ -136,7 +136,7 @@ class AppointmentDetailed(AppointmentSchema): patient: Patient -class ClinicDoctor(ClinicDoctorBase): +class ClinicDoctorResponse(ClinicDoctorBase): id: int create_time: datetime update_time: datetime diff --git a/schemas/UpdateSchemas.py b/schemas/UpdateSchemas.py index 6819023..5f36ee7 100644 --- a/schemas/UpdateSchemas.py +++ b/schemas/UpdateSchemas.py @@ -1,12 +1,32 @@ from .BaseSchemas import * - +from enums.enums import ClinicStatus, Integration # Update schemas (all fields optional for partial updates) class ClinicUpdate(BaseModel): name: Optional[str] = None address: Optional[str] = None phone: Optional[str] = None - email: Optional[EmailStr] = None + status: Optional[ClinicStatus] = None + integration: Optional[Integration] = None + pms_id: Optional[str] = None + practice_name: Optional[str] = None + logo: Optional[str] = None + country: Optional[str] = None + postal_code: Optional[str] = None + city: Optional[str] = None + state: Optional[str] = None + abn_doc: Optional[str] = None + abn_number: Optional[str] = None + contract_doc: Optional[str] = None + clinic_phone: Optional[str] = None + is_clinic_phone_enabled: Optional[bool] = True + other_info: Optional[str] = None + greeting_msg: Optional[str] = None + voice_model: Optional[str] = None + voice_model_provider: Optional[str] = None + voice_model_gender: Optional[str] = None + scenarios: Optional[str] = None + general_info: Optional[str] = None class DoctorUpdate(BaseModel): diff --git a/services/authService.py b/services/authService.py index e8b97f1..a64e129 100644 --- a/services/authService.py +++ b/services/authService.py @@ -20,7 +20,7 @@ class AuthService: # remove password from user dict user_dict = user.__dict__.copy() - user_dict.pop("password", None) + user_dict.pop("password", None) # create token token = create_jwt_token(user_dict) diff --git a/services/clinicDoctorsServices.py b/services/clinicDoctorsServices.py index be083dc..e8a4780 100644 --- a/services/clinicDoctorsServices.py +++ b/services/clinicDoctorsServices.py @@ -1,21 +1,49 @@ from schemas.CreateSchemas import ClinicDoctorCreate from schemas.UpdateSchemas import ClinicDoctorUpdate +from schemas.ResponseSchemas import ClinicDoctorResponse from database import get_db from models import ClinicDoctors +from sqlalchemy.orm import Session +from services.clinicServices import ClinicServices +from exceptions import ResourceNotFoundException class ClinicDoctorsServices: def __init__(self): - self.db = next(get_db()) + self.db: Session = next(get_db()) + self.clinic_services = ClinicServices() - def create_clinic_doctor(self, clinic_doctor: ClinicDoctorCreate): - clinic_doctor = ClinicDoctors(**clinic_doctor.dict()) + def create_clinic_doctor(self, clinic_doctor: ClinicDoctorCreate) -> ClinicDoctorResponse: + + # check if clinic exists + self.clinic_services.get_clinic_by_id(clinic_doctor.clinic_id) + + clinic_doctor = ClinicDoctors(**clinic_doctor.model_dump()) self.db.add(clinic_doctor) self.db.commit() self.db.refresh(clinic_doctor) - return clinic_doctor + return ClinicDoctorResponse(**clinic_doctor.__dict__.copy()) - def update_clinic_doctor(self, clinic_doctor_id: int, clinic_doctor: ClinicDoctorUpdate): - pass + def update_clinic_doctor(self, clinic_doctor_id: int, clinic_doctor_data: ClinicDoctorUpdate) -> ClinicDoctorResponse: + # check if clinic doctor exists + 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) + self.db.commit() + self.db.refresh(clinic_doctor) + return ClinicDoctorResponse(**clinic_doctor.__dict__.copy()) def delete_clinic_doctor(self, clinic_doctor_id: int): - pass \ No newline at end of file + clinic_doctor = self.db.query(ClinicDoctors).filter(ClinicDoctors.id == clinic_doctor_id).first() + self.db.delete(clinic_doctor) + self.db.commit() \ No newline at end of file diff --git a/services/clinicServices.py b/services/clinicServices.py new file mode 100644 index 0000000..5147503 --- /dev/null +++ b/services/clinicServices.py @@ -0,0 +1,52 @@ +from database import get_db +from sqlalchemy.orm import Session +from models import Clinics +from schemas.UpdateSchemas import ClinicUpdate +from schemas.ResponseSchemas import Clinic +from typing import List +from exceptions import ResourceNotFoundException + +class ClinicServices: + def __init__(self): + self.db: Session = next(get_db()) + + def get_clinics(self, limit:int, offset:int) -> List[Clinic]: + clinics = self.db.query(Clinics).limit(limit).offset(offset).all() + + clinic_response = [Clinic(**clinic.__dict__.copy()) for clinic in clinics] + + return clinic_response + + + def get_clinic_by_id(self, clinic_id: int) -> Clinic: + clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first() + + if clinic is None: + raise ResourceNotFoundException("Clinic not found") + + clinic_response = Clinic(**clinic.__dict__.copy()) + return clinic_response + + def update_clinic(self, clinic_id: int, clinic_data: ClinicUpdate): + clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first() + + if clinic is None: + raise ResourceNotFoundException("Clinic not found") + + update_data = clinic_data.model_dump(exclude_unset=True) + for key, value in update_data.items(): + setattr(clinic, key, value) + + self.db.add(clinic) + self.db.commit() + self.db.refresh(clinic) + return Clinic(**clinic.__dict__.copy()) + + def delete_clinic(self, clinic_id: int): + clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first() + + if clinic is None: + raise ResourceNotFoundException("Clinic not found") + + self.db.delete(clinic) + self.db.commit() \ No newline at end of file diff --git a/services/userServices.py b/services/userServices.py index ae64d10..5bc0890 100644 --- a/services/userServices.py +++ b/services/userServices.py @@ -6,7 +6,7 @@ from models.Users import Users from exceptions.validation_exception import ValidationException from schemas.ResponseSchemas import UserResponse from models import Clinics -from enums.enums import ClinicStatus +from enums.enums import ClinicStatus, UserType from schemas.UpdateSchemas import UserUpdate from exceptions.unauthorized_exception import UnauthorizedException from utils.password_utils import hash_password @@ -150,34 +150,36 @@ class UserServices: return user_response - def update_user(self, admin_id:int|None, user_id: int, user_data: UserUpdate): - + def update_user(self, admin_id:int|None, user_id: int, user_data: UserUpdate) -> UserResponse: + # Check admin authorization if admin_id is provided if admin_id: admin = self.db.query(Users).filter(Users.id == admin_id).first() - if not admin: logger.error("Admin not found") raise ResourceNotFoundException("Admin not found") + + # Only check admin type if admin_id was provided + if admin.userType != UserType.SUPER_ADMIN: + logger.error("User is not authorized to perform this action") + raise UnauthorizedException("User is not authorized to perform this action") - if admin.userType != UserType.ADMIN: - logger.error("User is not authorized to perform this action") - raise UnauthorizedException("User is not authorized to perform this action") - + # Find the user to update user = self.db.query(Users).filter(Users.id == user_id).first() - if not user: logger.error("User not found") raise ResourceNotFoundException("User not found") - user.username = user_data.username - user.clinicRole = user_data.clinicRole - user.userType = user_data.userType - user.profile_pic = user_data.profile_pic + # Update only the fields that were provided + update_data = user_data.model_dump(exclude_unset=True) + for key, value in update_data.items(): + setattr(user, key, value) self.db.add(user) self.db.commit() - - return user + self.db.refresh(user) + + # Return properly serialized response + return UserResponse.model_validate(user) def delete_user(self, user_id: int): user = self.db.query(Users).filter(Users.id == user_id).first()