pass
This commit is contained in:
parent
ea0223e100
commit
54d5007dae
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)])
|
||||
|
|
@ -0,0 +1 @@
|
|||
default_app_config = 'appa.medicine.apps.MedicineConfig'
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MedicineConfig(AppConfig):
|
||||
|
||||
name = 'appa.medicine'
|
||||
verbose_name = 'Медицина'
|
||||
|
|
@ -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}'
|
||||
|
|
@ -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
|
||||
|
|
@ -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}'
|
||||
}
|
||||
)
|
||||
|
|
@ -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': 'Услуги',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -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': 'Филиалы',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -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='Услуга'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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='Дата и время записи на прием'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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='Адрес для произношения роботом'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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': 'Специальности',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -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(),
|
||||
),
|
||||
]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
.field-medical_service .related-widget-wrapper select {
|
||||
width: 600px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
.field-speciality .related-widget-wrapper select {
|
||||
width: 600px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
5
build.sh
5
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
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
Loading…
Reference in New Issue