сделал отмену запланированного приема
This commit is contained in:
parent
264ac5063a
commit
d101036445
@ -17,6 +17,7 @@ class ScheduledAppointmentsRepository:
|
||||
.options(joinedload(ScheduledAppointment.type))
|
||||
.options(joinedload(ScheduledAppointment.patient))
|
||||
.options(joinedload(ScheduledAppointment.doctor))
|
||||
.filter_by(is_canceled=False)
|
||||
.order_by(desc(ScheduledAppointment.scheduled_datetime))
|
||||
)
|
||||
result = await self.db.execute(stmt)
|
||||
@ -28,7 +29,7 @@ class ScheduledAppointmentsRepository:
|
||||
.options(joinedload(ScheduledAppointment.type))
|
||||
.options(joinedload(ScheduledAppointment.patient))
|
||||
.options(joinedload(ScheduledAppointment.doctor))
|
||||
.filter_by(doctor_id=doctor_id)
|
||||
.filter_by(doctor_id=doctor_id, is_canceled=False)
|
||||
.order_by(desc(ScheduledAppointment.scheduled_datetime))
|
||||
)
|
||||
result = await self.db.execute(stmt)
|
||||
@ -40,7 +41,7 @@ class ScheduledAppointmentsRepository:
|
||||
.options(joinedload(ScheduledAppointment.type))
|
||||
.options(joinedload(ScheduledAppointment.patient))
|
||||
.options(joinedload(ScheduledAppointment.doctor))
|
||||
.filter_by(patient_id=patient_id)
|
||||
.filter_by(patient_id=patient_id, is_canceled=False)
|
||||
.order_by(desc(ScheduledAppointment.scheduled_datetime))
|
||||
)
|
||||
result = await self.db.execute(stmt)
|
||||
@ -52,7 +53,7 @@ class ScheduledAppointmentsRepository:
|
||||
.options(joinedload(ScheduledAppointment.type))
|
||||
.options(joinedload(ScheduledAppointment.patient))
|
||||
.options(joinedload(ScheduledAppointment.doctor))
|
||||
.filter_by(id=scheduled_appointment_id)
|
||||
.filter_by(id=scheduled_appointment_id, is_canceled=False)
|
||||
)
|
||||
result = await self.db.execute(stmt)
|
||||
return result.scalars().first()
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@ -68,6 +70,21 @@ async def create_appointment(
|
||||
return await appointment_service.create_scheduled_appointment(appointment, user.id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{appointment_id}/cancel/",
|
||||
response_model=Optional[ScheduledAppointmentEntity],
|
||||
summary="Cancel scheduled appointment",
|
||||
description="Cancel scheduled appointment",
|
||||
)
|
||||
async def cancel_appointment(
|
||||
appointment_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user=Depends(get_current_user),
|
||||
):
|
||||
appointment_service = ScheduledAppointmentsService(db)
|
||||
return await appointment_service.cancel_scheduled_appointment(appointment_id, user.id)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{appointment_id}/",
|
||||
response_model=ScheduledAppointmentEntity,
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
"""0004_добавил поле отмены приема
|
||||
|
||||
Revision ID: 69fee5fc14c8
|
||||
Revises: 1e122f5b8727
|
||||
Create Date: 2025-06-01 19:38:02.360583
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '69fee5fc14c8'
|
||||
down_revision: Union[str, None] = '1e122f5b8727'
|
||||
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('scheduled_appointments', sa.Column('is_canceled', sa.Boolean(), server_default='false', nullable=False))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('scheduled_appointments', 'is_canceled')
|
||||
# ### end Alembic commands ###
|
||||
@ -11,6 +11,7 @@ from app.domain.entities.user import UserEntity
|
||||
class ScheduledAppointmentEntity(BaseModel):
|
||||
id: Optional[int] = None
|
||||
scheduled_datetime: datetime.datetime
|
||||
is_canceled: bool
|
||||
|
||||
patient_id: int
|
||||
doctor_id: Optional[int] = None
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from sqlalchemy import Column, Integer, ForeignKey, DateTime
|
||||
from sqlalchemy import Column, Integer, ForeignKey, DateTime, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
@ -9,6 +9,7 @@ class ScheduledAppointment(BaseModel):
|
||||
__tablename__ = 'scheduled_appointments'
|
||||
|
||||
scheduled_datetime = Column(DateTime, nullable=False, server_default=func.now())
|
||||
is_canceled = Column(Boolean, nullable=False, default=False, server_default='false')
|
||||
|
||||
patient_id = Column(Integer, ForeignKey('patients.id'), nullable=False)
|
||||
doctor_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
||||
|
||||
@ -64,9 +64,9 @@ class ScheduledAppointmentsService:
|
||||
]
|
||||
|
||||
async def create_scheduled_appointment(self, scheduled_appointment: ScheduledAppointmentEntity, doctor_id: int) -> \
|
||||
Optional[
|
||||
ScheduledAppointmentEntity
|
||||
]:
|
||||
Optional[
|
||||
ScheduledAppointmentEntity
|
||||
]:
|
||||
patient = await self.patients_repository.get_by_id(scheduled_appointment.patient_id)
|
||||
|
||||
if not patient:
|
||||
@ -99,6 +99,36 @@ class ScheduledAppointmentsService:
|
||||
|
||||
return self.model_to_entity(scheduled_appointment_model)
|
||||
|
||||
async def cancel_scheduled_appointment(self, scheduled_appointment_id: int, doctor_id):
|
||||
scheduled_appointment_model = await self.scheduled_appointment_repository.get_by_id(scheduled_appointment_id)
|
||||
|
||||
if not scheduled_appointment_model:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Scheduled appointment not found")
|
||||
|
||||
if scheduled_appointment_model.is_canceled:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Scheduled appointment already cancelled")
|
||||
|
||||
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/user with this ID was not found',
|
||||
)
|
||||
|
||||
if scheduled_appointment_model.doctor_id != doctor_id and doctor.role.title != 'Администратор':
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail='Permission denied',
|
||||
)
|
||||
|
||||
scheduled_appointment_model.is_canceled = True
|
||||
|
||||
await self.scheduled_appointment_repository.update(scheduled_appointment_model)
|
||||
|
||||
return self.model_to_entity(scheduled_appointment_model)
|
||||
|
||||
async def update_scheduled_appointment(
|
||||
self,
|
||||
scheduled_appointment_id: int,
|
||||
@ -137,6 +167,7 @@ class ScheduledAppointmentsService:
|
||||
scheduled_appointment_model.patient_id = scheduled_appointment.patient_id
|
||||
scheduled_appointment_model.doctor_id = scheduled_appointment.doctor_id
|
||||
scheduled_appointment_model.type_id = scheduled_appointment.type_id
|
||||
scheduled_appointment_model.is_canceled = scheduled_appointment.is_canceled
|
||||
|
||||
await self.scheduled_appointment_repository.update(scheduled_appointment_model)
|
||||
|
||||
@ -149,6 +180,7 @@ class ScheduledAppointmentsService:
|
||||
patient_id=scheduled_appointment.patient_id,
|
||||
doctor_id=scheduled_appointment.doctor_id,
|
||||
type_id=scheduled_appointment.type_id,
|
||||
is_canceled=scheduled_appointment.is_canceled,
|
||||
)
|
||||
|
||||
if scheduled_appointment.id:
|
||||
@ -164,6 +196,7 @@ class ScheduledAppointmentsService:
|
||||
patient_id=scheduled_appointment.patient_id,
|
||||
doctor_id=scheduled_appointment.doctor_id,
|
||||
type_id=scheduled_appointment.type_id,
|
||||
is_canceled=scheduled_appointment.is_canceled,
|
||||
)
|
||||
|
||||
if scheduled_appointment.patient is not None:
|
||||
|
||||
@ -33,6 +33,13 @@ export const scheduledAppointmentsApi = createApi({
|
||||
}),
|
||||
invalidatesTags: ['ScheduledAppointment'],
|
||||
}),
|
||||
cancelScheduledAppointment: builder.mutation({
|
||||
query: (id) => ({
|
||||
url: `/scheduled_appointments/${id}/cancel/`,
|
||||
method: 'POST',
|
||||
}),
|
||||
invalidatesTags: ['ScheduledAppointment'],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -40,4 +47,5 @@ export const {
|
||||
useGetScheduledAppointmentsQuery,
|
||||
useCreateScheduledAppointmentMutation,
|
||||
useUpdateScheduledAppointmentMutation,
|
||||
useCancelScheduledAppointmentMutation,
|
||||
} = scheduledAppointmentsApi;
|
||||
@ -15,10 +15,10 @@ const AppRouter = () => (
|
||||
|
||||
<Route element={<PrivateRoute/>}>
|
||||
<Route element={<MainLayout/>}>
|
||||
<Route path={"/Patients"} element={<PatientsPage/>}/>
|
||||
<Route path={"/Lenses"} element={<LensesSetsPage/>}/>
|
||||
<Route path={"/patients"} element={<PatientsPage/>}/>
|
||||
<Route path={"/lenses"} element={<LensesSetsPage/>}/>
|
||||
<Route path={"/issues"} element={<IssuesPage/>}/>
|
||||
<Route path={"/Appointments"} element={<AppointmentsPage/>}/>
|
||||
<Route path={"/appointments"} element={<AppointmentsPage/>}/>
|
||||
<Route path={"/"} element={<HomePage/>}/>
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
@ -28,7 +28,7 @@ const MainLayout = () => {
|
||||
|
||||
const menuItems = [
|
||||
getItem("Главная", "/", <HomeOutlined/>),
|
||||
getItem("Приёмы", "/Appointments", <CalendarOutlined/>),
|
||||
getItem("Приёмы", "/appointments", <CalendarOutlined/>),
|
||||
getItem("Выдачи линз", "/issues", <DatabaseOutlined/>),
|
||||
getItem("Линзы и наборы", "/Lenses", <FolderViewOutlined/>),
|
||||
getItem("Пациенты", "/Patients", <TeamOutlined/>),
|
||||
|
||||
@ -14,6 +14,8 @@ import {closeModal, openModal} from "../../../Redux/Slices/appointmentsSlice.js"
|
||||
import AppointmentViewModal
|
||||
from "./Components/AppointmentViewModal/AppointmentViewModal.jsx";
|
||||
import ScheduledAppointmentFormModal from "./Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
|
||||
import ScheduledAppointmentsViewModal
|
||||
from "./Components/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
|
||||
|
||||
const AppointmentsPage = () => {
|
||||
const appointmentsData = useAppointments();
|
||||
@ -84,7 +86,6 @@ const AppointmentsPage = () => {
|
||||
{appointmentsPageUI.upcomingEvents.map(app => (
|
||||
<li key={app.id}>
|
||||
{dayjs(app.appointment_datetime || app.scheduled_datetime)
|
||||
.tz('Europe/Moscow')
|
||||
.format('DD.MM.YYYY HH:mm')} -
|
||||
{app.appointment_datetime ? 'Прием' : 'Запланировано'}
|
||||
</li>
|
||||
@ -140,6 +141,7 @@ const AppointmentsPage = () => {
|
||||
/>
|
||||
|
||||
<ScheduledAppointmentFormModal/>
|
||||
<ScheduledAppointmentsViewModal/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -38,15 +38,15 @@ const useAppointmentCalendarUI = (appointments, scheduledAppointments) => {
|
||||
const calendarContainerStyle = {padding: 20};
|
||||
|
||||
const onSelect = (date) => {
|
||||
const selectedDateStr = date.tz('Europe/Moscow').format('YYYY-MM-DD');
|
||||
const selectedDateStr = date.format('YYYY-MM-DD');
|
||||
dispatch(setSelectedDate(selectedDateStr));
|
||||
|
||||
const appointmentsForDate = appointments.filter(app =>
|
||||
dayjs(app.appointment_datetime).tz('Europe/Moscow').format('YYYY-MM-DD') === selectedDateStr
|
||||
dayjs(app.appointment_datetime).format('YYYY-MM-DD') === selectedDateStr
|
||||
);
|
||||
|
||||
const scheduledForDate = scheduledAppointments.filter(app =>
|
||||
dayjs(app.scheduled_datetime).tz('Europe/Moscow').format('YYYY-MM-DD') === selectedDateStr
|
||||
dayjs(app.scheduled_datetime).format('YYYY-MM-DD') === selectedDateStr
|
||||
);
|
||||
|
||||
dispatch(setSelectedAppointments([...appointmentsForDate, ...scheduledForDate]));
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
import {Button, Modal, Popconfirm, Row, Typography} from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import useScheduledAppointmentsViewModal from "./useScheduledAppointmentsViewModal.js";
|
||||
import useScheduledAppointmentsViewModalUI from "./useScheduledAppointmentsViewModalUI.js";
|
||||
|
||||
const ScheduledAppointmentsViewModal = () => {
|
||||
const scheduledAppointmentsViewModalData = useScheduledAppointmentsViewModal();
|
||||
const scheduledAppointmentsViewModalUI = useScheduledAppointmentsViewModalUI(scheduledAppointmentsViewModalData.cancelAppointment);
|
||||
|
||||
if (!scheduledAppointmentsViewModalUI.selectedScheduledAppointment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Просмотр запланированного приема"
|
||||
open={true}
|
||||
onCancel={scheduledAppointmentsViewModalUI.onCancel}
|
||||
footer={null}
|
||||
width={scheduledAppointmentsViewModalUI.modalWidth}
|
||||
>
|
||||
<div style={scheduledAppointmentsViewModalUI.blockStyle}>
|
||||
<Typography.Title level={4}>Информация о приеме</Typography.Title>
|
||||
<p>
|
||||
<b>Пациент:</b>{" "}
|
||||
{scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient ? `${scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient.last_name} ${scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient.first_name}` : "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Дата рождения:</b>{" "}
|
||||
{scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient ? scheduledAppointmentsViewModalUI.getDateString(scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient.birthday) : "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Email:</b> {scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient?.email || "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Телефон:</b> {scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient?.phone || "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Тип
|
||||
приема:</b> {scheduledAppointmentsViewModalUI.selectedScheduledAppointment.type?.title || "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Время приема:</b>{" "}
|
||||
{scheduledAppointmentsViewModalUI.selectedScheduledAppointment.scheduled_datetime
|
||||
? dayjs(scheduledAppointmentsViewModalUI.selectedScheduledAppointment.scheduled_datetime).format("DD.MM.YYYY HH:mm")
|
||||
: "Не указано"}
|
||||
</p>
|
||||
</div>
|
||||
<Row justify="end" style={{...scheduledAppointmentsViewModalUI.footerRowStyle, gap: 8}}>
|
||||
<Button style={scheduledAppointmentsViewModalUI.footerButtonStyle}
|
||||
onClick={scheduledAppointmentsViewModalUI.onCancel}>
|
||||
Закрыть
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="Вы уверены, что хотите отменить прием?"
|
||||
onConfirm={scheduledAppointmentsViewModalUI.cancelScheduledAppointment}
|
||||
okText="Да, отменить"
|
||||
cancelText="Отмена"
|
||||
>
|
||||
<Button type={"primary"} danger>
|
||||
Отмена приема
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Row>
|
||||
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScheduledAppointmentsViewModal;
|
||||
@ -0,0 +1,10 @@
|
||||
import {useCancelScheduledAppointmentMutation} from "../../../../../Api/scheduledAppointmentsApi.js";
|
||||
|
||||
|
||||
const useScheduledAppointmentsViewModal = () => {
|
||||
const [cancelAppointment] = useCancelScheduledAppointmentMutation();
|
||||
|
||||
return {cancelAppointment};
|
||||
};
|
||||
|
||||
export default useScheduledAppointmentsViewModal;
|
||||
@ -0,0 +1,52 @@
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import dayjs from "dayjs";
|
||||
import {setSelectedScheduledAppointment} from "../../../../../Redux/Slices/appointmentsSlice.js";
|
||||
import {notification} from "antd";
|
||||
|
||||
|
||||
const useScheduledAppointmentsViewModalUI = (cancelAppointment) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
const {
|
||||
selectedScheduledAppointment,
|
||||
} = useSelector(state => state.appointmentsUI);
|
||||
|
||||
const blockStyle = {marginBottom: 16};
|
||||
|
||||
const getDateString = (date) => {
|
||||
return date ? dayjs(date).format('DD.MM.YYYY') : 'Не указано';
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
dispatch(setSelectedScheduledAppointment(null));
|
||||
};
|
||||
|
||||
const cancelScheduledAppointment = async () => {
|
||||
try {
|
||||
await cancelAppointment(selectedScheduledAppointment.id);
|
||||
notification.success({
|
||||
message: 'Прием отменен',
|
||||
placement: 'topRight',
|
||||
description: 'Прием успешно отменен.',
|
||||
})
|
||||
onCancel();
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: 'Ошибка',
|
||||
description: error.data?.message || 'Не удалось отменить прием.',
|
||||
placement: 'topRight',
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
selectedScheduledAppointment,
|
||||
blockStyle,
|
||||
getDateString,
|
||||
onCancel,
|
||||
cancelScheduledAppointment,
|
||||
};
|
||||
};
|
||||
|
||||
export default useScheduledAppointmentsViewModalUI;
|
||||
@ -1,22 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import {AppointmentPropType} from "../../Types/appointmentPropType.js";
|
||||
import {ScheduledAppointmentPropType} from "../../Types/scheduledAppointmentPropType.js";
|
||||
import {Modal} from "antd";
|
||||
|
||||
|
||||
const AppointmentCellViewModal = ({visible, onCancel, appointment}) => {
|
||||
return (
|
||||
<Modal
|
||||
open={visible}
|
||||
title={``}
|
||||
>
|
||||
|
||||
</Modal>
|
||||
)
|
||||
};
|
||||
|
||||
AppointmentCellViewModal.propTypes = {
|
||||
appointment: PropTypes.oneOfType([ScheduledAppointmentPropType, AppointmentPropType]).isRequired,
|
||||
};
|
||||
|
||||
export default AppointmentCellViewModal;
|
||||
Loading…
x
Reference in New Issue
Block a user