diff --git a/api/app/application/appointments_repository.py b/api/app/application/appointments_repository.py index 139423f..b9d78f0 100644 --- a/api/app/application/appointments_repository.py +++ b/api/app/application/appointments_repository.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence +from typing import Sequence from sqlalchemy import select, desc from sqlalchemy.ext.asyncio import AsyncSession @@ -16,7 +16,7 @@ class AppointmentsRepository: select(Appointment) .options(joinedload(Appointment.type)) .options(joinedload(Appointment.patient)) - .order_by(desc(Appointment.)) + .order_by(desc(Appointment.appointment_datetime)) ) result = await self.db.execute(stmt) return result.scalars().all() @@ -26,5 +26,45 @@ class AppointmentsRepository: select(Appointment) .options(joinedload(Appointment.type)) .options(joinedload(Appointment.patient)) - .filter() + .filter(Appointment.doctor_id == doctor_id) + .order_by(desc(Appointment.appointment_datetime)) ) + result = await self.db.execute(stmt) + return result.scalars().all() + + async def get_by_patient_id(self, patient_id: int): + stmt = ( + select(Appointment) + .options(joinedload(Appointment.type)) + .options(joinedload(Appointment.patient)) + .filter(Appointment.patient_id == patient_id) + .order_by(desc(Appointment.appointment_datetime)) + ) + result = await self.db.execute(stmt) + return result.scalars().all() + + async def get_by_id(self, appointment_id: int): + stmt = ( + select(Appointment) + .options(joinedload(Appointment.type)) + .options(joinedload(Appointment.patient)) + .filter(Appointment.id == appointment_id) + ) + result = await self.db.execute(stmt) + return result.scalars().first() + + async def create(self, appointment: Appointment) -> Appointment: + self.db.add(appointment) + await self.db.commit() + await self.db.refresh(appointment) + return appointment + + async def update(self, appointment: Appointment) -> Appointment: + await self.db.merge(appointment) + await self.db.commit() + return appointment + + async def delete(self, appointment) -> Appointment: + await self.db.delete(appointment) + await self.db.commit() + return appointment diff --git a/api/app/controllers/appointment_types_router.py b/api/app/controllers/appointment_types_router.py new file mode 100644 index 0000000..8245488 --- /dev/null +++ b/api/app/controllers/appointment_types_router.py @@ -0,0 +1,23 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.database.session import get_db +from app.domain.entities.appointment_type import AppointmentTypeEntity +from app.infrastructure.appointment_types_service import AppointmentTypesService +from app.infrastructure.dependencies import get_current_user + +router = APIRouter() + + +@router.get( + "/appointment_types/", + response_model=list[AppointmentTypeEntity], + summary="Get all appointment types", + description="Returns a list of all appointment types", +) +async def get_all_appointment_types( + db: AsyncSession = Depends(get_db), + user=Depends(get_current_user), +): + appointment_types_service = AppointmentTypesService(db) + return await appointment_types_service.get_all_appointment_types() diff --git a/api/app/controllers/appointments_router.py b/api/app/controllers/appointments_router.py new file mode 100644 index 0000000..06d89ac --- /dev/null +++ b/api/app/controllers/appointments_router.py @@ -0,0 +1,84 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.database.session import get_db +from app.domain.entities.appointment import AppointmentEntity +from app.infrastructure.appointments_service import AppointmentsService +from app.infrastructure.dependencies import get_current_user + +router = APIRouter() + + +@router.get( + "/appointments/", + response_model=list[AppointmentEntity], + summary="Get all appointments", + description="Returns a list of all appointments", +) +async def get_all_appointments( + db: AsyncSession = Depends(get_db), + user=Depends(get_current_user), +): + appointments_service = AppointmentsService(db) + return await appointments_service.get_all_appointments() + + +@router.get( + "/appointments/doctor/{doctor_id}/", + response_model=AppointmentEntity, + summary="Get all appointments for doctor", + description="Returns a list of appointments for doctor", +) +async def get_all_appointments_by_doctor_id( + doctor_id: int, + db: AsyncSession = Depends(get_db), + user=Depends(get_current_user), +): + appointments_service = AppointmentsService(db) + return await appointments_service.get_appointments_by_doctor_id(doctor_id) + + +@router.get( + "/appointments/patient/{patient_id}/", + response_model=AppointmentEntity, + summary="Get all appointments for patient", + description="Returns a list of appointments for patient", +) +async def get_all_appointments_by_patient_id( + patient_id: int, + db: AsyncSession = Depends(get_db), + user=Depends(get_current_user), +): + appointments_service = AppointmentsService(db) + return await appointments_service.get_appointments_by_patient_id(patient_id) + + +@router.post( + "/appointments/", + response_model=AppointmentEntity, + summary="Create appointment", + description="Creates a new appointment", +) +async def create_appointment( + appointment: AppointmentEntity, + db: AsyncSession = Depends(get_db), + user=Depends(get_current_user), +): + appointment_service = AppointmentsService(db) + return await appointment_service.create_appointment(appointment) + + +@router.put( + "/appointments/{appointment_id}/", + response_model=AppointmentEntity, + summary="Update appointment", + description="Updates an existing appointment", +) +async def update_appointment( + appointment_id: int, + appointment: AppointmentEntity, + db: AsyncSession = Depends(get_db), + user=Depends(get_current_user), +): + appointment_service = AppointmentsService(db) + return await appointment_service.update_appointment(appointment_id, appointment) diff --git a/api/app/database/migrations/versions/f28580d2d60f_0002_изменил_поле_даты_приема_на_.py b/api/app/database/migrations/versions/f28580d2d60f_0002_изменил_поле_даты_приема_на_.py new file mode 100644 index 0000000..a549ff1 --- /dev/null +++ b/api/app/database/migrations/versions/f28580d2d60f_0002_изменил_поле_даты_приема_на_.py @@ -0,0 +1,32 @@ +"""0002_изменил_поле_даты_приема_на_датувремя + +Revision ID: f28580d2d60f +Revises: b10579618fb5 +Create Date: 2025-03-11 14:44:43.322192 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'f28580d2d60f' +down_revision: Union[str, None] = 'b10579618fb5' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('appointments', sa.Column('appointment_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False)) + op.drop_column('appointments', 'date') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('appointments', sa.Column('date', sa.DATE(), server_default=sa.text('now()'), autoincrement=False, nullable=False)) + op.drop_column('appointments', 'appointment_datetime') + # ### end Alembic commands ### diff --git a/api/app/domain/entities/appointment.py b/api/app/domain/entities/appointment.py new file mode 100644 index 0000000..e27f174 --- /dev/null +++ b/api/app/domain/entities/appointment.py @@ -0,0 +1,23 @@ +import datetime +from typing import Optional + +from pydantic import BaseModel + +from app.domain.entities.appointment_type import AppointmentTypeEntity +from app.domain.entities.patient import PatientEntity +from app.domain.entities.user import UserEntity + + +class AppointmentEntity(BaseModel): + id: Optional[int] = None + results: Optional[str] = None + days_until_the_next_appointment: Optional[int] = None + appointment_datetime: datetime.datetime + + patient_id: int + doctor_id: int + type_id: int + + patient: Optional[PatientEntity] = None + doctor: Optional[UserEntity] = None + type: Optional[AppointmentTypeEntity] = None diff --git a/api/app/domain/entities/appointment_type.py b/api/app/domain/entities/appointment_type.py new file mode 100644 index 0000000..21d0aa2 --- /dev/null +++ b/api/app/domain/entities/appointment_type.py @@ -0,0 +1,8 @@ +from typing import Optional + +from pydantic import BaseModel + + +class AppointmentTypeEntity(BaseModel): + id: Optional[int] = None + title: str diff --git a/api/app/domain/models/appointment_types.py b/api/app/domain/models/appointment_types.py index e385ee9..54b6b2c 100644 --- a/api/app/domain/models/appointment_types.py +++ b/api/app/domain/models/appointment_types.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, VARCHAR +from sqlalchemy import Column, VARCHAR from sqlalchemy.orm import relationship from app.domain.models.base import BaseModel diff --git a/api/app/domain/models/appointments.py b/api/app/domain/models/appointments.py index af7f4d2..e57bfac 100644 --- a/api/app/domain/models/appointments.py +++ b/api/app/domain/models/appointments.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, String, ForeignKey, Date +from sqlalchemy import Column, Integer, String, ForeignKey, DateTime from sqlalchemy.orm import relationship from sqlalchemy.sql import func @@ -10,7 +10,7 @@ class Appointment(BaseModel): results = Column(String) days_until_the_next_appointment = Column(Integer) - date = Column(Date, nullable=False, server_default=func.now()) + appointment_datetime = Column(DateTime, nullable=False, server_default=func.now()) patient_id = Column(Integer, ForeignKey('patients.id'), nullable=False) doctor_id = Column(Integer, ForeignKey('users.id'), nullable=False) diff --git a/api/app/infrastructure/appointment_types_service.py b/api/app/infrastructure/appointment_types_service.py new file mode 100644 index 0000000..b72edbc --- /dev/null +++ b/api/app/infrastructure/appointment_types_service.py @@ -0,0 +1,36 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.application.appointment_types_repository import AppointmentTypesRepository +from app.domain.entities.appointment_type import AppointmentTypeEntity +from app.domain.models import AppointmentType + + +class AppointmentTypesService: + def __init__(self, db: AsyncSession): + self.appointment_types_repository = AppointmentTypesRepository(db) + + async def get_all_appointment_types(self) -> list[AppointmentTypeEntity]: + appointment_types = await self.appointment_types_repository.get_all() + + return [ + self.model_to_entity(appointment_type) + for appointment_type in appointment_types + ] + + @staticmethod + def entity_to_model(appointment_type: AppointmentTypeEntity) -> AppointmentType: + appointment_type_model = AppointmentType( + title=appointment_type.title, + ) + + if appointment_type.id is not None: + appointment_type_model.id = appointment_type.id + + return appointment_type_model + + @staticmethod + def model_to_entity(appointment_type_model: AppointmentType) -> AppointmentTypeEntity: + return AppointmentTypeEntity( + id=appointment_type_model.id, + title=appointment_type_model.title, + ) diff --git a/api/app/infrastructure/appointments_service.py b/api/app/infrastructure/appointments_service.py new file mode 100644 index 0000000..079d076 --- /dev/null +++ b/api/app/infrastructure/appointments_service.py @@ -0,0 +1,176 @@ +from typing import Optional + +from fastapi import HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from starlette import status + +from app.application.appointment_types_repository import AppointmentTypesRepository +from app.application.appointments_repository import AppointmentsRepository +from app.application.patients_repository import PatientsRepository +from app.application.users_repository import UsersRepository +from app.domain.entities.appointment import AppointmentEntity +from app.domain.models import Appointment +from app.infrastructure.appointment_types_service import AppointmentTypesService +from app.infrastructure.patients_service import PatientsService +from app.infrastructure.users_service import UsersService + + +class AppointmentsService: + def __init__(self, db: AsyncSession): + self.appointments_repository = AppointmentsRepository(db) + self.appointment_types_repository = AppointmentTypesRepository(db) + self.users_repository = UsersRepository(db) + self.patients_repository = PatientsRepository(db) + + async def get_all_appointments(self) -> list[AppointmentEntity]: + appointments = await self.appointments_repository.get_all() + + return [ + self.model_to_entity(appointment) + for appointment in appointments + ] + + async def get_appointments_by_doctor_id(self, doctor_id: int) -> Optional[list[AppointmentEntity]]: + doctor = await self.users_repository.get_by_id(doctor_id) + + if not doctor: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The doctor with this ID was not found', + ) + + appointments = await self.appointments_repository.get_by_doctor_id(doctor_id) + + return [ + self.model_to_entity(appointment) + for appointment in appointments + ] + + async def get_appointments_by_patient_id(self, patient_id: int) -> Optional[list[AppointmentEntity]]: + patient = await self.patients_repository.get_by_id(patient_id) + + if not patient: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The patient with this ID was not found', + ) + + appointments = await self.appointments_repository.get_by_patient_id(patient_id) + + return [ + self.model_to_entity(appointment) + for appointment in appointments + ] + + async def create_appointment(self, appointment: AppointmentEntity) -> Optional[AppointmentEntity]: + patient = await self.patients_repository.get_by_id(appointment.patient_id) + + if not patient: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The patient with this ID was not found', + ) + + doctor = await self.users_repository.get_by_id(appointment.doctor_id) + + if not doctor: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The doctor/user with this ID was not found', + ) + + appointment_type = await self.appointment_types_repository.get_by_id(appointment.type_id) + + if not appointment_type: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The appointment type with this ID was not found', + ) + + appointment_model = self.entity_to_model(appointment) + + await self.appointments_repository.create(appointment_model) + + return self.model_to_entity(appointment_model) + + async def update_appointment(self, appointment_id: int, appointment: AppointmentEntity) -> Optional[ + AppointmentEntity + ]: + appointment_model = await self.appointments_repository.get_by_id(appointment_id) + + if not appointment_model: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Appointment not found") + + patient = await self.patients_repository.get_by_id(appointment.patient_id) + + if not patient: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The patient with this ID was not found', + ) + + doctor = await self.users_repository.get_by_id(appointment.doctor_id) + + if not doctor: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The doctor/user with this ID was not found', + ) + + appointment_type = await self.appointment_types_repository.get_by_id(appointment.type_id) + + if not appointment_type: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The appointment type with this ID was not found', + ) + + appointment_model.results = appointment.results + appointment_model.days_until_the_next_appointment = appointment.days_until_the_next_appointment + appointment_model.appointment_datetime = appointment.appointment_datetime + appointment_model.patient_id = appointment.patient_id + appointment_model.doctor_id = appointment.doctor_id + appointment_model.type_id = appointment.type_id + + await self.appointments_repository.update(appointment_model) + + return self.model_to_entity(appointment_model) + + @staticmethod + def entity_to_model(appointment: AppointmentEntity) -> Appointment: + appointment_model = Appointment( + results=appointment.results, + days_until_the_next_appointment=appointment.days_until_the_next_appointment, + appointment_datetime=appointment.appointment_datetime, + patient_id=appointment.patient_id, + doctor_id=appointment.doctor_id, + type_id=appointment.type_id, + ) + + if appointment.id is not None: + appointment_model.id = appointment.id + + return appointment_model + + @staticmethod + def model_to_entity(appointment: Appointment) -> AppointmentEntity: + appointment_entity = AppointmentEntity( + id=appointment.id, + results=appointment.results, + days_until_the_next_appointment=appointment.days_until_the_next_appointment, + appointment_datetime=appointment.appointment_datetime, + patient_id=appointment.patient_id, + doctor_id=appointment.doctor_id, + type_id=appointment.type_id, + ) + + if appointment.patient is not None: + appointment_entity.patient = PatientsService.model_to_entity(appointment.patient) + + if appointment.doctor is not None: + appointment_entity.doctor = UsersService.model_to_entity(appointment.doctor) + + if appointment.type is not None: + appointment_entity.type = AppointmentTypesService.model_to_entity(appointment.type) + + return appointment_entity diff --git a/api/app/infrastructure/lens_issues_service.py b/api/app/infrastructure/lens_issues_service.py index ac1b9be..f690298 100644 --- a/api/app/infrastructure/lens_issues_service.py +++ b/api/app/infrastructure/lens_issues_service.py @@ -1,3 +1,5 @@ +from typing import Optional + from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession from starlette import status @@ -28,7 +30,7 @@ class LensIssuesService: for lens_issue in lens_issues ] - async def create_lens_issue(self, lens_issue: LensIssueEntity, user_id: int) -> LensIssueEntity: + async def create_lens_issue(self, lens_issue: LensIssueEntity, user_id: int) -> Optional[LensIssueEntity]: patient = await self.patient_repository.get_by_id(lens_issue.patient_id) if not patient: diff --git a/api/app/infrastructure/lens_types_service.py b/api/app/infrastructure/lens_types_service.py index 43e3097..6b98ac8 100644 --- a/api/app/infrastructure/lens_types_service.py +++ b/api/app/infrastructure/lens_types_service.py @@ -10,6 +10,7 @@ class LensTypesService: async def get_all_lens_types(self) -> list[LensTypeEntity]: lens_types = await self.lens_types_repository.get_all() + return [ LensTypeEntity( id=lens_type.id, diff --git a/api/app/infrastructure/lenses_service.py b/api/app/infrastructure/lenses_service.py index 3c18881..9a16369 100644 --- a/api/app/infrastructure/lenses_service.py +++ b/api/app/infrastructure/lenses_service.py @@ -32,7 +32,7 @@ class LensesService: for lens in lenses ] - async def create_lens(self, lens: LensEntity) -> LensEntity: + async def create_lens(self, lens: LensEntity) -> Optional[LensEntity]: lens_type = await self.lens_types_repository.get_by_id(lens.type_id) if not lens_type: @@ -47,7 +47,7 @@ class LensesService: return self.model_to_entity(lens_model) - async def update_lens(self, lens_id: int, lens: LensEntity) -> LensEntity: + async def update_lens(self, lens_id: int, lens: LensEntity) -> Optional[LensEntity]: lens_model = await self.lenses_repository.get_by_id(lens_id) if not lens_model: diff --git a/api/app/infrastructure/set_content_service.py b/api/app/infrastructure/set_content_service.py index 4dd1c18..c2d6053 100644 --- a/api/app/infrastructure/set_content_service.py +++ b/api/app/infrastructure/set_content_service.py @@ -42,7 +42,8 @@ class SetContentService: for content in set_content ] - async def create_list_sets(self, set_id: int, sets_content: list[SetContentEntity]) -> list[SetContentEntity]: + async def create_list_sets(self, set_id: int, sets_content: list[SetContentEntity]) -> Optional[ + list[SetContentEntity]]: _set = await self.set_repository.get_by_id(set_id) if not _set: @@ -73,7 +74,7 @@ class SetContentService: for content in sets_content_models ] - async def create_set_content(self, set_id: int, set_content: SetContentEntity) -> SetContentEntity: + async def create_set_content(self, set_id: int, set_content: SetContentEntity) -> Optional[SetContentEntity]: _set = await self.set_repository.get_by_id(set_id) if not _set: @@ -104,8 +105,8 @@ class SetContentService: return self.model_to_entity(set_content_model) - async def update_set_content_by_set_id(self, set_id: int, sets_content: list[SetContentEntity]) -> list[ - SetContentEntity + async def update_set_content_by_set_id(self, set_id: int, sets_content: list[SetContentEntity]) -> Optional[ + list[SetContentEntity] ]: _set = await self.set_repository.get_by_id(set_id) diff --git a/api/app/infrastructure/sets_service.py b/api/app/infrastructure/sets_service.py index c49a523..a09373c 100644 --- a/api/app/infrastructure/sets_service.py +++ b/api/app/infrastructure/sets_service.py @@ -10,6 +10,7 @@ from app.application.sets_repository import SetsRepository from app.domain.entities.lens import LensEntity from app.domain.entities.set import SetEntity from app.domain.models import Set, Lens +from app.infrastructure.lenses_service import LensesService class SetsService: @@ -20,25 +21,20 @@ class SetsService: async def get_all_sets(self) -> list[SetEntity]: sets = await self.sets_repository.get_all() + return [ - SetEntity( - id=_set.id, - title=_set.title, - ) + self.model_to_entity(_set) for _set in sets ] async def create_set(self, _set: SetEntity) -> SetEntity: - set_model = Set( - title=_set.title, - ) - await self.sets_repository.create(set_model) - return SetEntity( - id=set_model.id, - title=set_model.title, - ) + set_model = self.entity_to_model(_set) - async def update_set(self, set_id: int, _set: SetEntity) -> SetEntity: + await self.sets_repository.create(set_model) + + return self.model_to_entity(set_model) + + async def update_set(self, set_id: int, _set: SetEntity) -> Optional[SetEntity]: set_model = await self.sets_repository.get_by_id(set_id) if not set_model: @@ -48,10 +44,7 @@ class SetsService: await self.sets_repository.update(set_model) - return SetEntity( - id=set_model.id, - title=set_model.title, - ) + return self.model_to_entity(set_model) async def delete_set(self, set_id: int) -> Optional[SetEntity]: _set = await self.sets_repository.get_by_id(set_id) @@ -66,10 +59,7 @@ class SetsService: result = await self.sets_repository.delete(_set) - return SetEntity( - id=result.id, - title=result.title, - ) + return self.model_to_entity(result) async def append_set_content_to_lenses(self, set_id: int) -> Optional[list[LensEntity]]: _set = await self.sets_repository.get_by_id(set_id) @@ -81,31 +71,12 @@ class SetsService: lenses = [] for content in set_content: - lens = Lens( - tor=content.tor, - trial=content.trial, - esa=content.esa, - fvc=content.fvc, - preset_refraction=content.preset_refraction, - diameter=content.diameter, - periphery_toricity=content.periphery_toricity, - side=content.side, - type_id=content.type_id, - ) + lens = LensesService.entity_to_model(content) + await self.lenses_repository.create(lens) + lenses.append( - LensEntity( - id=lens.id, - tor=lens.tor, - trial=lens.trial, - esa=lens.esa, - fvc=lens.fvc, - preset_refraction=lens.preset_refraction, - diameter=lens.diameter, - periphery_toricity=lens.periphery_toricity, - side=lens.side.value, - type_id=lens.type_id, - ) + LensesService.model_to_entity(lens) ) return lenses @@ -122,3 +93,8 @@ class SetsService: set_model = Set( title=_set.title, ) + + if _set.id is not None: + set_model.id = _set.id + + return set_model diff --git a/api/app/main.py b/api/app/main.py index 087d77d..3a5e482 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -1,6 +1,8 @@ from fastapi import FastAPI from starlette.middleware.cors import CORSMiddleware +from app.controllers.appointment_types_router import router as appointment_types_router +from app.controllers.appointments_router import router as appointment_router from app.controllers.auth_router import router as auth_router from app.controllers.lens_issues_router import router as lens_issues_router from app.controllers.lens_types_router import router as lens_types_router @@ -31,6 +33,8 @@ def start_app(): api_app.include_router(sets_router, prefix=settings.APP_PREFIX, tags=['sets']) api_app.include_router(set_content_router, prefix=settings.APP_PREFIX, tags=['set_content']) api_app.include_router(lens_issues_router, prefix=settings.APP_PREFIX, tags=['lens_issue']) + api_app.include_router(appointment_types_router, prefix=settings.APP_PREFIX, tags=['appointment_types']) + api_app.include_router(appointment_router, prefix=settings.APP_PREFIX, tags=['appointments']) return api_app @@ -40,4 +44,4 @@ app = start_app() @app.get("/", tags=['root']) async def root(): - return {"message": "OK"} + return {"message": "Hello :з"}