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/