diff --git a/api/chambers/__init__.py b/api/chambers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/chambers/sql_func.py b/api/chambers/sql_func.py new file mode 100644 index 0000000000..d38d8a2063 --- /dev/null +++ b/api/chambers/sql_func.py @@ -0,0 +1,30 @@ +from django.db import connection +from utils.db import namedtuplefetchall + + +def patients_stationar_unallocated_sql(department_id): + with connection.cursor() as cursor: + cursor.execute( + """ + SELECT + family, + name, + patronymic, + sex, + napravleniye_id, + birthday, + date_part('year', age(birthday))::int AS age + FROM directions_issledovaniya + INNER JOIN directions_napravleniya ON directions_issledovaniya.napravleniye_id=directions_napravleniya.id + INNER JOIN clients_card ON directions_napravleniya.client_id=clients_card.id + INNER JOIN public.clients_individual ON clients_card.individual_id = public.clients_individual.id + WHERE hospital_department_override_id = %(department_id)s + AND data_sozdaniya > now() - INTERVAL '2 months' + AND NOT EXISTS (SELECT direction_id FROM podrazdeleniya_patienttobed WHERE date_out IS NULL AND napravleniye_id = direction_id) + AND NOT EXISTS (SELECT direction_id FROM podrazdeleniya_patientstationarwithoutbeds WHERE napravleniye_id = direction_id) + """, + params={"department_id": department_id}, + ) + + rows = namedtuplefetchall(cursor) + return rows diff --git a/api/chambers/urls.py b/api/chambers/urls.py new file mode 100644 index 0000000000..9c31556a9b --- /dev/null +++ b/api/chambers/urls.py @@ -0,0 +1,15 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path('get-unallocated-patients', views.get_unallocated_patients), + path('get-chambers-and-beds', views.get_chambers_and_beds), + path('entrance-patient-to-bed', views.entrance_patient_to_bed), + path('extract-patient-bed', views.extract_patient_bed), + path('get-attending-doctors', views.get_attending_doctors), + path('update-doctor-to-bed', views.update_doctor_to_bed), + path('get-patients-without-bed', views.get_patients_without_bed), + path('save-patient-without-bed', views.save_patient_without_bed), + path('delete-patient-without-bed', views.delete_patient_without_bed), +] diff --git a/api/chambers/views.py b/api/chambers/views.py new file mode 100644 index 0000000000..56ab8aa70e --- /dev/null +++ b/api/chambers/views.py @@ -0,0 +1,139 @@ +from laboratory.decorators import group_required +from django.contrib.auth.decorators import login_required + +import simplejson as json +from django.http import JsonResponse + +from podrazdeleniya.models import Chamber, Bed, PatientToBed, PatientStationarWithoutBeds +from directions.models import Napravleniya +from users.models import DoctorProfile + +from utils.response import status_response + +import datetime +from .sql_func import patients_stationar_unallocated_sql + + +@login_required +@group_required("Управления палатами") +def get_unallocated_patients(request): + request_data = json.loads(request.body) + department_pk = request_data.get('department_pk', -1) + patients = [ + { + "fio": f'{patient.family} {patient.name} {patient.patronymic if patient.patronymic else None}', + "age": patient.age, + "short_fio": f'{patient.family} {patient.name[0]}. {patient.patronymic[0] if patient.patronymic else None}.', + "sex": patient.sex, + "direction_pk": patient.napravleniye_id, + } + for patient in patients_stationar_unallocated_sql(department_pk) + ] + return JsonResponse({"data": patients}) + + +@login_required +@group_required("Управления палатами") +def get_chambers_and_beds(request): + request_data = json.loads(request.body) + chambers = [] + for ward in Chamber.objects.filter(podrazdelenie_id=request_data.get('department_pk', -1)): + chamber = { + "pk": ward.pk, + "label": ward.title, + "beds": [], + } + for bed in Bed.objects.filter(chamber_id=ward.pk).prefetch_related('chamber'): + chamber["beds"].append({"pk": bed.pk, "bed_number": bed.bed_number, "doctor": [], "patient": []}) + history = PatientToBed.objects.filter(bed_id=bed.pk, date_out__isnull=True).last() + if history: + direction_obj = Napravleniya.objects.get(pk=history.direction.pk) + ind_card = direction_obj.client + patient_data = ind_card.get_data_individual() + chamber["beds"][-1]["patient"] = [ + {"fio": patient_data["fio"], "short_fio": patient_data["short_fio"], "age": patient_data["age"], "sex": patient_data["sex"], "direction_pk": history.direction_id} + ] + if history.doctor: + chamber["beds"][-1]["doctor"] = [ + { + "fio": history.doctor.get_full_fio(), + "pk": history.doctor.pk, + "highlight": False, + "short_fio": history.doctor.get_fio(), + } + ] + chambers.append(chamber) + return JsonResponse({"data": chambers}) + + +@login_required +@group_required("Управления палатами") +def entrance_patient_to_bed(request): + request_data = json.loads(request.body) + bed_id = request_data.get('bed_id') + direction_id = request_data.get('direction_id') + if not PatientToBed.objects.filter(bed_id=bed_id, date_out=None).exists(): + PatientToBed(direction_id=direction_id, bed_id=bed_id).save() + return status_response(True) + + +@login_required +@group_required("Управления палатами") +def extract_patient_bed(request): + request_data = json.loads(request.body) + direction_pk = request_data.get('patient') + patient = PatientToBed.objects.filter(direction_id=direction_pk, date_out=None).first() + patient.date_out = datetime.datetime.today() + patient.save() + return status_response(True) + + +@login_required +@group_required("Управления палатами") +def get_attending_doctors(request): + request_data = json.loads(request.body) + department_pk = request_data.get('department_pk', -1) + doctors = [{'fio': doctor.get_full_fio(), 'pk': doctor.pk, 'highlight': False, 'short_fio': doctor.get_fio()} for doctor in DoctorProfile.objects.filter(podrazdeleniye_id=department_pk)] + return JsonResponse({"data": doctors}) + + +@login_required +@group_required("Управления палатами") +def update_doctor_to_bed(request): + request_data = json.loads(request.body) + doctor_obj = request_data.get('doctor') + result = PatientToBed.update_doctor(doctor_obj) + return status_response(result) + + +@login_required +@group_required("Управления палатами") +def get_patients_without_bed(request): + request_data = json.loads(request.body) + department_pk = request_data.get('department_pk', -1) + patients = [] + for patient in PatientStationarWithoutBeds.objects.filter(department_id=department_pk): + direction_obj = Napravleniya.objects.get(pk=patient.direction.pk) + ind_card = direction_obj.client + patient_data = ind_card.get_data_individual() + patients.append({"fio": patient_data["fio"], "short_fio": patient_data["short_fio"], "age": patient_data["age"], "sex": patient_data["sex"], "direction_pk": patient.direction_id}) + return JsonResponse({"data": patients}) + + +@login_required +@group_required("Управления палатами") +def save_patient_without_bed(request): + request_data = json.loads(request.body) + department_pk = request_data.get('department_pk') + patient_obj = request_data.get('patient_obj') + PatientStationarWithoutBeds(direction_id=patient_obj["direction_pk"], department_id=department_pk).save() + return status_response(True) + + +@login_required +@group_required("Управления палатами") +def delete_patient_without_bed(request): + request_data = json.loads(request.body) + patient_obj = request_data.get('patient_obj') + PatientStationarWithoutBeds.objects.get(direction_id=patient_obj["direction_pk"]).delete() + return status_response(True) diff --git a/api/urls.py b/api/urls.py index 071d6e719a..4588e26f74 100644 --- a/api/urls.py +++ b/api/urls.py @@ -76,6 +76,7 @@ path('search-param', views.search_param), path('statistic-params-search', views.statistic_params_search), path('analyzers/', include('api.analyzers.urls')), + path('chambers/', include('api.chambers.urls')), path('researches/', include('api.researches.urls')), path('patients/', include('api.patients.urls')), path('directions/', include('api.directions.urls')), diff --git a/clients/models.py b/clients/models.py index 1dce6e8f9f..3260bba0e0 100644 --- a/clients/models.py +++ b/clients/models.py @@ -1289,6 +1289,7 @@ def get_data_individual(self, empty=False, full_empty=False, only_json_serializa docs.append(Document.objects.filter(pk=cd[d])[0]) ind_data['doc'] = docs if not full_empty else [] ind_data['fio'] = self.individual.fio() + ind_data['short_fio'] = self.individual.fio(short=True, dots=True) ind_data['sex'] = self.individual.sex ind_data['family'] = self.individual.family ind_data['name'] = self.individual.name diff --git a/context_processors/utils.py b/context_processors/utils.py index 30e577e565..397b296f12 100644 --- a/context_processors/utils.py +++ b/context_processors/utils.py @@ -144,6 +144,7 @@ def menu(request): "nt": False, "access": ["Управление анализаторами"], }, + {"url": "/ui/chambers", "title": "Палаты", "nt": False, "access": ["Управления палатами"]}, {"url": '/ui/list-wait', "title": "Листы ожидания", "nt": False, "access": ["Лечащий врач", "Оператор лечащего врача"], "module": "l2_list_wait"}, {"url": '/ui/doc-call', "title": "Вызовы врача и заявки", "nt": False, "access": ["Лечащий врач", "Оператор лечащего врача", "Вызов врача"], "module": "l2_doc_call"}, { diff --git a/l2-frontend/src/mainWithRouter.ts b/l2-frontend/src/mainWithRouter.ts index 3fb27227ec..3333efbe40 100644 --- a/l2-frontend/src/mainWithRouter.ts +++ b/l2-frontend/src/mainWithRouter.ts @@ -743,6 +743,17 @@ const router = new Router({ groups: ['Управление анализаторами'], }, }, + { + path: '/ui/chambers', + name: 'ManageChamber', + component: () => import('@/pages/ManageChambers/index.vue'), + meta: { + narrowLayout: true, + title: 'Палаты', + groups: ['Палаты'], + fullPageLayout: true, + }, + }, { path: '/ui/turnovers', name: 'Turnovers', diff --git a/l2-frontend/src/pages/ManageChambers/components/Filters.vue b/l2-frontend/src/pages/ManageChambers/components/Filters.vue new file mode 100644 index 0000000000..2398f1866a --- /dev/null +++ b/l2-frontend/src/pages/ManageChambers/components/Filters.vue @@ -0,0 +1,33 @@ + + + diff --git a/l2-frontend/src/pages/ManageChambers/index.vue b/l2-frontend/src/pages/ManageChambers/index.vue new file mode 100644 index 0000000000..1206742c63 --- /dev/null +++ b/l2-frontend/src/pages/ManageChambers/index.vue @@ -0,0 +1,704 @@ + + + + + diff --git a/podrazdeleniya/admin.py b/podrazdeleniya/admin.py index 00e0fd78cf..c750d8bf87 100644 --- a/podrazdeleniya/admin.py +++ b/podrazdeleniya/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from podrazdeleniya.models import Podrazdeleniya, Room +from podrazdeleniya.models import Podrazdeleniya, Room, Chamber, Bed, PatientToBed, PatientStationarWithoutBeds class PodrazdeleniyaAdmin(admin.ModelAdmin): @@ -15,5 +15,31 @@ class RoomAdmin(admin.ModelAdmin): search_fields = ('title', 'hospital') +class ChamberAdmin(admin.ModelAdmin): + list_display = ('pk', 'podrazdelenie', 'title') + autocomplete_fields = ('podrazdelenie',) + search_fields = ('podrazdelenie', 'title') + + +class BedAdmin(admin.ModelAdmin): + list_display = ('pk', 'chamber', 'bed_number') + autocomplete_fields = ('chamber',) + search_fields = ('chamber', 'bed_number') + + +class PatientToBedAdmin(admin.ModelAdmin): + list_display = ('pk', '__str__', 'bed', 'doctor', 'date_in', 'date_out') + autocomplete_fields = ('direction',) + search_fields = ('direction', 'bed') + + +class PatientStationarWithoutBedsAdmin(admin.ModelAdmin): + list_display = ('__str__', 'direction', 'department') + + +admin.site.register(PatientStationarWithoutBeds, PatientStationarWithoutBedsAdmin) +admin.site.register(PatientToBed, PatientToBedAdmin) +admin.site.register(Bed, BedAdmin) +admin.site.register(Chamber, ChamberAdmin) admin.site.register(Podrazdeleniya, PodrazdeleniyaAdmin) admin.site.register(Room, RoomAdmin) diff --git a/podrazdeleniya/models.py b/podrazdeleniya/models.py index 273e551b5d..92171ca115 100644 --- a/podrazdeleniya/models.py +++ b/podrazdeleniya/models.py @@ -116,3 +116,66 @@ class Meta: verbose_name = 'Кабинет' verbose_name_plural = 'Кабинеты' ordering = ['-id'] + + +class Chamber(models.Model): + podrazdelenie = models.ForeignKey(Podrazdeleniya, on_delete=models.CASCADE) + title = models.CharField(max_length=64, help_text='Название палаты') + + def __str__(self): + return f'{self.podrazdelenie} {self.title}' + + class Meta: + verbose_name = 'Палата' + verbose_name_plural = 'Палаты' + + +class Bed(models.Model): + chamber = models.ForeignKey(Chamber, on_delete=models.CASCADE) + bed_number = models.PositiveSmallIntegerField(help_text="Номер койки") + + def __str__(self): + return f'{self.chamber} - {self.bed_number}' + + class Meta: + verbose_name = 'Койка' + verbose_name_plural = 'Койки' + + +class PatientToBed(models.Model): + direction = models.ForeignKey("directions.Napravleniya", db_index=True, on_delete=models.CASCADE) + doctor = models.ForeignKey("users.DoctorProfile", db_index=True, on_delete=models.CASCADE, null=True) + bed = models.ForeignKey(Bed, db_index=True, on_delete=models.CASCADE) + date_in = models.DateField(auto_now_add=True) + date_out = models.DateField(null=True) + + def __str__(self): + return f'{self.direction.client.individual.fio()}' + + @staticmethod + def update_doctor(doctor_obj): + if doctor_obj["is_assign"]: + doctor = PatientToBed.objects.filter(direction_id=doctor_obj["direction_id"], doctor=None, date_out=None).first() + doctor.doctor_id = doctor_obj["doctor_pk"] + doctor.save() + else: + doctor = PatientToBed.objects.filter(doctor_id=doctor_obj["doctor_pk"], direction_id=doctor_obj["direction_id"], date_out=None).first() + doctor.doctor = None + doctor.save() + return True + + class Meta: + verbose_name = 'Историю коек' + verbose_name_plural = 'История коек' + + +class PatientStationarWithoutBeds(models.Model): + direction = models.ForeignKey("directions.Napravleniya", db_index=True, on_delete=models.CASCADE) + department = models.ForeignKey(Podrazdeleniya, db_index=True, on_delete=models.CASCADE, null=True) + + def __str__(self): + return f'{self.direction.client.individual.fio()}' + + class Meta: + verbose_name = 'Пациент без койки' + verbose_name_plural = 'Пациенты без коек'