feat: clinic setup update

fix: migration issue
This commit is contained in:
deepvasoya 2025-05-13 17:01:48 +05:30
parent dabc7dd308
commit 13023e5913
12 changed files with 280 additions and 74 deletions

View File

@ -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")

View File

@ -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")

View File

@ -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 ###

View File

@ -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")

View File

@ -10,4 +10,3 @@ class MasterAppointmentTypes(Base, CustomBase):
type = Column(String)
appointmentRelations = relationship("AppointmentRelations", back_populates="masterAppointmentTypes")
clinicDoctors = relationship("ClinicDoctors", back_populates="masterAppointmentTypes")

View File

@ -76,3 +76,4 @@ class ClinicDoctorBase(BaseModel):
name: str
role: ClinicDoctorType
status: ClinicDoctorStatus
clinic_id: int

View File

@ -136,7 +136,7 @@ class AppointmentDetailed(AppointmentSchema):
patient: Patient
class ClinicDoctor(ClinicDoctorBase):
class ClinicDoctorResponse(ClinicDoctorBase):
id: int
create_time: datetime
update_time: datetime

View File

@ -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):

View File

@ -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
clinic_doctor = self.db.query(ClinicDoctors).filter(ClinicDoctors.id == clinic_doctor_id).first()
self.db.delete(clinic_doctor)
self.db.commit()

View File

@ -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()

View File

@ -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")
if admin.userType != UserType.ADMIN:
# 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")
# 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()
self.db.refresh(user)
return 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()