diff --git a/apis/__init__.py b/apis/__init__.py index 1ed7000..fdc326e 100644 --- a/apis/__init__.py +++ b/apis/__init__.py @@ -11,7 +11,7 @@ from .endpoints import clinics, doctors, calender, appointments, patients, admin api_router = APIRouter() # api_router.include_router(twilio.router, prefix="/twilio") -api_router.include_router(clinics.router, prefix="/clinics", tags=["clinics"]) +api_router.include_router(clinics.router, prefix="/clinics", tags=["clinics"], dependencies=[Depends(auth_required)]) api_router.include_router(doctors.router, prefix="/doctors", tags=["doctors"]) diff --git a/apis/endpoints/admin.py b/apis/endpoints/admin.py index 8163248..b44cd2a 100644 --- a/apis/endpoints/admin.py +++ b/apis/endpoints/admin.py @@ -1,8 +1,13 @@ -from fastapi import APIRouter, status +from fastapi import APIRouter, Request, status + +from services.clinicServices import ClinicServices +from schemas.UpdateSchemas import ClinicStatusUpdate +from schemas.ApiResponse import ApiResponse router = APIRouter() -@router.get("/", status_code=status.HTTP_200_OK) -def get_admin(): - return {"message": "Admin"} +@router.put("/clinic/status") +def update_clinic_status(req:Request, data: ClinicStatusUpdate): + response = ClinicServices().update_clinic_status(req.state.user, data.clinic_id, data.status) + return ApiResponse(data=response, message="Clinic status updated successfully") diff --git a/apis/endpoints/clinics.py b/apis/endpoints/clinics.py index 39b9865..98df410 100644 --- a/apis/endpoints/clinics.py +++ b/apis/endpoints/clinics.py @@ -1,29 +1,33 @@ -from typing import List -from fastapi import APIRouter, HTTPException, status - -# database -from database import get_db +from typing import List, Literal, Union +from fastapi import APIRouter, status, Request # schemas from schemas.ResponseSchemas import Clinic 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 +from interface.common_response import CommonResponse +from utils.constants import DEFAULT_PAGE, DEFAULT_SKIP, DEFAULT_LIMIT router = APIRouter() -@router.get("/", response_model=List[Clinic]) +@router.get("/") async def get_clinics( - skip: int = DEFAULT_SKIP, limit: int = DEFAULT_LIMIT + req:Request, + page: int = DEFAULT_PAGE, + limit: int = DEFAULT_LIMIT, + filter_type: Union[Literal["UNREGISTERED"], Literal["REGISTERED"]] = "UNREGISTERED", + search:str = "" ): - clinics = ClinicServices().get_clinics(skip, limit) + if page < 1: + page = 1 + offset = (page - 1) * limit + clinics = ClinicServices().get_clinics(req.state.user, limit, offset, filter_type, search) return ApiResponse(data=clinics, message="Clinics retrieved successfully" ) @router.get("/latest-id") @@ -31,26 +35,26 @@ async def get_latest_clinic_id(): clinic_id = ClinicServices().get_latest_clinic_id() return ApiResponse(data=clinic_id, message="Latest clinic ID retrieved successfully") + +@router.get("/verified-files/{clinic_id}") +async def get_verified_files(clinic_id: int): + clinic = ClinicServices().get_clinic_by_id(clinic_id) + return ApiResponse(data=clinic, message="Clinic retrieved successfully") + @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") - -@router.put("/{clinic_id}", response_model=Clinic) +@router.put("/{clinic_id}") async def update_clinic( + req:Request, clinic_id: int, clinic: ClinicUpdate ): - clinic = ClinicServices().update_clinic(clinic_id, clinic) + clinic = ClinicServices().update_clinic(req.state.user, clinic_id, clinic) return ApiResponse(data=clinic, message="Clinic updated successfully") - @router.delete("/{clinic_id}", status_code=status.HTTP_204_NO_CONTENT) 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") - - db.delete(db_clinic) - db.commit() - return None + ClinicServices().delete_clinic(clinic_id) + return ApiResponse(message="Clinic deleted successfully") diff --git a/apis/endpoints/dashboard.py b/apis/endpoints/dashboard.py index 407ee47..2d0db81 100644 --- a/apis/endpoints/dashboard.py +++ b/apis/endpoints/dashboard.py @@ -2,6 +2,7 @@ from fastapi import APIRouter, Request from services.dashboardService import DashboardService from schemas.ApiResponse import ApiResponse from enums.enums import UserType +from schemas.CreateSchemas import SignupPricingMasterCreate router = APIRouter() @@ -9,3 +10,15 @@ router = APIRouter() async def get_clinic_doctor_status_count(req:Request): counts = DashboardService().get_dashboard_counts(isSuperAdmin=req.state.user["userType"] == UserType.SUPER_ADMIN) return ApiResponse(data=counts, message="Counts fetched successfully") + +@router.post("/signup-pricing-master") +async def update_signup_pricing_master(req:Request, signup_pricing_master:SignupPricingMasterCreate): + user = req.state.user + response = DashboardService().update_signup_pricing_master(user, signup_pricing_master) + return ApiResponse(data=response, message="Signup pricing master updated successfully") + + +@router.get("/signup-pricing-master") +async def get_signup_pricing_master(): + pricing = DashboardService().get_signup_pricing_master() + return ApiResponse(data=pricing, message="Signup pricing master fetched successfully") diff --git a/migrations/versions/497238c0338d_file_verification_table.py b/migrations/versions/497238c0338d_file_verification_table.py new file mode 100644 index 0000000..cb8878d --- /dev/null +++ b/migrations/versions/497238c0338d_file_verification_table.py @@ -0,0 +1,32 @@ +"""file-verification-table + +Revision ID: 497238c0338d +Revises: ad47f4af583e +Create Date: 2025-05-19 16:34:54.211429 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '497238c0338d' +down_revision: Union[str, None] = 'ad47f4af583e' +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! ### + op.add_column('clinic_file_verifications', sa.Column('rejection_reason', sa.String(length=255), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('clinic_file_verifications', 'rejection_reason') + # ### end Alembic commands ### diff --git a/migrations/versions/ec157808ef2a_file_verification_table.py b/migrations/versions/ec157808ef2a_file_verification_table.py new file mode 100644 index 0000000..52a3922 --- /dev/null +++ b/migrations/versions/ec157808ef2a_file_verification_table.py @@ -0,0 +1,32 @@ +"""file-verification-table + +Revision ID: ec157808ef2a +Revises: 497238c0338d +Create Date: 2025-05-19 17:16:52.137111 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'ec157808ef2a' +down_revision: Union[str, None] = '497238c0338d' +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! ### + op.add_column('clinic_file_verifications', sa.Column('logo_is_verified', sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('clinic_file_verifications', 'logo_is_verified') + # ### end Alembic commands ### diff --git a/models/ClinicFileVerifications.py b/models/ClinicFileVerifications.py new file mode 100644 index 0000000..14503af --- /dev/null +++ b/models/ClinicFileVerifications.py @@ -0,0 +1,18 @@ +from database import Base +from sqlalchemy import Column, Integer, Boolean, ForeignKey, String +from .CustomBase import CustomBase +from sqlalchemy.orm import relationship + +class ClinicFileVerifications(Base, CustomBase): + __tablename__ = "clinic_file_verifications" + + id = Column(Integer, primary_key=True, index=True) + clinic_id = Column(Integer, ForeignKey("clinics.id"), nullable=False) + logo_is_verified = Column(Boolean, default=False) + abn_doc_is_verified = Column(Boolean, default=False) + contract_doc_is_verified = Column(Boolean, default=False) + last_changed_by = Column(Integer, ForeignKey("users.id"), nullable=False) + rejection_reason = Column(String(255), nullable=True) + + clinic = relationship("Clinics", back_populates="clinic_file_verifications") + last_changed_by_user = relationship("Users", back_populates="clinic_file_verifications") \ No newline at end of file diff --git a/models/Clinics.py b/models/Clinics.py index bb64abc..ad92ce2 100644 --- a/models/Clinics.py +++ b/models/Clinics.py @@ -44,4 +44,5 @@ class Clinics(Base, CustomBase): # Relationships doctors = relationship("Doctors", back_populates="clinic") clinicDoctors = relationship("ClinicDoctors", back_populates="clinic") - creator = relationship("Users", back_populates="created_clinics") \ No newline at end of file + creator = relationship("Users", back_populates="created_clinics") + clinic_file_verifications = relationship("ClinicFileVerifications", back_populates="clinic") \ No newline at end of file diff --git a/models/SignupPricingMaster.py b/models/SignupPricingMaster.py new file mode 100644 index 0000000..4f86e33 --- /dev/null +++ b/models/SignupPricingMaster.py @@ -0,0 +1,11 @@ +from sqlalchemy import Column, Integer, Numeric +from database import Base +from .CustomBase import CustomBase + +class SignupPricingMaster(Base, CustomBase): + __tablename__ = "signup_pricing_master" + + id = Column(Integer, primary_key=True, index=True) + setup_fees = Column(Numeric(precision=10, scale=2)) + subscription_fees = Column(Numeric(precision=10, scale=2)) + per_call_charges = Column(Numeric(precision=10, scale=2)) \ No newline at end of file diff --git a/models/Users.py b/models/Users.py index d9c5d32..3cddf1b 100644 --- a/models/Users.py +++ b/models/Users.py @@ -25,3 +25,4 @@ class Users(Base, CustomBase): # Clinics created by this user created_clinics = relationship("Clinics", back_populates="creator") + clinic_file_verifications = relationship("ClinicFileVerifications", back_populates="last_changed_by_user") \ No newline at end of file diff --git a/models/__init__.py b/models/__init__.py index d8f6762..ec0f574 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -11,6 +11,8 @@ from .Notifications import Notifications from .CallTranscripts import CallTranscripts from .Fcm import Fcm from .BlockedEmail import BlockedEmail +from .SignupPricingMaster import SignupPricingMaster +from .ClinicFileVerifications import ClinicFileVerifications __all__ = [ "Users", @@ -26,4 +28,6 @@ __all__ = [ "CallTranscripts", "Fcm", "BlockedEmail", + "SignupPricingMaster", + "ClinicFileVerifications" ] diff --git a/schemas/BaseSchemas.py b/schemas/BaseSchemas.py index dc61634..e9de7ff 100644 --- a/schemas/BaseSchemas.py +++ b/schemas/BaseSchemas.py @@ -13,6 +13,17 @@ class SNSBase(BaseModel): Message: str +class ClinicFileVerificationBase(BaseModel): + abn_doc_is_verified: Optional[bool] = None + contract_doc_is_verified: Optional[bool] = None + logo_is_verified: Optional[bool] = None + last_changed_by: Optional[int] = None + + +class SignupPricingMasterBase(BaseModel): + setup_fees: Optional[float] = None + subscription_fees: Optional[float] = None + per_call_charges: Optional[float] = None class AuthBase(BaseModel): email: EmailStr diff --git a/schemas/CreateSchemas.py b/schemas/CreateSchemas.py index 7697e9f..1450151 100644 --- a/schemas/CreateSchemas.py +++ b/schemas/CreateSchemas.py @@ -25,6 +25,10 @@ class CalendarCreate(CalendarBase): pass +class SignupPricingMasterCreate(SignupPricingMasterBase): + pass + + class AppointmentCreateWithNames(BaseModel): doctor_name: str patient_name: str diff --git a/schemas/ResponseSchemas.py b/schemas/ResponseSchemas.py index 07dda9f..4a999cf 100644 --- a/schemas/ResponseSchemas.py +++ b/schemas/ResponseSchemas.py @@ -1,5 +1,7 @@ from datetime import datetime -from typing import List +from typing import Any, List, Optional + +from enums.enums import ClinicStatus from .BaseSchemas import * from pydantic import Field @@ -8,6 +10,7 @@ class Clinic(ClinicBase): id: int create_time: datetime update_time: datetime + status: ClinicStatus class Config: orm_mode = True @@ -22,6 +25,15 @@ class ClinicDoctorResponse(ClinicDoctorBase): orm_mode = True +class SignupPricingMasterResponse(SignupPricingMasterBase): + id: int + create_time: datetime + update_time: datetime + + class Config: + orm_mode = True + + class UserResponse(UserBase): id: int create_time: datetime diff --git a/schemas/UpdateSchemas.py b/schemas/UpdateSchemas.py index 5f36ee7..c2998f9 100644 --- a/schemas/UpdateSchemas.py +++ b/schemas/UpdateSchemas.py @@ -6,7 +6,6 @@ class ClinicUpdate(BaseModel): name: Optional[str] = None address: Optional[str] = None phone: Optional[str] = None - status: Optional[ClinicStatus] = None integration: Optional[Integration] = None pms_id: Optional[str] = None practice_name: Optional[str] = None @@ -29,6 +28,14 @@ class ClinicUpdate(BaseModel): general_info: Optional[str] = None +class ClinicStatusUpdate(BaseModel): + clinic_id: int + status: ClinicStatus + rejection_reason: Optional[str] = None + +class SignupPricingMasterUpdate(SignupPricingMasterBase): + pass + class DoctorUpdate(BaseModel): name: Optional[str] = None age: Optional[int] = None diff --git a/services/clinicServices.py b/services/clinicServices.py index 97ae0a9..e26e784 100644 --- a/services/clinicServices.py +++ b/services/clinicServices.py @@ -1,51 +1,145 @@ from database import get_db -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, joinedload from models import Clinics -from schemas.UpdateSchemas import ClinicUpdate +from schemas.UpdateSchemas import ClinicStatusUpdate, ClinicUpdate from schemas.ResponseSchemas import Clinic -from typing import List +from typing import List, Literal, Union from exceptions import ResourceNotFoundException -from enums.enums import ClinicStatus +from enums.enums import ClinicStatus, UserType +from exceptions.unauthorized_exception import UnauthorizedException +from interface.common_response import CommonResponse +from sqlalchemy import or_,func, case + +from services.s3Service import get_signed_url +from models import ClinicFileVerifications +from schemas.BaseSchemas import ClinicFileVerificationBase 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() + def get_clinics(self, user, limit:int, offset:int, filter_type: Union[Literal["UNREGISTERED"], Literal["REGISTERED"]] = "UNREGISTERED", search:str = ""): + + if user["userType"] != UserType.SUPER_ADMIN: + raise UnauthorizedException("You are not authorized to get clinics") + + clinics_query = self.db.query(Clinics) + + if filter_type == "UNREGISTERED": + clinics_query = clinics_query.filter(Clinics.status != ClinicStatus.ACTIVE) + elif filter_type == "REGISTERED": + clinics_query = clinics_query.filter(Clinics.status == ClinicStatus.ACTIVE) + + if search: + clinics_query = clinics_query.filter( + or_( + Clinics.name.contains(search), + Clinics.email.contains(search), + Clinics.phone.contains(search), + Clinics.address.contains(search) + + ) + ) + + clinics = clinics_query.limit(limit).offset(offset).all() + + # Get all counts in a single optimized query + from sqlalchemy import text + + count_query = text(""" + SELECT + COUNT(*) as total, + COUNT(CASE WHEN status = 'ACTIVE' THEN 1 END) as active, + COUNT(CASE WHEN status = 'REJECTED' THEN 1 END) as rejected + FROM clinics + """) + + result = self.db.execute(count_query).first() + + totalClinics = result.total or 0 + totalRegisteredClinics = result.active or 0 + totalRejectedClinics = result.rejected or 0 clinic_response = [Clinic(**clinic.__dict__.copy()) for clinic in clinics] - return clinic_response + for clinic in clinic_response: + clinic.logo = get_signed_url(clinic.logo) if clinic.logo else None + + clinic_response_with_total = { + "clinics": clinic_response, + "totalRegisteredClinics": totalRegisteredClinics, + "totalRejectedClinics": totalRejectedClinics, + } + + response = CommonResponse(data=clinic_response_with_total, total=totalClinics) + + return response def get_latest_clinic_id(self) -> int: clinic = self.db.query(Clinics).order_by(Clinics.id.desc()).first() return clinic.id if clinic else 0 - def get_clinic_by_id(self, clinic_id: int) -> Clinic: + def get_clinic_by_id(self, clinic_id: int): + try: + clinic = self.db.query(Clinics).options(joinedload(Clinics.creator)).filter(Clinics.id == clinic_id).first() + + if clinic is None: + raise ResourceNotFoundException("Clinic not found") + + clinic_response = Clinic(**clinic.__dict__.copy()) + + clinic_response.logo = get_signed_url(clinic_response.logo) if clinic_response.logo else None + clinic_response.abn_doc = get_signed_url(clinic_response.abn_doc) if clinic_response.abn_doc else None + clinic_response.contract_doc = get_signed_url(clinic_response.contract_doc) if clinic_response.contract_doc else None + + clinic_resp = { + "clinic": clinic_response, + "creator": { + "name": clinic.creator.username, + "email": clinic.creator.email, + "phone": clinic.creator.mobile, + "designation": clinic.creator.clinicRole + }, + "clinic_files": self.get_clinic_files(clinic_id) + } + + return clinic_resp + except Exception as e: + raise Exception(e) + + def get_clinic_files(self, clinic_id: int): + clinic_files = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first() + + if clinic_files is None: + raise ResourceNotFoundException("Clinic not found") + + response = ClinicFileVerificationBase(**clinic_files.__dict__.copy()) + + return response + + def update_clinic(self, user, 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") - 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") + if clinic.creator_id != user["id"]: + raise UnauthorizedException("You are not authorized to update this clinic") 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()) + + clinic_response = Clinic(**clinic.__dict__.copy()) + clinic_response.logo = get_signed_url(clinic_response.logo) if clinic_response.logo else None + + return clinic_response def delete_clinic(self, clinic_id: int): clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first() @@ -94,4 +188,29 @@ class ClinicServices: if key: counts[key] = count - return counts \ No newline at end of file + return counts + + + def update_clinic_status(self, user, clinic_id: int, status: ClinicStatus): + + if user["userType"] != UserType.SUPER_ADMIN: + raise UnauthorizedException("You are not authorized to update clinic status") + + clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first() + + if clinic is None: + raise ResourceNotFoundException("Clinic not found") + + clinic.status = status + self.db.add(clinic) + self.db.commit() + self.db.refresh(clinic) + + clinic_response = Clinic(**clinic.__dict__.copy()) + + # if rejected then email to clinic creator + if clinic.status == ClinicStatus.REJECTED: + pass + + return clinic_response + \ No newline at end of file diff --git a/services/dashboardService.py b/services/dashboardService.py index 592be30..4ddd391 100644 --- a/services/dashboardService.py +++ b/services/dashboardService.py @@ -1,17 +1,74 @@ from database import get_db from services.clinicDoctorsServices import ClinicDoctorsServices from services.clinicServices import ClinicServices +from schemas.BaseSchemas import SignupPricingMasterBase +from schemas.ResponseSchemas import SignupPricingMasterResponse +from models.SignupPricingMaster import SignupPricingMaster +from exceptions import UnauthorizedException +from enums.enums import UserType +from exceptions import ResourceNotFoundException class DashboardService: def __init__(self): self.db = next(get_db()) self.clinicDoctorsServices = ClinicDoctorsServices() self.clinicServices = ClinicServices() - - def get_dashboard_counts(self, isSuperAdmin:bool): + + def get_dashboard_counts(self, isSuperAdmin: bool): if isSuperAdmin: clinicCounts = self.clinicServices.get_clinic_count() return clinicCounts else: clinicDoctorsCount = self.clinicDoctorsServices.get_doctor_status_count() - return clinicDoctorsCount \ No newline at end of file + return clinicDoctorsCount + + def update_signup_pricing_master( + self, user, pricing_data: SignupPricingMasterBase + ): + if user["userType"] != UserType.SUPER_ADMIN: + raise UnauthorizedException( + "You are not authorized to update signup pricing master" + ) + + existing_pricing = self.db.query(SignupPricingMaster).first() + + if existing_pricing is None: + # Create new record + new_pricing = SignupPricingMaster( + **pricing_data.model_dump() + ) + self.db.add(new_pricing) + self.db.commit() + self.db.refresh(new_pricing) + + response = SignupPricingMasterResponse( + **new_pricing.__dict__.copy() + ) + + return response + else: + # Update existing record with values from the request + existing_pricing.setup_fees = pricing_data.setup_fees + existing_pricing.subscription_fees = pricing_data.subscription_fees + existing_pricing.per_call_charges = pricing_data.per_call_charges + + self.db.commit() + self.db.refresh(existing_pricing) + + response = SignupPricingMasterResponse( + **existing_pricing.__dict__.copy() + ) + + return response + + + def get_signup_pricing_master(self): + signup_pricing_master = self.db.query(SignupPricingMaster).first() + if signup_pricing_master is None: + raise ResourceNotFoundException("Signup pricing master not found") + + response = SignupPricingMasterResponse( + **signup_pricing_master.__dict__.copy() + ) + + return response diff --git a/services/userServices.py b/services/userServices.py index de5621f..a3d1bb3 100644 --- a/services/userServices.py +++ b/services/userServices.py @@ -11,10 +11,12 @@ from schemas.UpdateSchemas import UserUpdate from exceptions.unauthorized_exception import UnauthorizedException from interface.common_response import CommonResponse from exceptions.business_exception import BusinessValidationException +from models import ClinicFileVerifications from utils.password_utils import hash_password from schemas.CreateSchemas import UserCreate from exceptions.resource_not_found_exception import ResourceNotFoundException from exceptions.db_exceptions import DBExceptionHandler +from sqlalchemy.orm import joinedload class UserServices: @@ -98,7 +100,19 @@ class UserServices: # Add clinic to database self.db.add(new_clinic) - + self.db.flush() + + # Create clinic files + clinic_files = ClinicFileVerifications( + clinic_id=new_clinic.id, + abn_doc_is_verified=False, + contract_doc_is_verified=False, + last_changed_by=new_user.id + ) + + # Add clinic files to database + self.db.add(clinic_files) + # Now commit both user and clinic in a single transaction self.db.commit() @@ -114,7 +128,6 @@ class UserServices: def get_user(self, user_id) -> UserResponse: try: # Query the user by ID and explicitly load the created clinics relationship - from sqlalchemy.orm import joinedload user = self.db.query(Users).options(joinedload(Users.created_clinics)).filter(Users.id == user_id).first() if not user: