Compare commits

...

2 Commits

Author SHA1 Message Date
1541aba89f feat: Добавлен запрос истории линз по пациенту
Добавлен API endpoint и query для получения истории линз по ID пациента.
2025-07-04 07:06:57 +05:00
12eea23299 feat: Добавлена каскадное удаление для моделей. 2025-07-03 15:50:02 +05:00
21 changed files with 145 additions and 56 deletions

View File

@ -1,10 +1,22 @@
FROM python:3.10-slim
RUN apt-get update && apt-get install -y \
lsb-release \
wget \
gnupg \
&& rm -rf /var/lib/apt/lists/*
RUN echo "deb http://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
&& wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN apt-get update && apt-get install -y \
libpq-dev \
libmagic1 \
postgresql-client-17 \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/backups && chmod 777 /app/backups
WORKDIR /app
COPY req.txt .
@ -15,4 +27,4 @@ COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host=0.0.0.0"]
CMD ["uvicorn", "app.main:app", "--host=0.0.0.0"]

View File

@ -13,13 +13,13 @@ class LensIssuesRepository:
self.db = db
async def get_all(
self,
skip: int = 0,
limit: int = 10,
search: Optional[str] = None,
sort_order: Literal["asc", "desc"] = "desc",
start_date: Optional[date] = None,
end_date: Optional[date] = None
self,
skip: int = 0,
limit: int = 10,
search: Optional[str] = None,
sort_order: Literal["asc", "desc"] = "desc",
start_date: Optional[date] = None,
end_date: Optional[date] = None
) -> Tuple[Sequence[LensIssue], int]:
stmt = (
select(LensIssue)
@ -76,6 +76,15 @@ class LensIssuesRepository:
return issues, total_count
async def get_by_patient_id(self, patient_id: int) -> Sequence[LensIssue]:
stmt = (
select(LensIssue)
.filter_by(patient_id=patient_id)
.options(joinedload(LensIssue.lens))
)
result = await self.db.execute(stmt)
return result.scalars().all()
async def get_by_id(self, lens_issue_id: int) -> Optional[LensIssue]:
stmt = select(LensIssue).filter_by(id=lens_issue_id)
result = await self.db.execute(stmt)

View File

@ -20,14 +20,14 @@ router = APIRouter()
description="Returns a paginated list of lens issues with optional filtering and sorting",
)
async def get_all_lens_issues(
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
search: Optional[str] = Query(None),
sort_order: Literal["asc", "desc"] = Query("desc"),
start_date: Optional[date] = Query(None),
end_date: Optional[date] = Query(None),
db: AsyncSession = Depends(get_db),
user=Depends(get_current_user),
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
search: Optional[str] = Query(None),
sort_order: Literal["asc", "desc"] = Query("desc"),
start_date: Optional[date] = Query(None),
end_date: Optional[date] = Query(None),
db: AsyncSession = Depends(get_db),
user=Depends(get_current_user),
):
lens_issues_service = LensIssuesService(db)
skip = (page - 1) * page_size
@ -44,6 +44,22 @@ async def get_all_lens_issues(
total_count=total_count
)
@router.get(
'/by-patient/{patient_id}/',
response_model=list[LensIssueEntity],
summary="Get lens issues by patients id",
description="Returns a paginated list of lens issues by patients id",
)
async def get_lens_issues_by_patient(
patient_id: int,
db: AsyncSession = Depends(get_db),
user=Depends(get_current_user),
):
lens_issues_service = LensIssuesService(db)
return await lens_issues_service.get_lens_issues_by_patient_id(patient_id)
@router.post(
"/",
response_model=LensIssueEntity,

View File

@ -11,5 +11,5 @@ class AppointmentType(BaseModel):
title = Column(VARCHAR(150), nullable=False, unique=True)
appointments = relationship('Appointment', back_populates='type')
scheduled_appointments = relationship('ScheduledAppointment', back_populates='type')
appointments = relationship('Appointment', back_populates='type', cascade="all, delete")
scheduled_appointments = relationship('ScheduledAppointment', back_populates='type', cascade="all, delete")

View File

@ -22,4 +22,4 @@ class Appointment(BaseModel):
doctor = relationship('User', back_populates='appointments')
type = relationship('AppointmentType', back_populates='appointments')
files = relationship('AppointmentFile', back_populates='appointment')
files = relationship('AppointmentFile', back_populates='appointment', cascade="all, delete")

View File

@ -30,4 +30,4 @@ class Lens(BaseModel):
type = relationship('LensType', back_populates='lenses')
lens_issues = relationship('LensIssue', back_populates='lens')
lens_issues = relationship('LensIssue', back_populates='lens', cascade="all, delete")

View File

@ -11,5 +11,5 @@ class LensType(BaseModel):
title = Column(VARCHAR(150), nullable=False, unique=True)
lenses = relationship('Lens', back_populates='type')
contents = relationship('SetContent', back_populates='type')
lenses = relationship('Lens', back_populates='type', cascade="all, delete")
contents = relationship('SetContent', back_populates='type', cascade="all, delete")

View File

@ -18,5 +18,5 @@ class Mailing(BaseModel):
user = relationship('User', back_populates='mailing')
recipients = relationship('Recipient', back_populates='mailing')
mailing_options = relationship('MailingOption', back_populates='mailing')
recipients = relationship('Recipient', back_populates='mailing', cascade="all, delete")
mailing_options = relationship('MailingOption', back_populates='mailing', cascade="all, delete")

View File

@ -11,4 +11,4 @@ class MailingDeliveryMethod(BaseModel):
title = Column(VARCHAR(200), nullable=False)
mailing = relationship('MailingOption', back_populates='method')
mailing = relationship('MailingOption', back_populates='method', cascade="all, delete")

View File

@ -13,5 +13,5 @@ class MailingOption(BaseModel):
nullable=False)
mailing_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.mailing.id', ondelete='CASCADE'), nullable=False)
method = relationship('MailingDeliveryMethod', back_populates='mailing')
mailing = relationship('Mailing', back_populates='mailing_options')
method = relationship('MailingDeliveryMethod', back_populates='mailing', cascade="all, delete")
mailing = relationship('Mailing', back_populates='mailing_options', cascade="all, delete")

View File

@ -19,7 +19,7 @@ class Patient(BaseModel):
diagnosis = Column(String)
correction = Column(String)
lens_issues = relationship('LensIssue', back_populates='patient')
appointments = relationship('Appointment', back_populates='patient')
mailing = relationship('Recipient', back_populates='patient')
scheduled_appointments = relationship('ScheduledAppointment', back_populates='patient')
lens_issues = relationship('LensIssue', back_populates='patient', cascade="all, delete")
appointments = relationship('Appointment', back_populates='patient', cascade="all, delete")
mailing = relationship('Recipient', back_populates='patient', cascade="all, delete")
scheduled_appointments = relationship('ScheduledAppointment', back_populates='patient', cascade="all, delete")

View File

@ -12,5 +12,5 @@ class Recipient(BaseModel):
patient_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.patients.id', ondelete='CASCADE'), nullable=False)
mailing_id = Column(Integer, ForeignKey(f'{settings.SCHEMA}.mailing.id', ondelete='CASCADE'), nullable=False)
patient = relationship('Patient', back_populates='mailing')
mailing = relationship('Mailing', back_populates='recipients')
patient = relationship('Patient', back_populates='mailing', cascade="all, delete")
mailing = relationship('Mailing', back_populates='recipients', cascade="all, delete")

View File

@ -11,4 +11,4 @@ class Set(BaseModel):
title = Column(VARCHAR(150), nullable=False, unique=True)
contents = relationship('SetContent', back_populates='set')
contents = relationship('SetContent', back_populates='set', cascade="all, delete")

View File

@ -1,16 +1,14 @@
import datetime
import io
import os
import shutil
import subprocess
import tarfile
from typing import Optional
from fastapi_maintenance import maintenance_mode_on
import aiofiles
import magic
from fastapi import HTTPException, UploadFile
from magic import magic
from sqlalchemy.ext.asyncio import AsyncSession, AsyncEngine
from sqlalchemy.ext.asyncio import AsyncSession
from starlette.responses import FileResponse
from werkzeug.utils import secure_filename

View File

@ -24,13 +24,13 @@ class LensIssuesService:
self.lenses_repository = LensesRepository(db)
async def get_all_lens_issues(
self,
skip: int = 0,
limit: int = 10,
search: Optional[str] = None,
sort_order: Literal["asc", "desc"] = "desc",
start_date: Optional[date] = None,
end_date: Optional[date] = None
self,
skip: int = 0,
limit: int = 10,
search: Optional[str] = None,
sort_order: Literal["asc", "desc"] = "desc",
start_date: Optional[date] = None,
end_date: Optional[date] = None
) -> Tuple[list[LensIssueEntity], int]:
lens_issues, total_count = await self.lens_issues_repository.get_all(
skip=skip,
@ -45,6 +45,24 @@ class LensIssuesService:
total_count
)
async def get_lens_issues_by_patient_id(self, patient_id: int) -> list[LensIssueEntity]:
patient = await self.patient_repository.get_by_id(patient_id)
if not patient:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Пациент с таким ID не найден',
)
lens_issues = await self.lens_issues_repository.get_by_patient_id(patient.id)
print(lens_issues)
return [
self.model_to_entity(lens_issue)
for lens_issue in lens_issues
]
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)

View File

@ -19,6 +19,13 @@ spec:
ports:
- containerPort: {{ .Values.service.port }}
env:
- name: PG_DUMP_PATH
value: "{{ .Values.env.PG_DUMP_PATH }}"
- name: BACKUP_DB_URL
valueFrom:
secretKeyRef:
name: visus-api-secret
key: BACKUP_DB_URL
- name: SECRET_KEY
valueFrom:
secretKeyRef:

View File

@ -32,4 +32,5 @@ ingress:
env:
LOG_LEVEL: info
LOG_FILE: logs/app.log
ALGORITHM: HS256
ALGORITHM: HS256
PG_DUMP_PATH: pg_dump

View File

@ -12,4 +12,6 @@ pyjwt==2.10.1
python-magic==0.4.27
aiofiles==24.1.0
python-multipart==0.0.20
fastapi-maintenance==0.0.4
fastapi-maintenance==0.0.4
python-magic==0.4.27
libmagic==1.0

View File

@ -1,5 +1,5 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQueryWithAuth } from "./baseQuery.js";
import {createApi} from "@reduxjs/toolkit/query/react";
import {baseQueryWithAuth} from "./baseQuery.js";
export const lensIssuesApi = createApi({
reducerPath: 'lensIssuesApi',
@ -7,7 +7,7 @@ export const lensIssuesApi = createApi({
tagTypes: ['LensIssues'],
endpoints: (builder) => ({
getLensIssues: builder.query({
query: ({ page, pageSize, search, sortOrder, startDate, endDate }) => ({
query: ({page, pageSize, search, sortOrder, startDate, endDate}) => ({
url: '/lens_issues/',
params: {
page,
@ -22,16 +22,16 @@ export const lensIssuesApi = createApi({
transformResponse: (response) => {
if (!response) {
console.warn('Empty lens issues API response:', response);
return { issues: [], total_count: 0 };
return {issues: [], total_count: 0};
}
if (Array.isArray(response.results) && typeof response.count === 'number') {
return { issues: response.results, total_count: response.count };
return {issues: response.results, total_count: response.count};
}
if (Array.isArray(response.issues) && typeof response.total_count === 'number') {
return response;
}
console.warn('Unexpected lens issues API response:', response);
return { issues: [], total_count: 0 };
return {issues: [], total_count: 0};
},
transformErrorResponse: (response) => {
console.error('Lens issues API error:', response);
@ -46,10 +46,15 @@ export const lensIssuesApi = createApi({
}),
invalidatesTags: ['LensIssues'],
}),
getLensIssuesByPatient: builder.query({
query: (patientId) => `/lens_issues/by-patient/${patientId}/`,
providesTags: ['LensIssues'],
}),
}),
});
export const {
useGetLensIssuesQuery,
useAddLensIssuesMutation,
useGetLensIssuesByPatientQuery,
} = lensIssuesApi;

View File

@ -7,8 +7,8 @@ import {useAddPatientMutation, useUpdatePatientMutation} from "../../../Api/pati
const usePatientFormModal = () => {
const dispatch = useDispatch();
const [addPatient, { isLoading: isAdding }] = useAddPatientMutation();
const [updatePatient, { isLoading: isUpdating }] = useUpdatePatientMutation();
const [addPatient, {isLoading: isAdding}] = useAddPatientMutation();
const [updatePatient, {isLoading: isUpdating}] = useUpdatePatientMutation();
const {
selectedPatient,
@ -19,7 +19,7 @@ const usePatientFormModal = () => {
try {
if (selectedPatient) {
await updatePatient({ id: selectedPatient.id, ...patientData }).unwrap();
await updatePatient({id: selectedPatient.id, ...patientData}).unwrap();
notification.success({
message: "Пациент обновлён",
description: `Данные пациента ${patientData.first_name} ${patientData.last_name} успешно обновлены.`,

View File

@ -0,0 +1,21 @@
import { useGetLensIssuesByPatientQuery } from "../../../../../Api/lensIssuesApi.js";
const usePatientsViewModal = (patient, visible) => {
const {
data: lensIssues = [],
isLoading: isLensIssuesLoading,
isError: isLensIssuesError,
} = useGetLensIssuesByPatientQuery(patient?.id, {
skip: !visible || !patient?.id,
pollingInterval: 60000,
refetchOnMountOrArgChange: true,
});
return {
lensIssues,
isLensIssuesLoading,
isLensIssuesError,
};
};
export default usePatientsViewModal;