Compare commits
2 Commits
a5fccd0710
...
79e352037b
| Author | SHA1 | Date | |
|---|---|---|---|
| 79e352037b | |||
| 44c7f031cc |
@ -11,7 +11,8 @@ class PatientsRepository:
|
|||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
async def get_all(self, skip: int = 0, limit: int = 100, search: str = None,
|
async def get_all(self, skip: int = 0, limit: int = 100, search: str = None,
|
||||||
sort_order: Literal["asc", "desc"] = "asc", all_params: bool = False) -> Tuple[Sequence[Patient], int]:
|
sort_order: Literal["asc", "desc"] = "asc", all_params: bool = False) -> Tuple[
|
||||||
|
Sequence[Patient], int]:
|
||||||
stmt = select(Patient)
|
stmt = select(Patient)
|
||||||
|
|
||||||
if search:
|
if search:
|
||||||
@ -58,6 +59,11 @@ class PatientsRepository:
|
|||||||
|
|
||||||
return patients, total_count
|
return patients, total_count
|
||||||
|
|
||||||
|
async def get_width_email(self) -> Sequence[Patient]:
|
||||||
|
stmt = select(Patient).filter(Patient.email != None)
|
||||||
|
result = await self.db.execute(stmt)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
async def get_by_id(self, patient_id: int) -> Optional[Patient]:
|
async def get_by_id(self, patient_id: int) -> Optional[Patient]:
|
||||||
stmt = select(Patient).filter_by(id=patient_id)
|
stmt = select(Patient).filter_by(id=patient_id)
|
||||||
result = await self.db.execute(stmt)
|
result = await self.db.execute(stmt)
|
||||||
|
|||||||
@ -47,6 +47,20 @@ async def create_patient(
|
|||||||
return await patients_service.create_patient(patient)
|
return await patients_service.create_patient(patient)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
'/email/',
|
||||||
|
response_model=list[PatientEntity],
|
||||||
|
summary="Get all patients with email",
|
||||||
|
description="Returns all patients with email",
|
||||||
|
)
|
||||||
|
async def get_all_patients_with_email(
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
patients_service = PatientsService(db)
|
||||||
|
return await patients_service.get_patients_with_email()
|
||||||
|
|
||||||
|
|
||||||
@router.put(
|
@router.put(
|
||||||
"/{patient_id}/",
|
"/{patient_id}/",
|
||||||
response_model=PatientEntity,
|
response_model=PatientEntity,
|
||||||
|
|||||||
@ -0,0 +1,148 @@
|
|||||||
|
"""0009 расставил каскадные удаления
|
||||||
|
|
||||||
|
Revision ID: 4e6d875a676b
|
||||||
|
Revises: b013393cef10
|
||||||
|
Create Date: 2025-07-03 14:59:33.778959
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '4e6d875a676b'
|
||||||
|
down_revision: Union[str, None] = 'b013393cef10'
|
||||||
|
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.drop_constraint('appointment_files_appointment_id_fkey', 'appointment_files', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'appointment_files', 'appointments', ['appointment_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.alter_column('appointments', 'doctor_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=True)
|
||||||
|
op.drop_constraint('appointments_type_id_fkey', 'appointments', type_='foreignkey')
|
||||||
|
op.drop_constraint('appointments_doctor_id_fkey', 'appointments', type_='foreignkey')
|
||||||
|
op.drop_constraint('appointments_patient_id_fkey', 'appointments', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'appointments', 'patients', ['patient_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.create_foreign_key(None, 'appointments', 'users', ['doctor_id'], ['id'], source_schema='public', referent_schema='public', ondelete='SET NULL')
|
||||||
|
op.create_foreign_key(None, 'appointments', 'appointment_types', ['type_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.alter_column('backups', 'user_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=True)
|
||||||
|
op.drop_constraint('backups_user_id_fkey', 'backups', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'backups', 'users', ['user_id'], ['id'], source_schema='public', referent_schema='public', ondelete='SET NULL')
|
||||||
|
op.drop_constraint('lens_type_id_fkey', 'lens', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'lens', 'lens_types', ['type_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.alter_column('lens_issues', 'doctor_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=True)
|
||||||
|
op.drop_constraint('lens_issues_patient_id_fkey', 'lens_issues', type_='foreignkey')
|
||||||
|
op.drop_constraint('lens_issues_lens_id_fkey', 'lens_issues', type_='foreignkey')
|
||||||
|
op.drop_constraint('lens_issues_doctor_id_fkey', 'lens_issues', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'lens_issues', 'users', ['doctor_id'], ['id'], source_schema='public', referent_schema='public', ondelete='SET NULL')
|
||||||
|
op.create_foreign_key(None, 'lens_issues', 'patients', ['patient_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.create_foreign_key(None, 'lens_issues', 'lens', ['lens_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.alter_column('mailing', 'user_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=True)
|
||||||
|
op.drop_constraint('mailing_user_id_fkey', 'mailing', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'mailing', 'users', ['user_id'], ['id'], source_schema='public', referent_schema='public', ondelete='SET NULL')
|
||||||
|
op.drop_constraint('mailing_options_mailing_id_fkey', 'mailing_options', type_='foreignkey')
|
||||||
|
op.drop_constraint('mailing_options_option_id_fkey', 'mailing_options', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'mailing_options', 'mailing_delivery_methods', ['option_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.create_foreign_key(None, 'mailing_options', 'mailing', ['mailing_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.drop_constraint('recipients_patient_id_fkey', 'recipients', type_='foreignkey')
|
||||||
|
op.drop_constraint('recipients_mailing_id_fkey', 'recipients', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'recipients', 'patients', ['patient_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.create_foreign_key(None, 'recipients', 'mailing', ['mailing_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.alter_column('scheduled_appointments', 'doctor_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=True)
|
||||||
|
op.drop_constraint('scheduled_appointments_doctor_id_fkey', 'scheduled_appointments', type_='foreignkey')
|
||||||
|
op.drop_constraint('scheduled_appointments_patient_id_fkey', 'scheduled_appointments', type_='foreignkey')
|
||||||
|
op.drop_constraint('scheduled_appointments_type_id_fkey', 'scheduled_appointments', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'scheduled_appointments', 'users', ['doctor_id'], ['id'], source_schema='public', referent_schema='public', ondelete='SET NULL')
|
||||||
|
op.create_foreign_key(None, 'scheduled_appointments', 'appointment_types', ['type_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.create_foreign_key(None, 'scheduled_appointments', 'patients', ['patient_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.alter_column('sessions', 'user_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=True)
|
||||||
|
op.drop_constraint('sessions_user_id_fkey', 'sessions', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'sessions', 'users', ['user_id'], ['id'], source_schema='public', referent_schema='public', ondelete='SET NULL')
|
||||||
|
op.drop_constraint('set_contents_type_id_fkey', 'set_contents', type_='foreignkey')
|
||||||
|
op.drop_constraint('set_contents_set_id_fkey', 'set_contents', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'set_contents', 'lens_types', ['type_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.create_foreign_key(None, 'set_contents', 'sets', ['set_id'], ['id'], source_schema='public', referent_schema='public', ondelete='CASCADE')
|
||||||
|
op.drop_constraint('users_role_id_fkey', 'users', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'users', 'roles', ['role_id'], ['id'], source_schema='public', referent_schema='public')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint(None, 'users', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('users_role_id_fkey', 'users', 'roles', ['role_id'], ['id'])
|
||||||
|
op.drop_constraint(None, 'set_contents', schema='public', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'set_contents', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('set_contents_set_id_fkey', 'set_contents', 'sets', ['set_id'], ['id'])
|
||||||
|
op.create_foreign_key('set_contents_type_id_fkey', 'set_contents', 'lens_types', ['type_id'], ['id'])
|
||||||
|
op.drop_constraint(None, 'sessions', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('sessions_user_id_fkey', 'sessions', 'users', ['user_id'], ['id'])
|
||||||
|
op.alter_column('sessions', 'user_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
op.drop_constraint(None, 'scheduled_appointments', schema='public', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'scheduled_appointments', schema='public', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'scheduled_appointments', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('scheduled_appointments_type_id_fkey', 'scheduled_appointments', 'appointment_types', ['type_id'], ['id'])
|
||||||
|
op.create_foreign_key('scheduled_appointments_patient_id_fkey', 'scheduled_appointments', 'patients', ['patient_id'], ['id'])
|
||||||
|
op.create_foreign_key('scheduled_appointments_doctor_id_fkey', 'scheduled_appointments', 'users', ['doctor_id'], ['id'])
|
||||||
|
op.alter_column('scheduled_appointments', 'doctor_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
op.drop_constraint(None, 'recipients', schema='public', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'recipients', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('recipients_mailing_id_fkey', 'recipients', 'mailing', ['mailing_id'], ['id'])
|
||||||
|
op.create_foreign_key('recipients_patient_id_fkey', 'recipients', 'patients', ['patient_id'], ['id'])
|
||||||
|
op.drop_constraint(None, 'mailing_options', schema='public', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'mailing_options', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('mailing_options_option_id_fkey', 'mailing_options', 'mailing_delivery_methods', ['option_id'], ['id'])
|
||||||
|
op.create_foreign_key('mailing_options_mailing_id_fkey', 'mailing_options', 'mailing', ['mailing_id'], ['id'])
|
||||||
|
op.drop_constraint(None, 'mailing', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('mailing_user_id_fkey', 'mailing', 'users', ['user_id'], ['id'])
|
||||||
|
op.alter_column('mailing', 'user_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
op.drop_constraint(None, 'lens_issues', schema='public', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'lens_issues', schema='public', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'lens_issues', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('lens_issues_doctor_id_fkey', 'lens_issues', 'users', ['doctor_id'], ['id'])
|
||||||
|
op.create_foreign_key('lens_issues_lens_id_fkey', 'lens_issues', 'lens', ['lens_id'], ['id'])
|
||||||
|
op.create_foreign_key('lens_issues_patient_id_fkey', 'lens_issues', 'patients', ['patient_id'], ['id'])
|
||||||
|
op.alter_column('lens_issues', 'doctor_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
op.drop_constraint(None, 'lens', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('lens_type_id_fkey', 'lens', 'lens_types', ['type_id'], ['id'])
|
||||||
|
op.drop_constraint(None, 'backups', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('backups_user_id_fkey', 'backups', 'users', ['user_id'], ['id'])
|
||||||
|
op.alter_column('backups', 'user_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
op.drop_constraint(None, 'appointments', schema='public', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'appointments', schema='public', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'appointments', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('appointments_patient_id_fkey', 'appointments', 'patients', ['patient_id'], ['id'])
|
||||||
|
op.create_foreign_key('appointments_doctor_id_fkey', 'appointments', 'users', ['doctor_id'], ['id'])
|
||||||
|
op.create_foreign_key('appointments_type_id_fkey', 'appointments', 'appointment_types', ['type_id'], ['id'])
|
||||||
|
op.alter_column('appointments', 'doctor_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
op.drop_constraint(None, 'appointment_files', schema='public', type_='foreignkey')
|
||||||
|
op.create_foreign_key('appointment_files_appointment_id_fkey', 'appointment_files', 'appointments', ['appointment_id'], ['id'])
|
||||||
|
# ### end Alembic commands ###
|
||||||
@ -12,6 +12,7 @@ class AppointmentFile(BaseModel):
|
|||||||
file_path = Column(String, nullable=False)
|
file_path = Column(String, nullable=False)
|
||||||
file_title = Column(String, nullable=False)
|
file_title = Column(String, nullable=False)
|
||||||
|
|
||||||
appointment_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.appointments.id'), nullable=False)
|
appointment_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.appointments.id', ondelete='CASCADE'),
|
||||||
|
nullable=False)
|
||||||
|
|
||||||
appointment = relationship('Appointment', back_populates='files')
|
appointment = relationship('Appointment', back_populates='files')
|
||||||
|
|||||||
@ -14,9 +14,9 @@ class Appointment(BaseModel):
|
|||||||
days_until_the_next_appointment = Column(Integer)
|
days_until_the_next_appointment = Column(Integer)
|
||||||
appointment_datetime = Column(DateTime, nullable=False, server_default=func.now())
|
appointment_datetime = Column(DateTime, nullable=False, server_default=func.now())
|
||||||
|
|
||||||
patient_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.patients.id'), nullable=False)
|
patient_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.patients.id', ondelete='CASCADE'), nullable=False)
|
||||||
doctor_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id'), nullable=False)
|
doctor_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id', ondelete='SET NULL'), nullable=True)
|
||||||
type_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.appointment_types.id'), nullable=False)
|
type_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.appointment_types.id', ondelete='CASCADE'), nullable=False)
|
||||||
|
|
||||||
patient = relationship('Patient', back_populates='appointments')
|
patient = relationship('Patient', back_populates='appointments')
|
||||||
doctor = relationship('User', back_populates='appointments')
|
doctor = relationship('User', back_populates='appointments')
|
||||||
|
|||||||
@ -15,4 +15,4 @@ class Backup(BaseModel):
|
|||||||
filename = Column(String, nullable=False)
|
filename = Column(String, nullable=False)
|
||||||
is_by_user = Column(Boolean, nullable=False, default=False)
|
is_by_user = Column(Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
user_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id'), nullable=False)
|
user_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id', ondelete='SET NULL'), nullable=True)
|
||||||
@ -26,7 +26,7 @@ class Lens(BaseModel):
|
|||||||
side = Column(Enum(SideEnum), nullable=False)
|
side = Column(Enum(SideEnum), nullable=False)
|
||||||
issued = Column(Boolean, nullable=False, default=False)
|
issued = Column(Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
type_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.lens_types.id'), nullable=False)
|
type_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.lens_types.id', ondelete='CASCADE'), nullable=False)
|
||||||
|
|
||||||
type = relationship('LensType', back_populates='lenses')
|
type = relationship('LensType', back_populates='lenses')
|
||||||
|
|
||||||
|
|||||||
@ -11,9 +11,9 @@ class LensIssue(BaseModel):
|
|||||||
|
|
||||||
issue_date = Column(Date, nullable=False)
|
issue_date = Column(Date, nullable=False)
|
||||||
|
|
||||||
patient_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.patients.id'), nullable=False)
|
patient_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.patients.id', ondelete='CASCADE'), nullable=False)
|
||||||
doctor_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id'), nullable=False)
|
doctor_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id', ondelete='SET NULL'), nullable=True)
|
||||||
lens_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.lens.id'), nullable=False)
|
lens_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.lens.id', ondelete='CASCADE'), nullable=False)
|
||||||
|
|
||||||
patient = relationship('Patient', back_populates='lens_issues')
|
patient = relationship('Patient', back_populates='lens_issues')
|
||||||
doctor = relationship('User', back_populates='lens_issues')
|
doctor = relationship('User', back_populates='lens_issues')
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class Mailing(BaseModel):
|
|||||||
title = Column(String, nullable=False)
|
title = Column(String, nullable=False)
|
||||||
datetime = Column(DateTime, nullable=False, default=func.utcnow)
|
datetime = Column(DateTime, nullable=False, default=func.utcnow)
|
||||||
|
|
||||||
user_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id'), nullable=False)
|
user_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id', ondelete='SET NULL'), nullable=True)
|
||||||
|
|
||||||
user = relationship('User', back_populates='mailing')
|
user = relationship('User', back_populates='mailing')
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,9 @@ class MailingOption(BaseModel):
|
|||||||
__tablename__ = 'mailing_options'
|
__tablename__ = 'mailing_options'
|
||||||
__table_args__ = {"schema": settings.SCHEMA}
|
__table_args__ = {"schema": settings.SCHEMA}
|
||||||
|
|
||||||
option_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.mailing_delivery_methods.id'), nullable=False)
|
option_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.mailing_delivery_methods.id', ondelete='CASCADE'),
|
||||||
mailing_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.mailing.id'), nullable=False)
|
nullable=False)
|
||||||
|
mailing_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.mailing.id', ondelete='CASCADE'), nullable=False)
|
||||||
|
|
||||||
method = relationship('MailingDeliveryMethod', back_populates='mailing')
|
method = relationship('MailingDeliveryMethod', back_populates='mailing')
|
||||||
mailing = relationship('Mailing', back_populates='mailing_options')
|
mailing = relationship('Mailing', back_populates='mailing_options')
|
||||||
|
|||||||
@ -9,8 +9,8 @@ class Recipient(BaseModel):
|
|||||||
__tablename__ = 'recipients'
|
__tablename__ = 'recipients'
|
||||||
__table_args__ = {"schema": settings.SCHEMA}
|
__table_args__ = {"schema": settings.SCHEMA}
|
||||||
|
|
||||||
patient_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.patients.id'), nullable=False)
|
patient_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.patients.id', ondelete='CASCADE'), nullable=False)
|
||||||
mailing_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.mailing.id'), nullable=False)
|
mailing_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.mailing.id', ondelete='CASCADE'), nullable=False)
|
||||||
|
|
||||||
patient = relationship('Patient', back_populates='mailing')
|
patient = relationship('Patient', back_populates='mailing')
|
||||||
mailing = relationship('Mailing', back_populates='recipients')
|
mailing = relationship('Mailing', back_populates='recipients')
|
||||||
|
|||||||
@ -13,9 +13,9 @@ class ScheduledAppointment(BaseModel):
|
|||||||
scheduled_datetime = Column(DateTime, nullable=False, server_default=func.now())
|
scheduled_datetime = Column(DateTime, nullable=False, server_default=func.now())
|
||||||
is_canceled = Column(Boolean, nullable=False, default=False, server_default='false')
|
is_canceled = Column(Boolean, nullable=False, default=False, server_default='false')
|
||||||
|
|
||||||
patient_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.patients.id'), nullable=False)
|
patient_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.patients.id', ondelete='CASCADE'), nullable=False)
|
||||||
doctor_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id'), nullable=False)
|
doctor_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id', ondelete='SET NULL'), nullable=True)
|
||||||
type_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.appointment_types.id'), nullable=False)
|
type_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.appointment_types.id', ondelete='CASCADE'), nullable=False)
|
||||||
|
|
||||||
patient = relationship('Patient', back_populates='scheduled_appointments')
|
patient = relationship('Patient', back_populates='scheduled_appointments')
|
||||||
doctor = relationship('User', back_populates='scheduled_appointments')
|
doctor = relationship('User', back_populates='scheduled_appointments')
|
||||||
|
|||||||
@ -15,6 +15,6 @@ class Session(BaseModel):
|
|||||||
expires_at = Column(DateTime, nullable=False)
|
expires_at = Column(DateTime, nullable=False)
|
||||||
is_active = Column(Boolean, default=True, nullable=False)
|
is_active = Column(Boolean, default=True, nullable=False)
|
||||||
|
|
||||||
user_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id'), nullable=False)
|
user_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.users.id', ondelete='SET NULL'), nullable=True)
|
||||||
|
|
||||||
user = relationship("User", back_populates="sessions")
|
user = relationship("User", back_populates="sessions")
|
||||||
|
|||||||
@ -20,8 +20,8 @@ class SetContent(BaseModel):
|
|||||||
side = Column(Enum(SideEnum), nullable=False)
|
side = Column(Enum(SideEnum), nullable=False)
|
||||||
count = Column(Integer, nullable=False)
|
count = Column(Integer, nullable=False)
|
||||||
|
|
||||||
type_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.lens_types.id'), nullable=False)
|
type_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.lens_types.id', ondelete='CASCADE'), nullable=False)
|
||||||
set_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.sets.id'), nullable=False)
|
set_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.sets.id', ondelete='CASCADE'), nullable=False)
|
||||||
|
|
||||||
type = relationship('LensType', back_populates='contents')
|
type = relationship('LensType', back_populates='contents')
|
||||||
set = relationship('Set', back_populates='contents')
|
set = relationship('Set', back_populates='contents')
|
||||||
|
|||||||
@ -24,6 +24,13 @@ class PatientsService:
|
|||||||
total_count
|
total_count
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def get_patients_with_email(self) -> list[PatientEntity]:
|
||||||
|
patients = await self.patient_repository.get_width_email()
|
||||||
|
return [
|
||||||
|
self.model_to_entity(patient)
|
||||||
|
for patient in patients
|
||||||
|
]
|
||||||
|
|
||||||
async def create_patient(self, patient: PatientEntity) -> PatientEntity:
|
async def create_patient(self, patient: PatientEntity) -> PatientEntity:
|
||||||
patient_model = self.entity_to_model(patient)
|
patient_model = self.entity_to_model(patient)
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export const patientsApi = createApi({
|
|||||||
tagTypes: ['Patient'],
|
tagTypes: ['Patient'],
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getPatients: builder.query({
|
getPatients: builder.query({
|
||||||
query: ({ page, pageSize, search, sortOrder }) => ({
|
query: ({page, pageSize, search, sortOrder}) => ({
|
||||||
url: '/patients/',
|
url: '/patients/',
|
||||||
params: {
|
params: {
|
||||||
page,
|
page,
|
||||||
@ -21,7 +21,7 @@ export const patientsApi = createApi({
|
|||||||
getAllPatients: builder.query({
|
getAllPatients: builder.query({
|
||||||
query: () => ({
|
query: () => ({
|
||||||
url: '/patients/',
|
url: '/patients/',
|
||||||
params: { all_params: true },
|
params: {all_params: true},
|
||||||
}),
|
}),
|
||||||
providesTags: ['Patients'],
|
providesTags: ['Patients'],
|
||||||
transformResponse: (response) => {
|
transformResponse: (response) => {
|
||||||
@ -55,6 +55,12 @@ export const patientsApi = createApi({
|
|||||||
}),
|
}),
|
||||||
invalidatesTags: ['Patients']
|
invalidatesTags: ['Patients']
|
||||||
}),
|
}),
|
||||||
|
getPatientsWithEmail: builder.query({
|
||||||
|
query: () => ({
|
||||||
|
url: `/patients/email/`,
|
||||||
|
}),
|
||||||
|
providesTags: ['Patients'],
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -64,4 +70,5 @@ export const {
|
|||||||
useAddPatientMutation,
|
useAddPatientMutation,
|
||||||
useUpdatePatientMutation,
|
useUpdatePatientMutation,
|
||||||
useDeletePatientMutation,
|
useDeletePatientMutation,
|
||||||
|
useGetPatientsWithEmailQuery,
|
||||||
} = patientsApi;
|
} = patientsApi;
|
||||||
@ -10,6 +10,7 @@ import AppointmentsPage from "../Components/Pages/AppointmentsPage/AppointmentsP
|
|||||||
import ProfilePage from "../Components/Pages/ProfilePage/ProfilePage.jsx";
|
import ProfilePage from "../Components/Pages/ProfilePage/ProfilePage.jsx";
|
||||||
import AdminRoute from "./AdminRoute.jsx";
|
import AdminRoute from "./AdminRoute.jsx";
|
||||||
import AdminPage from "../Components/Pages/AdminPage/AdminPage.jsx";
|
import AdminPage from "../Components/Pages/AdminPage/AdminPage.jsx";
|
||||||
|
import MailingPage from "../Components/Pages/MailingPage/MailingPage.jsx";
|
||||||
|
|
||||||
|
|
||||||
const AppRouter = () => (
|
const AppRouter = () => (
|
||||||
@ -23,6 +24,7 @@ const AppRouter = () => (
|
|||||||
<Route path={"/issues"} element={<IssuesPage/>}/>
|
<Route path={"/issues"} element={<IssuesPage/>}/>
|
||||||
<Route path={"/appointments"} element={<AppointmentsPage/>}/>
|
<Route path={"/appointments"} element={<AppointmentsPage/>}/>
|
||||||
<Route path={"/profile"} element={<ProfilePage/>}/>
|
<Route path={"/profile"} element={<ProfilePage/>}/>
|
||||||
|
<Route path={"/mailing"} element={<MailingPage/>}/>
|
||||||
<Route path={"/"} element={<HomePage/>}/>
|
<Route path={"/"} element={<HomePage/>}/>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
127
web-app/src/Components/Pages/MailingPage/MailingPage.jsx
Normal file
127
web-app/src/Components/Pages/MailingPage/MailingPage.jsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { Button, Card, Input, Table, Typography, Result, Space } from "antd";
|
||||||
|
import { MailOutlined, SearchOutlined } from "@ant-design/icons";
|
||||||
|
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
|
import useMailingPage from "./useMailingPage.js";
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
const MailingPage = () => {
|
||||||
|
const {
|
||||||
|
patientsWithEmail,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
selectedPatientIds,
|
||||||
|
subject,
|
||||||
|
body,
|
||||||
|
searchQuery,
|
||||||
|
containerStyle,
|
||||||
|
cardStyle,
|
||||||
|
buttonStyle,
|
||||||
|
handleSelectPatients,
|
||||||
|
handleSubjectChange,
|
||||||
|
handleBodyChange,
|
||||||
|
handleSearchChange,
|
||||||
|
handleSendEmail,
|
||||||
|
isMobile,
|
||||||
|
} = useMailingPage();
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<Result
|
||||||
|
status="error"
|
||||||
|
title="Ошибка"
|
||||||
|
subTitle="Произошла ошибка при загрузке данных пациентов"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "Фамилия",
|
||||||
|
dataIndex: "last_name",
|
||||||
|
key: "last_name",
|
||||||
|
sorter: (a, b) => a.last_name.localeCompare(b.last_name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Имя",
|
||||||
|
dataIndex: "first_name",
|
||||||
|
key: "first_name",
|
||||||
|
sorter: (a, b) => a.first_name.localeCompare(b.first_name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Email",
|
||||||
|
dataIndex: "email",
|
||||||
|
key: "email",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={containerStyle}>
|
||||||
|
<Title level={1}><MailOutlined /> Рассылки</Title>
|
||||||
|
{isLoading ? (
|
||||||
|
<LoadingIndicator />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Card style={cardStyle} title="Выбор пациентов">
|
||||||
|
<Input
|
||||||
|
placeholder="Поиск по имени или email"
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
style={{ marginBottom: 16, width: isMobile ? "100%" : 300 }}
|
||||||
|
/>
|
||||||
|
<Table
|
||||||
|
rowSelection={{
|
||||||
|
type: "checkbox",
|
||||||
|
selectedRowKeys: selectedPatientIds,
|
||||||
|
onChange: handleSelectPatients,
|
||||||
|
}}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={patientsWithEmail.map((patient) => ({
|
||||||
|
...patient,
|
||||||
|
key: patient.id,
|
||||||
|
}))}
|
||||||
|
pagination={{ pageSize: 10 }}
|
||||||
|
rowKey="id"
|
||||||
|
size={isMobile ? "small" : "middle"}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Card style={cardStyle} title="Создание письма">
|
||||||
|
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||||
|
<div>
|
||||||
|
<Text strong>Тема письма:</Text>
|
||||||
|
<Input
|
||||||
|
placeholder="Введите тему письма"
|
||||||
|
value={subject}
|
||||||
|
onChange={handleSubjectChange}
|
||||||
|
style={{ marginTop: 8 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text strong>Текст письма:</Text>
|
||||||
|
<TextArea
|
||||||
|
placeholder="Введите текст письма"
|
||||||
|
value={body}
|
||||||
|
onChange={handleBodyChange}
|
||||||
|
rows={6}
|
||||||
|
style={{ marginTop: 8 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<MailOutlined />}
|
||||||
|
onClick={handleSendEmail}
|
||||||
|
style={buttonStyle}
|
||||||
|
>
|
||||||
|
Отправить письмо
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MailingPage;
|
||||||
109
web-app/src/Components/Pages/MailingPage/useMailingPage.js
Normal file
109
web-app/src/Components/Pages/MailingPage/useMailingPage.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { useState, useMemo } from "react";
|
||||||
|
import { notification } from "antd";
|
||||||
|
import { Grid } from "antd";
|
||||||
|
import { useGetPatientsWithEmailQuery } from "../../../Api/patientsApi.js";
|
||||||
|
|
||||||
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
|
const useMailingPage = () => {
|
||||||
|
const screens = useBreakpoint();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: patientsWithEmail = [],
|
||||||
|
isLoading: isLoadingPatients,
|
||||||
|
isError: isErrorPatients,
|
||||||
|
} = useGetPatientsWithEmailQuery(undefined, {
|
||||||
|
pollingInterval: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [selectedPatientIds, setSelectedPatientIds] = useState([]);
|
||||||
|
const [subject, setSubject] = useState("");
|
||||||
|
const [body, setBody] = useState("");
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
|
||||||
|
const containerStyle = { padding: screens.xs ? 16 : 24 };
|
||||||
|
const cardStyle = { marginBottom: 24 };
|
||||||
|
const buttonStyle = { width: screens.xs ? "100%" : "auto", marginTop: 16 };
|
||||||
|
|
||||||
|
const filteredPatients = useMemo(() => {
|
||||||
|
if (!searchQuery) return patientsWithEmail;
|
||||||
|
const lowerQuery = searchQuery.toLowerCase();
|
||||||
|
return patientsWithEmail.filter(
|
||||||
|
(patient) =>
|
||||||
|
`${patient.last_name} ${patient.first_name}`.toLowerCase().includes(lowerQuery) ||
|
||||||
|
patient.email.toLowerCase().includes(lowerQuery)
|
||||||
|
);
|
||||||
|
}, [patientsWithEmail, searchQuery]);
|
||||||
|
|
||||||
|
const handleSelectPatients = (selectedRowKeys) => {
|
||||||
|
setSelectedPatientIds(selectedRowKeys);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubjectChange = (e) => {
|
||||||
|
setSubject(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBodyChange = (e) => {
|
||||||
|
setBody(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchChange = (e) => {
|
||||||
|
setSearchQuery(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateMailtoLink = () => {
|
||||||
|
if (!selectedPatientIds.length) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка",
|
||||||
|
description: "Выберите хотя бы одного пациента.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!subject || !body) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка",
|
||||||
|
description: "Заполните тему и текст письма.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedEmails = patientsWithEmail
|
||||||
|
.filter((patient) => selectedPatientIds.includes(patient.id))
|
||||||
|
.map((patient) => patient.email)
|
||||||
|
.join(",");
|
||||||
|
|
||||||
|
const encodedSubject = encodeURIComponent(subject);
|
||||||
|
const encodedBody = encodeURIComponent(body);
|
||||||
|
return `mailto:?bcc=${selectedEmails}&subject=${encodedSubject}&body=${encodedBody}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendEmail = () => {
|
||||||
|
const mailtoLink = generateMailtoLink();
|
||||||
|
if (mailtoLink) {
|
||||||
|
window.location.href = mailtoLink;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
patientsWithEmail: filteredPatients,
|
||||||
|
isLoading: isLoadingPatients,
|
||||||
|
isError: isErrorPatients,
|
||||||
|
selectedPatientIds,
|
||||||
|
subject,
|
||||||
|
body,
|
||||||
|
searchQuery,
|
||||||
|
containerStyle,
|
||||||
|
cardStyle,
|
||||||
|
buttonStyle,
|
||||||
|
handleSelectPatients,
|
||||||
|
handleSubjectChange,
|
||||||
|
handleBodyChange,
|
||||||
|
handleSearchChange,
|
||||||
|
handleSendEmail,
|
||||||
|
isMobile: screens.xs,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useMailingPage;
|
||||||
Loading…
x
Reference in New Issue
Block a user