This commit is contained in:
Ilya Mukhortov 2025-03-03 19:46:57 +10:00
parent ea0223e100
commit 54d5007dae
23 changed files with 643 additions and 87 deletions

View File

@ -1,3 +1,4 @@
from django.contrib.auth.models import User
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
@ -5,13 +6,35 @@ from django.core.checks import messages
from django.utils import timezone from django.utils import timezone
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.conf import settings from django.conf import settings
from constance import config
from ace_editor import AceJSONWidget from ace_editor import AceJSONWidget
from rangefilter.filters import DateRangeFilterBuilder from rangefilter.filters import DateRangeFilterBuilder
from .models import * from .models import *
from .admin_forms import * from .admin_forms import *
from .admin_filters 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) @admin.register(CallRequest)
@ -23,6 +46,7 @@ class CallRequestAdmin(admin.ModelAdmin):
'patient_last_name', 'patient_last_name',
'patient_first_name', 'patient_first_name',
'patient_phone', 'patient_phone',
'service_name',
'is_active', 'is_active',
) )
list_filter = ( list_filter = (
@ -38,7 +62,13 @@ class CallRequestAdmin(admin.ModelAdmin):
form = forms.modelform_factory(CallRequest, fields='__all__', widgets={ form = forms.modelform_factory(CallRequest, fields='__all__', widgets={
'data': AceJSONWidget() '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',) list_editable = ('is_active',)
readonly_fields = ( readonly_fields = (
'call_id', 'call_id',
@ -71,10 +101,11 @@ class CallRequestAdmin(admin.ModelAdmin):
@admin.action(description='Отправить выбранные записи на обзвон') @admin.action(description='Отправить выбранные записи на обзвон')
def send_call_request(self, request, queryset): def send_call_request(self, request, queryset):
now = timezone.now() if config.CALL_TIME_START <= timezone.now().time() <= config.CALL_TIME_END:
if settings.CALL_REQUEST_TIME_START <= now.time() <= settings.CALL_REQUEST_TIME_END: ids = [item.id for item in queryset.filter(request_status=CallRequest.RequestStatus.NOT_SENT)]
ids = [item.id for item in queryset] if len(ids) > 0:
send_call_request_task.apply_async(args=(ids,)) send_call_request_task.apply_async(args=(ids,))
self.message_user(request, message=f'Записей отправлено на обзвон: {len(ids)}') self.message_user(request, message=f'Записей отправлено на обзвон: {len(ids)}')
else: else:
self.message_user( self.message_user(
@ -84,6 +115,27 @@ class CallRequestAdmin(admin.ModelAdmin):
level=messages.ERROR 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) @admin.register(RequestLog)
class RequestLogAdmin(admin.ModelAdmin): class RequestLogAdmin(admin.ModelAdmin):

View File

@ -6,8 +6,8 @@ import requests
from django.utils import timezone from django.utils import timezone
from constance import config from constance import config
from .models import CallRequest, RequestLog from appa.models import CallRequest, RequestLog
from appa.call_api import call_status from . import call_status
def auth_request(): def auth_request():
@ -19,7 +19,6 @@ def auth_request():
) )
) )
if r.status_code == 200: if r.status_code == 200:
print(r.json())
config.ACCESS_TOKEN = r.json()['accessToken'] config.ACCESS_TOKEN = r.json()['accessToken']
config.REFRESH_TOKEN = r.json()['refreshToken'] config.REFRESH_TOKEN = r.json()['refreshToken']
config.ACCESS_TOKEN_EXPIRED = datetime.datetime.now() + datetime.timedelta(minutes=5) 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()}'}, headers={'Authorization': f'Bearer {get_token()}'},
hooks=hooks hooks=hooks
) )
print(r.status_code)
if r.status_code == 200: if r.status_code == 200:
call_request.reset_request() call_request.reset_request()
@ -65,15 +65,28 @@ def get_record(call_request: CallRequest, logging=True):
) )
if r.status_code == 200: if r.status_code == 200:
result = r.json() result = r.json()
if result['call_result_code'] != call_status.STATUS_1:
call_request.call_text_log = result['text_log'] call_request.call_text_log = result['text_log']
call_request.call_result = result['call_result'] call_request.call_result = result['call_result']
if result['call_result_code'] not in [call_status.STATUS_14]:
call_request.request_status = call_request.RequestStatus.COMPLETED
if result['confirmed']: if result['confirmed']:
call_request.status = call_request.Status.APPROVED 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: elif result['call_result_code'] in call_status.STATUS_CANCELLED:
call_request.status = call_request.Status.CANCELED 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: elif result['call_result_code'] in call_status.STATUS_WITHOUT_ANSWER:
call_request.status = call_request.Status.WITHOUT_ANSWER call_request.status = call_request.Status.WITHOUT_ANSWER
else: else:
call_request.status = call_request.Status.OTHER call_request.status = call_request.Status.OTHER
@ -90,10 +103,10 @@ def add_call_request(call_request: CallRequest, logging=True, delete=False):
second_name=call_request.patient_last_name, second_name=call_request.patient_last_name,
middle_name=call_request.patient_middle_name, middle_name=call_request.patient_middle_name,
phone_number=call_request.patient_phone, phone_number=call_request.patient_phone,
receipt_date=call_request.date.strftime('%Y-%m-%d'), receipt_date=call_request.booking_date.strftime('%Y-%m-%d'),
receipt_time=call_request.date.strftime('%H:%M'), receipt_time=call_request.booking_date.strftime('%H:%M'),
doctor_specialisation=call_request.doctor_speciality, doctor_specialisation=call_request.service_name,
doctor_fullname=call_request.doctor_name, note=call_request.organization,
filial=call_request.address, filial=call_request.address,
ext_id=str(call_request.uid) ext_id=str(call_request.uid)
)]) )])

