diff --git a/appa/admin.py b/appa/admin.py index 6d189f0..49103f6 100644 --- a/appa/admin.py +++ b/appa/admin.py @@ -1,3 +1,4 @@ +from django.contrib.auth.models import User from django import forms from django.contrib import admin @@ -5,13 +6,35 @@ from django.core.checks import messages from django.utils import timezone from django.utils.safestring import mark_safe from django.conf import settings +from constance import config from ace_editor import AceJSONWidget from rangefilter.filters import DateRangeFilterBuilder from .models import * from .admin_forms import * from .admin_filters import * -from .tasks import send_call_request_task +from .tasks import send_call_request_task, update_call_requests_task + + +@admin.register(CallMedicalSpeciality) +class CallMedicalSpecialityAdmin(admin.ModelAdmin): + autocomplete_fields = ['speciality'] + list_display = ('speciality_name', 'name') + search_fields = ('name',) + + @admin.display(description='Специальность') + def speciality_name(self, obj): + return obj.speciality.name + + +@admin.register(CallLPU) +class CallLPUAdmin(admin.ModelAdmin): + autocomplete_fields = ['lpu'] + list_display = ('__str__', 'lpu_short_name', 'name', 'address') + + @admin.display(description='Название') + def lpu_short_name(self, obj): + return obj.lpu.short_name @admin.register(CallRequest) @@ -23,6 +46,7 @@ class CallRequestAdmin(admin.ModelAdmin): 'patient_last_name', 'patient_first_name', 'patient_phone', + 'service_name', 'is_active', ) list_filter = ( @@ -38,7 +62,13 @@ class CallRequestAdmin(admin.ModelAdmin): form = forms.modelform_factory(CallRequest, fields='__all__', widgets={ 'data': AceJSONWidget() }) - actions = ['send_call_request'] + actions = [ + 'send_call_request', + 'delete_call_request', + 'set_is_active_false', + 'set_is_active_true', + 'update_call_requests' + ] list_editable = ('is_active',) readonly_fields = ( 'call_id', @@ -71,10 +101,11 @@ class CallRequestAdmin(admin.ModelAdmin): @admin.action(description='Отправить выбранные записи на обзвон') def send_call_request(self, request, queryset): - now = timezone.now() - if settings.CALL_REQUEST_TIME_START <= now.time() <= settings.CALL_REQUEST_TIME_END: - ids = [item.id for item in queryset] - send_call_request_task.apply_async(args=(ids,)) + if config.CALL_TIME_START <= timezone.now().time() <= config.CALL_TIME_END: + ids = [item.id for item in queryset.filter(request_status=CallRequest.RequestStatus.NOT_SENT)] + if len(ids) > 0: + send_call_request_task.apply_async(args=(ids,)) + self.message_user(request, message=f'Записей отправлено на обзвон: {len(ids)}') else: self.message_user( @@ -84,6 +115,27 @@ class CallRequestAdmin(admin.ModelAdmin): level=messages.ERROR ) + @admin.action(description='Удалить выбранные записи из обзвона') + def delete_call_request(self, request, queryset): + for call_request in queryset: #.filter(request_status=CallRequest.RequestStatus.SENT): + from appa.call_api.api import delete_call_request + delete_call_request(call_request) + + @admin.action(description='Установить активность на "False"') + def set_is_active_false(self, request, queryset): + queryset.update(is_active=False) + self.message_user(request, f'Обновлено записей: {queryset.count()}') + + @admin.action(description='Установить активность на "True"') + def set_is_active_true(self, request, queryset): + queryset.update(is_active=True) + self.message_user(request, f'Обновлено записей: {queryset.count()}') + + @admin.action(description='Синхронизировать список запросов на звонки с МИС') + def update_call_requests(self, request, queryset): + update_call_requests_task.apply_async() + self.message_user(request, f'Задача отправлена на выполнение') + @admin.register(RequestLog) class RequestLogAdmin(admin.ModelAdmin): diff --git a/appa/call_api.py b/appa/call_api/api.py similarity index 68% rename from appa/call_api.py rename to appa/call_api/api.py index 7ca14f9..5824b12 100644 --- a/appa/call_api.py +++ b/appa/call_api/api.py @@ -6,8 +6,8 @@ import requests from django.utils import timezone from constance import config -from .models import CallRequest, RequestLog -from appa.call_api import call_status +from appa.models import CallRequest, RequestLog +from . import call_status def auth_request(): @@ -19,7 +19,6 @@ def auth_request(): ) ) if r.status_code == 200: - print(r.json()) config.ACCESS_TOKEN = r.json()['accessToken'] config.REFRESH_TOKEN = r.json()['refreshToken'] config.ACCESS_TOKEN_EXPIRED = datetime.datetime.now() + datetime.timedelta(minutes=5) @@ -45,6 +44,7 @@ def delete_call_request(call_request: CallRequest, logging=True): headers={'Authorization': f'Bearer {get_token()}'}, hooks=hooks ) + print(r.status_code) if r.status_code == 200: call_request.reset_request() @@ -65,19 +65,32 @@ def get_record(call_request: CallRequest, logging=True): ) if r.status_code == 200: result = r.json() - call_request.call_text_log = result['text_log'] - call_request.call_result = result['call_result'] + if result['call_result_code'] != call_status.STATUS_1: + call_request.call_text_log = result['text_log'] + call_request.call_result = result['call_result'] - if result['confirmed']: - call_request.status = call_request.Status.APPROVED - elif result['call_result_code'] in call_status.STATUS_CANCELLED: - call_request.status = call_request.Status.CANCELED - elif result['call_result_code'] in call_status.STATUS_WITHOUT_ANSWER: - call_request.status = call_request.Status.WITHOUT_ANSWER - else: - call_request.status = call_request.Status.OTHER + if result['call_result_code'] not in [call_status.STATUS_14]: + call_request.request_status = call_request.RequestStatus.COMPLETED - call_request.save() + if result['confirmed']: + call_request.status = call_request.Status.APPROVED + + elif result['call_result_code'] == call_status.STATUS_5: + call_request.status = call_request.Status.CALLBACK + + elif result['call_result_code'] in call_status.STATUS_CANCELLED: + call_request.status = call_request.Status.CANCELED + + from appa.medicine_api.api import cancel_booking + cancel_booking(call_request.booking_id) + + elif result['call_result_code'] in call_status.STATUS_WITHOUT_ANSWER: + call_request.status = call_request.Status.WITHOUT_ANSWER + + else: + call_request.status = call_request.Status.OTHER + + call_request.save() def add_call_request(call_request: CallRequest, logging=True, delete=False): @@ -90,10 +103,10 @@ def add_call_request(call_request: CallRequest, logging=True, delete=False): second_name=call_request.patient_last_name, middle_name=call_request.patient_middle_name, phone_number=call_request.patient_phone, - receipt_date=call_request.date.strftime('%Y-%m-%d'), - receipt_time=call_request.date.strftime('%H:%M'), - doctor_specialisation=call_request.doctor_speciality, - doctor_fullname=call_request.doctor_name, + receipt_date=call_request.booking_date.strftime('%Y-%m-%d'), + receipt_time=call_request.booking_date.strftime('%H:%M'), + doctor_specialisation=call_request.service_name, + note=call_request.organization, filial=call_request.address, ext_id=str(call_request.uid) )]) diff --git a/appa/medicine/__init__.py b/appa/medicine/__init__.py new file mode 100644 index 0000000..e5aea25 --- /dev/null +++ b/appa/medicine/__init__.py @@ -0,0 +1 @@ +default_app_config = 'appa.medicine.apps.MedicineConfig' diff --git a/appa/medicine/admin.py b/appa/medicine/admin.py new file mode 100644 index 0000000..2929e82 --- /dev/null +++ b/appa/medicine/admin.py @@ -0,0 +1,33 @@ + +from django.contrib import admin +from .models import * + + +@admin.register(MedicalSpeciality) +class CallMedicalSpecialityAdmin(admin.ModelAdmin): + search_fields = ['code', 'name'] + list_display = ['code', 'name'] + + def has_add_permission(self, request): + return False + + def has_delete_permission(self, request, obj=None): + return False + + def has_change_permission(self, request, obj=None): + return False + + +@admin.register(LPU) +class LPUAdmin(admin.ModelAdmin): + search_fields = ['full_name'] + list_display = ['full_name'] + + def has_add_permission(self, request): + return False + + def has_delete_permission(self, request, obj=None): + return False + + def has_change_permission(self, request, obj=None): + return False diff --git a/appa/medicine/apps.py b/appa/medicine/apps.py new file mode 100644 index 0000000..4d6b5c8 --- /dev/null +++ b/appa/medicine/apps.py @@ -0,0 +1,8 @@ + +from django.apps import AppConfig + + +class MedicineConfig(AppConfig): + + name = 'appa.medicine' + verbose_name = 'Медицина' diff --git a/appa/medicine/models.py b/appa/medicine/models.py new file mode 100644 index 0000000..fbbbe20 --- /dev/null +++ b/appa/medicine/models.py @@ -0,0 +1,59 @@ + +from django.db import models + +__all__ = [ + 'MedicalSpeciality', + 'MedicalService', + 'LPU' +] + + +class MedicalService(models.Model): + + code = models.CharField(max_length=32, verbose_name='Код') + local_code = models.CharField(max_length=32, null=True, blank=True, verbose_name='Локальный код') + name = models.CharField(max_length=1024, db_index=True, verbose_name='Название') + is_active = models.BooleanField(default=True, db_index=True, verbose_name='Активность') + + class Meta: + verbose_name = 'Услуга' + verbose_name_plural = 'Услуги' + db_table = 'catalog_medicalservice' + managed = False + + def __str__(self): + if self.local_code: + return f'{self.local_code} - {self.code} - {self.name}' + else: + return f'{self.code} - {self.name}' + + +class MedicalSpeciality(models.Model): + + id = models.PositiveIntegerField(primary_key=True, verbose_name='id') + code = models.IntegerField(verbose_name='Код', unique=True) + name = models.CharField(max_length=1024, verbose_name='Название') + is_active = models.BooleanField(default=True, verbose_name='Активность') + + class Meta: + verbose_name = 'Медицинская специальность' + verbose_name_plural = 'Медицинские специальности' + db_table = 'catalog_medicalspeciality' + + def __str__(self): + return '%s (%s)' % (self.name, self.code) + + +class LPU(models.Model): + + code = models.IntegerField(unique=True, primary_key=True, verbose_name='Код учреждения') + full_name = models.CharField(max_length=200, verbose_name='Полное название') + short_name = models.CharField(max_length=200, verbose_name='Короткое название') + + class Meta: + verbose_name = 'ЛПУ' + verbose_name_plural = 'ЛПУ' + db_table = 'lpu' + + def __str__(self): + return f'{self.full_name} {self.code}' diff --git a/appa/medicine/router.py b/appa/medicine/router.py new file mode 100644 index 0000000..f431994 --- /dev/null +++ b/appa/medicine/router.py @@ -0,0 +1,32 @@ + +from django.db.utils import DEFAULT_DB_ALIAS + +APP_NAME = 'medicine' +DATABASE_NAME = 'medicine' + + +class MedicineRouter: + + def db_for_read(self, model, **hints): + if model._meta.app_label in [APP_NAME]: + return DATABASE_NAME + return DEFAULT_DB_ALIAS + + def db_for_write(self, model, **hints): + if model._meta.app_label in [APP_NAME]: + return DATABASE_NAME + return DEFAULT_DB_ALIAS + + def allow_migrate(self, db, app_label, model_name=None, **hints): + if db == DATABASE_NAME: + return False + + if app_label == APP_NAME: + return False + + def allow_relation(self, obj1, obj2, **hints): + app_label_obj1 = obj1.__class__._meta.app_label + app_label_obj2 = obj2.__class__._meta.app_label + + if app_label_obj1 == APP_NAME or app_label_obj2 == APP_NAME: + return True diff --git a/appa/medicine_api/__init__.py b/appa/medicine_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/appa/medicine_api/api.py b/appa/medicine_api/api.py new file mode 100644 index 0000000..b69d7ed --- /dev/null +++ b/appa/medicine_api/api.py @@ -0,0 +1,54 @@ +import datetime + +import requests +from constance import config + + +def get_bookings(date): + bookings = [] + limit = 50 + offset = 0 + while True: + r = requests.get( + f'{config.MEDICINE_HOST}api/1/booking/', + params={ + 'date_start__date': date.strftime('%d.%m.%Y'), + 'patient__phone_mobile__isnull': False, + 'patient__isnull': False, + 'status': 1, + 'limit': limit, + 'offset': offset, + }, + headers={ + 'Authorization': f'Token {config.MEDICINE_TOKEN}' + } + ) + if r.status_code == 200: + response = r.json() + for booking in response['results']: + detail_request = requests.get( + f'{config.MEDICINE_HOST}api/1/booking/{booking["id"]}/', + headers = { + 'Authorization': f'Token {config.MEDICINE_TOKEN}' + } + ) + bookings.append(detail_request.json()) + + if response['next'] is None: + break + + offset += 50 + + return bookings + + +def cancel_booking(booking_id): + r = requests.post( + f'{config.MEDICINE_HOST}api/1/booking/{booking_id}/failed/', + data={ + 'failed_reason': 'CANCELED' + }, + headers={ + 'Authorization': f'Token {config.MEDICINE_TOKEN}' + } + ) diff --git a/appa/migrations/0004_auto_20250225_1842.py b/appa/migrations/0004_auto_20250225_1842.py new file mode 100644 index 0000000..69bb33e --- /dev/null +++ b/appa/migrations/0004_auto_20250225_1842.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2 on 2025-02-25 18:42 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('medicine', '__first__'), + ('appa', '0003_callrequest_call_result'), + ] + + operations = [ + migrations.AddField( + model_name='callrequest', + name='booking_id', + field=models.PositiveIntegerField(null=True, verbose_name='ID записи на прием'), + ), + migrations.AlterField( + model_name='callrequest', + name='call_result', + field=models.CharField(blank=True, editable=False, max_length=100, null=True, verbose_name='Результат'), + ), + migrations.AlterField( + model_name='callrequest', + name='status', + field=models.CharField(choices=[('PENDING', 'Обзвон еще не состоялся'), ('APPROVED', 'Прием подтвержден'), ('CANCELED', 'Прием отменен'), ('WITHOUT_ANSWER', 'Не дозвонились'), ('OTHER', 'Другая ошибка')], default='PENDING', max_length=20, verbose_name='Статус приема'), + ), + migrations.CreateModel( + name='CallMedicalService', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='Произношение роботом')), + ('medical_service', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='medicine.medicalservice', verbose_name='Услуга (МИС)')), + ], + options={ + 'verbose_name': 'Услуга', + 'verbose_name_plural': 'Услуги', + }, + ), + ] diff --git a/appa/migrations/0005_calllpu.py b/appa/migrations/0005_calllpu.py new file mode 100644 index 0000000..280c2dc --- /dev/null +++ b/appa/migrations/0005_calllpu.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2 on 2025-02-25 18:53 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('medicine', '__first__'), + ('appa', '0004_auto_20250225_1842'), + ] + + operations = [ + migrations.CreateModel( + name='CallLPU', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='Произношение роботом')), + ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Адрес')), + ('lpu', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='medicine.lpu', verbose_name='ЛПУ')), + ], + options={ + 'verbose_name': 'Филиал', + 'verbose_name_plural': 'Филиалы', + }, + ), + ] diff --git a/appa/migrations/0006_auto_20250225_2042.py b/appa/migrations/0006_auto_20250225_2042.py new file mode 100644 index 0000000..2c39c7e --- /dev/null +++ b/appa/migrations/0006_auto_20250225_2042.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2 on 2025-02-25 20:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appa', '0005_calllpu'), + ] + + operations = [ + migrations.AlterField( + model_name='calllpu', + name='address', + field=models.TextField(blank=True, max_length=200, null=True, verbose_name='Адрес'), + ), + migrations.AlterField( + model_name='callrequest', + name='address', + field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Адрес'), + ), + migrations.AlterField( + model_name='callrequest', + name='doctor_speciality', + field=models.CharField(max_length=200, null=True, verbose_name='Специальность врача'), + ), + migrations.AlterField( + model_name='callrequest', + name='service_name', + field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Услуга'), + ), + ] diff --git a/appa/migrations/0007_callrequest_booking_date.py b/appa/migrations/0007_callrequest_booking_date.py new file mode 100644 index 0000000..90c0179 --- /dev/null +++ b/appa/migrations/0007_callrequest_booking_date.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2025-02-26 15:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appa', '0006_auto_20250225_2042'), + ] + + operations = [ + migrations.AddField( + model_name='callrequest', + name='booking_date', + field=models.DateTimeField(null=True, verbose_name='Дата и время записи на прием'), + ), + ] diff --git a/appa/migrations/0008_auto_20250227_1504.py b/appa/migrations/0008_auto_20250227_1504.py new file mode 100644 index 0000000..240fac7 --- /dev/null +++ b/appa/migrations/0008_auto_20250227_1504.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2 on 2025-02-27 15:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appa', '0007_callrequest_booking_date'), + ] + + operations = [ + migrations.AddField( + model_name='callrequest', + name='organization', + field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Организация'), + ), + migrations.AlterField( + model_name='calllpu', + name='address', + field=models.TextField(blank=True, max_length=200, null=True, verbose_name='Адрес для произношения роботом'), + ), + ] diff --git a/appa/migrations/0009_auto_20250301_2028.py b/appa/migrations/0009_auto_20250301_2028.py new file mode 100644 index 0000000..c8d152a --- /dev/null +++ b/appa/migrations/0009_auto_20250301_2028.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2 on 2025-03-01 20:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('medicine', '__first__'), + ('appa', '0008_auto_20250227_1504'), + ] + + operations = [ + migrations.AlterField( + model_name='callrequest', + name='request_status', + field=models.CharField(choices=[('PENDING', 'Не отправлен'), ('APPROVED', 'Отправлен'), ('ERROR', 'Ошибка'), ('SERVICE_UNAVAILABLE', 'Сервис недоступен'), ('COMPLETED', 'Завершен')], default='PENDING', max_length=20, verbose_name='Статус запроса'), + ), + migrations.AlterField( + model_name='callrequest', + name='status', + field=models.CharField(choices=[('PENDING', 'Обзвон еще не состоялся'), ('APPROVED', 'Прием подтвержден'), ('CANCELED', 'Прием отменен'), ('CALLBACK', 'Перезвонить'), ('WITHOUT_ANSWER', 'Не дозвонились'), ('OTHER', 'Другая ошибка')], default='PENDING', max_length=20, verbose_name='Статус приема'), + ), + migrations.CreateModel( + name='CallMedicalSpeciality', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='Произношение роботом')), + ('speciality', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='medicine.medicalspeciality', verbose_name='Специальность (МИС)')), + ], + options={ + 'verbose_name': 'Специальность', + 'verbose_name_plural': 'Специальности', + }, + ), + ] diff --git a/appa/migrations/0010_auto_20250302_0922.py b/appa/migrations/0010_auto_20250302_0922.py new file mode 100644 index 0000000..a3f645e --- /dev/null +++ b/appa/migrations/0010_auto_20250302_0922.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2 on 2025-03-02 09:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('appa', '0009_auto_20250301_2028'), + ] + + operations = [ + migrations.AlterField( + model_name='callrequest', + name='booking_id', + field=models.PositiveIntegerField(null=True, unique=True, verbose_name='ID записи на прием'), + ), + migrations.AlterUniqueTogether( + name='callrequest', + unique_together=set(), + ), + ] diff --git a/appa/models.py b/appa/models.py index 1bc6a1c..7551c0c 100644 --- a/appa/models.py +++ b/appa/models.py @@ -5,17 +5,63 @@ from django.db import models from .managers import CallRequestManager __all__ = [ + 'CallMedicalService', + 'CallMedicalSpeciality', + 'CallLPU', 'CallRequest', 'RequestLog' ] +class CallMedicalService(models.Model): + + medical_service = models.ForeignKey('medicine.MedicalService', db_constraint=False, on_delete=models.CASCADE, + verbose_name='Услуга (МИС)') + name = models.CharField(max_length=100, verbose_name='Произношение роботом') + + class Meta: + verbose_name = 'Услуга' + verbose_name_plural = 'Услуги' + + def __str__(self): + return f'{self.medical_service.name} ← {self.name}' + + +class CallMedicalSpeciality(models.Model): + + speciality = models.ForeignKey('medicine.MedicalSpeciality', db_constraint=False, on_delete=models.CASCADE, + verbose_name='Специальность (МИС)') + name = models.CharField(max_length=100, verbose_name='Произношение роботом') + + class Meta: + verbose_name = 'Специальность' + verbose_name_plural = 'Специальности' + + def __str__(self): + return f'{self.name}' + + +class CallLPU(models.Model): + + lpu = models.ForeignKey('medicine.LPU', db_constraint=False, on_delete=models.CASCADE, verbose_name='ЛПУ') + name = models.CharField(max_length=100, verbose_name='Произношение роботом') + address = models.TextField(max_length=200, null=True, blank=True, verbose_name='Адрес для произношения роботом') + + class Meta: + verbose_name = 'Филиал' + verbose_name_plural = 'Филиалы' + + def __str__(self): + return self.lpu.full_name + + class CallRequest(models.Model): class Status(models.TextChoices): PENDING = 'PENDING', 'Обзвон еще не состоялся' APPROVED = 'APPROVED', 'Прием подтвержден' CANCELED = 'CANCELED', 'Прием отменен' + CALLBACK = 'CALLBACK', 'Перезвонить' WITHOUT_ANSWER = 'WITHOUT_ANSWER', 'Не дозвонились' OTHER = 'OTHER', 'Другая ошибка' @@ -23,7 +69,9 @@ class CallRequest(models.Model): Status.PENDING: '#2D72D2', Status.APPROVED: '#238551', Status.CANCELED: '#CD4246', + Status.CALLBACK: '#935610', Status.WITHOUT_ANSWER: '#866103', + Status.OTHER: '#5A701A' } class RequestStatus(models.TextChoices): @@ -31,12 +79,14 @@ class CallRequest(models.Model): SENT = 'APPROVED', 'Отправлен' ERROR = 'ERROR', 'Ошибка' SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE', 'Сервис недоступен' + COMPLETED = 'COMPLETED', 'Завершен' REQUEST_STATUS_COLOR = { RequestStatus.NOT_SENT: '#2D72D2', RequestStatus.SENT: '#238551', RequestStatus.ERROR: '#CD4246', RequestStatus.SERVICE_UNAVAILABLE: '#7961DB', + RequestStatus.COMPLETED: '#147EB3', } uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) @@ -45,16 +95,18 @@ class CallRequest(models.Model): request_status = models.CharField(max_length=20, choices=RequestStatus.choices, default=RequestStatus.NOT_SENT, verbose_name='Статус запроса') date = models.DateField(verbose_name='Дата') - booking_id = models.PositiveIntegerField(null=True, verbose_name='ID записи на прием') + booking_id = models.PositiveIntegerField(unique=True, null=True, verbose_name='ID записи на прием') + booking_date = models.DateTimeField(null=True, verbose_name='Дата и время записи на прием') patient_id = models.IntegerField(verbose_name='ID пациента') patient_first_name = models.CharField(max_length=200, verbose_name='Имя') patient_last_name = models.CharField(max_length=200, verbose_name='Фамилия') patient_middle_name = models.CharField(max_length=200, null=True, blank=True, verbose_name='Отчество') patient_phone = models.CharField(max_length=100, verbose_name='Номер телефона') doctor_name = models.CharField(max_length=100, null=True, verbose_name='Врач') - doctor_speciality = models.CharField(max_length=100, null=True, verbose_name='Специальность врача') - service_name = models.CharField(max_length=100, null=True, blank=True, verbose_name='Услуга') - address = models.CharField(max_length=100, null=True, blank=True, verbose_name='Адрес') + doctor_speciality = models.CharField(max_length=200, null=True, verbose_name='Специальность врача') + service_name = models.CharField(max_length=200, null=True, blank=True, verbose_name='Услуга') + organization = models.CharField(max_length=200, null=True, blank=True, verbose_name='Организация') + address = models.CharField(max_length=200, null=True, blank=True, verbose_name='Адрес') data = models.JSONField(default=dict, blank=True, verbose_name='Данные') is_active = models.BooleanField(default=True, verbose_name='Активность') @@ -69,9 +121,6 @@ class CallRequest(models.Model): class Meta: verbose_name = 'Запрос на звонок' verbose_name_plural = 'Запросы на звонки' - unique_together = ( - ('date', 'patient_id'), - ) objects = CallRequestManager() @@ -88,6 +137,7 @@ class CallRequest(models.Model): self.call_id = None self.call_data = dict() self.request_status = CallRequest.RequestStatus.NOT_SENT + self.status = CallRequest.Status.PENDING self.request_time = None self.response_status_code = None self.response_message = None diff --git a/appa/settings.py b/appa/settings.py index 3ba5521..b342bc3 100644 --- a/appa/settings.py +++ b/appa/settings.py @@ -47,6 +47,7 @@ INSTALLED_APPS = [ 'rangefilter', 'appa', + 'appa.medicine', ] MIDDLEWARE = [ @@ -92,7 +93,7 @@ DATABASES = { }, 'medicine': { 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'medicine', + 'NAME': env.str('POSTGRES_MEDICINE_DB', 'medicine'), 'USER': POSTGRES_USER, 'PASSWORD': POSTGRES_PASSWORD, 'HOST': POSTGRES_HOST, @@ -143,11 +144,10 @@ CONSTANCE_ADDITIONAL_FIELDS = { 'widget': 'django.forms.TextInput', 'widget_kwargs': dict(attrs={'size': 60}), 'required': False - }], + }] } CONSTANCE_CONFIG = { - 'HOST': ('https://amniss-ai.ru/api/v.2/', 'Хост', 'char'), 'ENTRYPOINT_ADD_RECORDS': ('https://amniss-ai.ru/api/v.2/records/{scenarioId}', 'Адрес для добавления записей', 'char'), 'ENTRYPOINT_AUTH': ('https://amniss-ai.ru/api/auth', 'Адрес для авторизации', 'char'), 'ENTRYPOINT_DELETE': ('https://amniss-ai.ru/api/v.1/records/delete/%s', 'Адрес для удаления', 'char'), @@ -163,6 +163,16 @@ CONSTANCE_CONFIG = { 'Access token expired', datetime.datetime ), + 'CALL_TIME_START': ( + datetime.time(9, 30), + 'Время старта обзвона', + datetime.time + ), + 'CALL_TIME_END': ( + datetime.time(18, 30), + 'Время окончания обзвона', + datetime.time + ), 'MEDICINE_TOKEN': ('', 'Токен', 'char'), 'MEDICINE_HOST': ('http://medicine-app/', 'Хост', 'char'), @@ -170,18 +180,19 @@ CONSTANCE_CONFIG = { CONSTANCE_CONFIG_FIELDSETS = ( ('API', ( - 'HOST', + 'IS_ENABLED', + 'CALL_TIME_START', + 'CALL_TIME_END', + 'SCENARIO_ID', + 'USERNAME', + 'PASSWORD', 'ENTRYPOINT_AUTH', 'ENTRYPOINT_ADD_RECORDS', 'ENTRYPOINT_DELETE', 'ENTRYPOINT_RECORD', - 'SCENARIO_ID', - 'USERNAME', - 'PASSWORD', - 'IS_ENABLED', 'ACCESS_TOKEN', + 'REFRESH_TOKEN', 'ACCESS_TOKEN_EXPIRED', - 'REFRESH_TOKEN' )), ('МИС', ( 'MEDICINE_HOST', @@ -191,13 +202,23 @@ CONSTANCE_CONFIG_FIELDSETS = ( REQUEST_TIMEOUT = 5 -REDIS_CELERY_DB = env.str('REDIS_CELERY_DB', '3') +REDIS_CELERY_DB = env.str('REDIS_CELERY_DB', '7') BROKER_URL = f'redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CELERY_DB}' CELERY_DEFAULT_QUEUE = 'default' CELERY_ENABLE_UTC = True CELERY_IGNORE_RESULT = True CELERYBEAT_SCHEDULE = { + 'update_call_requests_task': { + 'task': 'appa.tasks.update_call_requests_task', + 'schedule': crontab(hour='23', minute='0'), + 'options': {'queue': CELERY_DEFAULT_QUEUE} + }, + 'check_call_requests_task': { + 'task': 'appa.tasks.check_call_requests_task', + 'schedule': crontab(minute='*/1'), + 'options': {'queue': CELERY_DEFAULT_QUEUE} + } } DATE_FORMAT = 'd.m.Y' @@ -206,6 +227,10 @@ DATETIME_FORMAT = 'd.m.Y H:i' CALL_REQUEST_TIME_START = datetime.time(9, 0) CALL_REQUEST_TIME_END = datetime.time(21, 0) +DATABASE_ROUTERS = [ + 'appa.medicine.router.MedicineRouter', +] + if DEBUG: AUTH_PASSWORD_VALIDATORS = [] SESSION_COOKIE_NAME = 'dev-medicine-call' diff --git a/appa/tasks.py b/appa/tasks.py index 79edb38..c423b0e 100644 --- a/appa/tasks.py +++ b/appa/tasks.py @@ -1,10 +1,11 @@ +import pytz import datetime -import requests from constance import config from appa.celery import app from appa.models import * -from appa.call_api import add_call_request +from appa.call_api import api as call_api +from appa.medicine_api import api as medicine_api @app.task(bind=True, acks_late=True) @@ -24,49 +25,54 @@ def send_call_request_task(self, ids=None): ) for call_request in call_requests: - add_call_request(call_request) + call_api.add_call_request(call_request) @app.task(bind=True, acks_late=True) -def update_call_requests(self): - for add_days in [1, 2]: - booking_date = datetime.date.today() + datetime.timedelta(days=add_days) - CallRequest.objects.filter(date=booking_date).delete() +def update_call_requests_task(self): + call_lpu = {call_lpu.lpu_id: call_lpu for call_lpu in CallLPU.objects.all()} + call_specialities = {medical_speciality.speciality_id: medical_speciality + for medical_speciality in CallMedicalSpeciality.objects.all()} - for page in range(1): - r = requests.get( - f'{config.MEDICINE_HOST}api/1/booking/', - params={ - 'date_start__date': booking_date.strftime('%d.%m.%Y'), - 'patient__phone_mobile__isnull': False, - 'patient__isnull': False, - 'page': page, - 'status': 1, - 'limit': 50 - }, - headers={ - 'Authorization': f'Token {config.MEDICINE_TOKEN}' - } - ) - if r.status_code == 200: - response = r.json() - results = response['results'] - for booking in results: - CallRequest.objects.update_or_create( - defaults={ - 'date': booking['date'], - 'patient_id': booking['patient']['id'], - 'patient_first_name': booking['patient']['first_name'], - 'patient_last_name': booking['patient']['last_name'], - 'patient_middle_name': booking['patient']['middle_name'], - 'patient_phone': '7' + booking['patient']['phone_mobile'], - 'doctor_name': '', - 'doctor_speciality': '', - 'service_name': '', - 'address': '' - }, - booking_id=booking['id'], - ) + now = datetime.datetime.now().astimezone(pytz.timezone('Asia/Vladivostok')) + bookings = medicine_api.get_bookings(now.date() + datetime.timedelta(days=1)) + for booking in bookings: + lpu = None + speciality = None + if booking['lpu']['code'] in call_lpu: + lpu = call_lpu[booking['lpu']['code']] - if response['next'] is None: - break + if booking['user'] is None: + continue + + if booking['user']['doctor']['speciality'] in call_specialities: + speciality = call_specialities[booking['user']['doctor']['speciality']] + + if lpu is None or speciality is None: + continue + + CallRequest.objects.update_or_create( + defaults=dict( + date=datetime.datetime.strptime(booking['date_start'].split('T')[0], '%Y-%m-%d').date(), + booking_date=booking['date_start'], + patient_id=booking['patient']['id'], + patient_first_name=booking['patient']['first_name'], + patient_last_name=booking['patient']['last_name'], + patient_middle_name=booking['patient']['middle_name'], + patient_phone='7' + booking['patient']['phone_mobile'], + doctor_name=booking['user']['display'], + doctor_speciality=booking['user']['doctor']['speciality_display'], + service_name=speciality.name, + organization=lpu.name, + address=lpu.address, + ), + booking_id=booking['id'], + ) + + +@app.task(bind=True, acks_late=True) +def check_call_requests_task(self): + for call_request in CallRequest.objects.filter( + request_status=CallRequest.RequestStatus.SENT + ): + call_api.get_record(call_request) diff --git a/appa/templates/admin/appa/callmedicalservice/change_form.html b/appa/templates/admin/appa/callmedicalservice/change_form.html new file mode 100644 index 0000000..7511ec4 --- /dev/null +++ b/appa/templates/admin/appa/callmedicalservice/change_form.html @@ -0,0 +1,10 @@ +{% extends "admin/change_form.html" %} + +{% block extrahead %} +{{ block.super }} + +{% endblock %} \ No newline at end of file diff --git a/appa/templates/admin/appa/callmedicalspeciality/change_form.html b/appa/templates/admin/appa/callmedicalspeciality/change_form.html new file mode 100644 index 0000000..4b40536 --- /dev/null +++ b/appa/templates/admin/appa/callmedicalspeciality/change_form.html @@ -0,0 +1,10 @@ +{% extends "admin/change_form.html" %} + +{% block extrahead %} +{{ block.super }} + +{% endblock %} \ No newline at end of file diff --git a/build.sh b/build.sh index 6bfa730..ad0e189 100755 --- a/build.sh +++ b/build.sh @@ -2,6 +2,5 @@ set -e -(cd client && npm run build) -docker build --tag docker.med-logic.ru/medicine-damask:latest . -docker push docker.med-logic.ru/medicine-damask:latest +docker build --platform=linux/amd64 --tag docker.med-logic.ru/medicine-call:latest . +docker push docker.med-logic.ru/medicine-call:latest diff --git a/local-server.sh b/local-server.sh index 579777c..ecf1daa 100755 --- a/local-server.sh +++ b/local-server.sh @@ -21,6 +21,7 @@ docker run --rm \ --master \ --py-autoreload 1 \ --disable-write-exception \ + --disable-logging \ --buffer-size 32768 \ --http-timeout 30 \ --check-static /app/