View File

@ -0,0 +1 @@
default_app_config = 'appa.medicine.apps.MedicineConfig'

33
appa/medicine/admin.py Normal file
View File

@ -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

8
appa/medicine/apps.py Normal file
View File

@ -0,0 +1,8 @@
from django.apps import AppConfig
class MedicineConfig(AppConfig):
name = 'appa.medicine'
verbose_name = 'Медицина'

59
appa/medicine/models.py Normal file
View File

@ -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}'

32
appa/medicine/router.py Normal file
View File

@ -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

View File

54
appa/medicine_api/api.py Normal file
View File

@ -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}'
}
)

View File

@ -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': 'Услуги',
},
),
]

View File

@ -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': 'Филиалы',
},
),
]

View File

@ -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='Услуга'),
),
]

View File

@ -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='Дата и время записи на прием'),
),
]

View File

@ -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='Адрес для произношения роботом'),
),
]

View File

@ -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': 'Специальности',
},
),
]

View File

@ -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(),
),
]

View File

@ -5,17 +5,63 @@ from django.db import models
from .managers import CallRequestManager from .managers import CallRequestManager
__all__ = [ __all__ = [
'CallMedicalService',
'CallMedicalSpeciality',
'CallLPU',
'CallRequest', 'CallRequest',
'RequestLog' '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 CallRequest(models.Model):
class Status(models.TextChoices): class Status(models.TextChoices):
PENDING = 'PENDING', 'Обзвон еще не состоялся' PENDING = 'PENDING', 'Обзвон еще не состоялся'
APPROVED = 'APPROVED', 'Прием подтвержден' APPROVED = 'APPROVED', 'Прием подтвержден'
CANCELED = 'CANCELED', 'Прием отменен' CANCELED = 'CANCELED', 'Прием отменен'
CALLBACK = 'CALLBACK', 'Перезвонить'
WITHOUT_ANSWER = 'WITHOUT_ANSWER', 'Не дозвонились' WITHOUT_ANSWER = 'WITHOUT_ANSWER', 'Не дозвонились'
OTHER = 'OTHER', 'Другая ошибка' OTHER = 'OTHER', 'Другая ошибка'
@ -23,7 +69,9 @@ class CallRequest(models.Model):
Status.PENDING: '#2D72D2', Status.PENDING: '#2D72D2',
Status.APPROVED: '#238551', Status.APPROVED: '#238551',
Status.CANCELED: '#CD4246', Status.CANCELED: '#CD4246',
Status.CALLBACK: '#935610',
Status.WITHOUT_ANSWER: '#866103', Status.WITHOUT_ANSWER: '#866103',
Status.OTHER: '#5A701A'
} }
class RequestStatus(models.TextChoices): class RequestStatus(models.TextChoices):
@ -31,12 +79,14 @@ class CallRequest(models.Model):
SENT = 'APPROVED', 'Отправлен' SENT = 'APPROVED', 'Отправлен'
ERROR = 'ERROR', 'Ошибка' ERROR = 'ERROR', 'Ошибка'
SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE', 'Сервис недоступен' SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE', 'Сервис недоступен'
COMPLETED = 'COMPLETED', 'Завершен'
REQUEST_STATUS_COLOR = { REQUEST_STATUS_COLOR = {
RequestStatus.NOT_SENT: '#2D72D2', RequestStatus.NOT_SENT: '#2D72D2',
RequestStatus.SENT: '#238551', RequestStatus.SENT: '#238551',
RequestStatus.ERROR: '#CD4246', RequestStatus.ERROR: '#CD4246',
RequestStatus.SERVICE_UNAVAILABLE: '#7961DB', RequestStatus.SERVICE_UNAVAILABLE: '#7961DB',
RequestStatus.COMPLETED: '#147EB3',
} }
uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) 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, request_status = models.CharField(max_length=20, choices=RequestStatus.choices, default=RequestStatus.NOT_SENT,
verbose_name='Статус запроса') verbose_name='Статус запроса')
date = models.DateField(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_id = models.IntegerField(verbose_name='ID пациента')
patient_first_name = models.CharField(max_length=200, verbose_name='Имя') patient_first_name = models.CharField(max_length=200, verbose_name='Имя')
patient_last_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_middle_name = models.CharField(max_length=200, null=True, blank=True, verbose_name='Отчество')
patient_phone = models.CharField(max_length=100, verbose_name='Номер телефона') patient_phone = models.CharField(max_length=100, verbose_name='Номер телефона')
doctor_name = models.CharField(max_length=100, null=True, verbose_name='Врач') doctor_name = models.CharField(max_length=100, null=True, verbose_name='Врач')
doctor_speciality = models.CharField(max_length=100, null=True, verbose_name='Специальность врача') doctor_speciality = models.CharField(max_length=200, null=True, verbose_name='Специальность врача')
service_name = models.CharField(max_length=100, null=True, blank=True, verbose_name='Услуга') service_name = models.CharField(max_length=200, null=True, blank=True, verbose_name='Услуга')
address = models.CharField(max_length=100, 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='Данные') data = models.JSONField(default=dict, blank=True, verbose_name='Данные')
is_active = models.BooleanField(default=True, verbose_name='Активность') is_active = models.BooleanField(default=True, verbose_name='Активность')
@ -69,9 +121,6 @@ class CallRequest(models.Model):
class Meta: class Meta:
verbose_name = 'Запрос на звонок' verbose_name = 'Запрос на звонок'
verbose_name_plural = 'Запросы на звонки' verbose_name_plural = 'Запросы на звонки'
unique_together = (
('date', 'patient_id'),
)
objects = CallRequestManager() objects = CallRequestManager()
@ -88,6 +137,7 @@ class CallRequest(models.Model):
self.call_id = None self.call_id = None
self.call_data = dict() self.call_data = dict()
self.request_status = CallRequest.RequestStatus.NOT_SENT self.request_status = CallRequest.RequestStatus.NOT_SENT
self.status = CallRequest.Status.PENDING
self.request_time = None self.request_time = None
self.response_status_code = None self.response_status_code = None
self.response_message = None self.response_message = None

View File

@ -47,6 +47,7 @@ INSTALLED_APPS = [
'rangefilter', 'rangefilter',
'appa', 'appa',
'appa.medicine',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -92,7 +93,7 @@ DATABASES = {
}, },
'medicine': { 'medicine': {
'ENGINE': 'django.db.backends.postgresql', 'ENGINE': 'django.db.backends.postgresql',
'NAME': 'medicine', 'NAME': env.str('POSTGRES_MEDICINE_DB', 'medicine'),
'USER': POSTGRES_USER, 'USER': POSTGRES_USER,
'PASSWORD': POSTGRES_PASSWORD, 'PASSWORD': POSTGRES_PASSWORD,
'HOST': POSTGRES_HOST, 'HOST': POSTGRES_HOST,
@ -143,11 +144,10 @@ CONSTANCE_ADDITIONAL_FIELDS = {
'widget': 'django.forms.TextInput', 'widget': 'django.forms.TextInput',
'widget_kwargs': dict(attrs={'size': 60}), 'widget_kwargs': dict(attrs={'size': 60}),
'required': False 'required': False
}], }]
} }
CONSTANCE_CONFIG = { 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_ADD_RECORDS': ('https://amniss-ai.ru/api/v.2/records/{scenarioId}', 'Адрес для добавления записей', 'char'),
'ENTRYPOINT_AUTH': ('https://amniss-ai.ru/api/auth', 'Адрес для авторизации', 'char'), 'ENTRYPOINT_AUTH': ('https://amniss-ai.ru/api/auth', 'Адрес для авторизации', 'char'),
'ENTRYPOINT_DELETE': ('https://amniss-ai.ru/api/v.1/records/delete/%s', 'Адрес для удаления', 'char'), 'ENTRYPOINT_DELETE': ('https://amniss-ai.ru/api/v.1/records/delete/%s', 'Адрес для удаления', 'char'),
@ -163,6 +163,16 @@ CONSTANCE_CONFIG = {
'Access token expired', 'Access token expired',
datetime.datetime datetime.datetime
), ),
'CALL_TIME_START': (
datetime.time(9, 30),
'Время старта обзвона',
datetime.time
),
'CALL_TIME_END': (
datetime.time(18, 30),
'Время окончания обзвона',
datetime.time
),
'MEDICINE_TOKEN': ('', 'Токен', 'char'), 'MEDICINE_TOKEN': ('', 'Токен', 'char'),
'MEDICINE_HOST': ('http://medicine-app/', 'Хост', 'char'), 'MEDICINE_HOST': ('http://medicine-app/', 'Хост', 'char'),
@ -170,18 +180,19 @@ CONSTANCE_CONFIG = {
CONSTANCE_CONFIG_FIELDSETS = ( CONSTANCE_CONFIG_FIELDSETS = (
('API', ( ('API', (
'HOST', 'IS_ENABLED',
'CALL_TIME_START',
'CALL_TIME_END',
'SCENARIO_ID',
'USERNAME',
'PASSWORD',
'ENTRYPOINT_AUTH', 'ENTRYPOINT_AUTH',
'ENTRYPOINT_ADD_RECORDS', 'ENTRYPOINT_ADD_RECORDS',
'ENTRYPOINT_DELETE', 'ENTRYPOINT_DELETE',
'ENTRYPOINT_RECORD', 'ENTRYPOINT_RECORD',
'SCENARIO_ID',
'USERNAME',
'PASSWORD',
'IS_ENABLED',
'ACCESS_TOKEN', 'ACCESS_TOKEN',
'REFRESH_TOKEN',
'ACCESS_TOKEN_EXPIRED', 'ACCESS_TOKEN_EXPIRED',
'REFRESH_TOKEN'
)), )),
('МИС', ( ('МИС', (
'MEDICINE_HOST', 'MEDICINE_HOST',
@ -191,13 +202,23 @@ CONSTANCE_CONFIG_FIELDSETS = (
REQUEST_TIMEOUT = 5 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}' BROKER_URL = f'redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CELERY_DB}'
CELERY_DEFAULT_QUEUE = 'default' CELERY_DEFAULT_QUEUE = 'default'
CELERY_ENABLE_UTC = True CELERY_ENABLE_UTC = True
CELERY_IGNORE_RESULT = True CELERY_IGNORE_RESULT = True
CELERYBEAT_SCHEDULE = { 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' 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_START = datetime.time(9, 0)
CALL_REQUEST_TIME_END = datetime.time(21, 0) CALL_REQUEST_TIME_END = datetime.time(21, 0)
DATABASE_ROUTERS = [
'appa.medicine.router.MedicineRouter',
]
if DEBUG: if DEBUG:
AUTH_PASSWORD_VALIDATORS = [] AUTH_PASSWORD_VALIDATORS = []
SESSION_COOKIE_NAME = 'dev-medicine-call' SESSION_COOKIE_NAME = 'dev-medicine-call'

View File

@ -1,10 +1,11 @@
import pytz
import datetime import datetime
import requests
from constance import config from constance import config
from appa.celery import app from appa.celery import app
from appa.models import * 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) @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: 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) @app.task(bind=True, acks_late=True)
def update_call_requests(self): def update_call_requests_task(self):
for add_days in [1, 2]: call_lpu = {call_lpu.lpu_id: call_lpu for call_lpu in CallLPU.objects.all()}
booking_date = datetime.date.today() + datetime.timedelta(days=add_days) call_specialities = {medical_speciality.speciality_id: medical_speciality
CallRequest.objects.filter(date=booking_date).delete() for medical_speciality in CallMedicalSpeciality.objects.all()}
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 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
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( CallRequest.objects.update_or_create(
defaults={ defaults=dict(
'date': booking['date'], date=datetime.datetime.strptime(booking['date_start'].split('T')[0], '%Y-%m-%d').date(),
'patient_id': booking['patient']['id'], booking_date=booking['date_start'],
'patient_first_name': booking['patient']['first_name'], patient_id=booking['patient']['id'],
'patient_last_name': booking['patient']['last_name'], patient_first_name=booking['patient']['first_name'],
'patient_middle_name': booking['patient']['middle_name'], patient_last_name=booking['patient']['last_name'],
'patient_phone': '7' + booking['patient']['phone_mobile'], patient_middle_name=booking['patient']['middle_name'],
'doctor_name': '', patient_phone='7' + booking['patient']['phone_mobile'],
'doctor_speciality': '', doctor_name=booking['user']['display'],
'service_name': '', doctor_speciality=booking['user']['doctor']['speciality_display'],
'address': '' service_name=speciality.name,
}, organization=lpu.name,
address=lpu.address,
),
booking_id=booking['id'], booking_id=booking['id'],
) )
if response['next'] is None:
break @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)

View File

@ -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 %}

View File

@ -0,0 +1,10 @@
{% extends "admin/change_form.html" %}
{% block extrahead %}
{{ block.super }}
<style>
.field-speciality .related-widget-wrapper select {
width: 600px;
}
</style>
{% endblock %}

View File

@ -2,6 +2,5 @@
set -e set -e
(cd client && npm run build) docker build --platform=linux/amd64 --tag docker.med-logic.ru/medicine-call:latest .
docker build --tag docker.med-logic.ru/medicine-damask:latest . docker push docker.med-logic.ru/medicine-call:latest
docker push docker.med-logic.ru/medicine-damask:latest

View File

@ -21,6 +21,7 @@ docker run --rm \
--master \ --master \
--py-autoreload 1 \ --py-autoreload 1 \
--disable-write-exception \ --disable-write-exception \
--disable-logging \
--buffer-size 32768 \ --buffer-size 32768 \
--http-timeout 30 \ --http-timeout 30 \
--check-static /app/ --check-static /app/