This commit is contained in:
Ilya Mukhortov 2025-03-09 10:28:30 +10:00
parent f8517d0677
commit 7a7f4f82cb
8 changed files with 68 additions and 79 deletions

View File

@ -8,7 +8,7 @@ RUN apt-get install -y \
nginx \ nginx \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN pip install pip==25.0 RUN pip install pip==25.0.1
WORKDIR /app WORKDIR /app

View File

@ -1,17 +1,11 @@
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
from django.core.checks import messages
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 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_filters import * from .admin_filters import *
from .tasks import send_call_request_task, update_call_requests_task from .tasks import send_call_request_task, update_call_requests_task
@ -101,19 +95,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):
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)] ids = [item.id for item in queryset.filter(request_status=CallRequest.RequestStatus.NOT_SENT)]
if len(ids) > 0: 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:
self.message_user(
request,
message=f'Время для обзвона регламентируется с '
f'{settings.CALL_REQUEST_TIME_START} по {settings.CALL_REQUEST_TIME_END}',
level=messages.ERROR
)
@admin.action(description='Удалить выбранные записи из обзвона') @admin.action(description='Удалить выбранные записи из обзвона')
def delete_call_request(self, request, queryset): def delete_call_request(self, request, queryset):

View File

@ -4,31 +4,26 @@ import json
import requests import requests
from django.utils import timezone from django.utils import timezone
from django.core.cache import cache
from constance import config from constance import config
from appa.models import CallRequest, RequestLog from appa.models import CallRequest, RequestLog
from . import call_status from . import call_status
def auth_request(): def get_token():
token = cache.get('ACCESS_TOKEN')
if token is None:
r = requests.post( r = requests.post(
config.ENTRYPOINT_AUTH, config.ENTRYPOINT_AUTH,
data=dict( data=dict(username=config.USERNAME, password=config.PASSWORD)
username=config.USERNAME,
password=config.PASSWORD,
)
) )
if r.status_code == 200: if r.status_code == 200:
config.ACCESS_TOKEN = r.json()['accessToken'] token = r.json()['accessToken']
config.REFRESH_TOKEN = r.json()['refreshToken'] cache.set('ACCESS_TOKEN', token, timeout=300)
config.ACCESS_TOKEN_EXPIRED = datetime.datetime.now() + datetime.timedelta(minutes=5) cache.set('REFRESH_TOKEN', r.json()['refreshToken'], timeout=300)
return token
def get_token():
if not config.ACCESS_TOKEN or config.ACCESS_TOKEN_EXPIRED <= timezone.now():
auth_request()
return config.ACCESS_TOKEN
def delete_call_request(call_request: CallRequest, logging=True): def delete_call_request(call_request: CallRequest, logging=True):
@ -73,6 +68,9 @@ def get_record(call_request: CallRequest, logging=True):
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_4:
call_request.status = call_request.Status.TRANSFER
elif result['call_result_code'] == call_status.STATUS_5: elif result['call_result_code'] == call_status.STATUS_5:
call_request.status = call_request.Status.CALLBACK call_request.status = call_request.Status.CALLBACK

View File

@ -1,4 +1,3 @@
import datetime
import requests import requests
from constance import config from constance import config
@ -43,10 +42,10 @@ def get_bookings(date):
def cancel_booking(booking_id): def cancel_booking(booking_id):
r = requests.post( requests.delete(
f'{config.MEDICINE_HOST}api/1/booking/{booking_id}/failed/', f'{config.MEDICINE_HOST}api/1/booking/{booking_id}/',
data={ data={
'failed_reason': 'CANCELED' 'comment': 'Пациент отменил запись по телефону'
}, },
headers={ headers={
'Authorization': f'Token {config.MEDICINE_TOKEN}' 'Authorization': f'Token {config.MEDICINE_TOKEN}'

View File

@ -63,6 +63,7 @@ class CallRequest(models.Model):
CANCELED = 'CANCELED', 'Прием отменен' CANCELED = 'CANCELED', 'Прием отменен'
CALLBACK = 'CALLBACK', 'Перезвонить' CALLBACK = 'CALLBACK', 'Перезвонить'
WITHOUT_ANSWER = 'WITHOUT_ANSWER', 'Не дозвонились' WITHOUT_ANSWER = 'WITHOUT_ANSWER', 'Не дозвонились'
TRANSFER = 'TRANSFER', 'Просьба перенести запись'
OTHER = 'OTHER', 'Другая ошибка' OTHER = 'OTHER', 'Другая ошибка'
STATUS_COLOR = { STATUS_COLOR = {
@ -71,6 +72,7 @@ class CallRequest(models.Model):
Status.CANCELED: '#CD4246', Status.CANCELED: '#CD4246',
Status.CALLBACK: '#935610', Status.CALLBACK: '#935610',
Status.WITHOUT_ANSWER: '#866103', Status.WITHOUT_ANSWER: '#866103',
Status.TRANSFER: '#B83211',
Status.OTHER: '#5A701A' Status.OTHER: '#5A701A'
} }
@ -151,11 +153,6 @@ class CallRequest(models.Model):
class RequestLog(models.Model): class RequestLog(models.Model):
class Meta:
verbose_name = 'Запрос'
verbose_name_plural = 'История запросов'
ordering = ('-created',)
request_url = models.CharField(max_length=500, verbose_name='Адрес запроса') request_url = models.CharField(max_length=500, verbose_name='Адрес запроса')
request_body = models.TextField(null=True, blank=True, verbose_name='Запрос') request_body = models.TextField(null=True, blank=True, verbose_name='Запрос')
request_method = models.CharField(max_length=100, null=True, verbose_name='Тип запроса') request_method = models.CharField(max_length=100, null=True, verbose_name='Тип запроса')
@ -165,6 +162,11 @@ class RequestLog(models.Model):
created = models.DateTimeField(auto_now_add=True, editable=False, db_index=True, verbose_name='Дата запроса') created = models.DateTimeField(auto_now_add=True, editable=False, db_index=True, verbose_name='Дата запроса')
class Meta:
verbose_name = 'Запрос'
verbose_name_plural = 'История запросов'
ordering = ('-created',)
def __str__(self): def __str__(self):
return self.request_url return self.request_url

View File

@ -152,27 +152,9 @@ CONSTANCE_CONFIG = {
'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'),
'ENTRYPOINT_RECORD': ('https://amniss-ai.ru/api/v.1/records/%s', 'Адрес записи', 'char'), 'ENTRYPOINT_RECORD': ('https://amniss-ai.ru/api/v.1/records/%s', 'Адрес записи', 'char'),
'SCENARIO_ID': ('', 'ID сценария', 'char'),
'USERNAME': ('', 'Имя пользователя', 'char'), 'USERNAME': ('', 'Имя пользователя', 'char'),
'PASSWORD': ('', 'Пароль', 'char'), 'PASSWORD': ('', 'Пароль', 'char'),
'IS_ENABLED': (False, 'Активен', bool), 'IS_ENABLED': (False, 'Активен', bool),
'ACCESS_TOKEN': ('', 'Access token', 'char'),
'REFRESH_TOKEN': ('', 'Refresh token', 'char'),
'ACCESS_TOKEN_EXPIRED': (
datetime.datetime(2025, 1, 1, 0, 0),
'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_TOKEN': ('', 'Токен', 'char'),
'MEDICINE_HOST': ('http://medicine-app/', 'Хост', 'char'), 'MEDICINE_HOST': ('http://medicine-app/', 'Хост', 'char'),
@ -181,18 +163,12 @@ CONSTANCE_CONFIG = {
CONSTANCE_CONFIG_FIELDSETS = ( CONSTANCE_CONFIG_FIELDSETS = (
('API', ( ('API', (
'IS_ENABLED', 'IS_ENABLED',
'CALL_TIME_START',
'CALL_TIME_END',
'SCENARIO_ID',
'USERNAME', 'USERNAME',
'PASSWORD', 'PASSWORD',
'ENTRYPOINT_AUTH', 'ENTRYPOINT_AUTH',
'ENTRYPOINT_ADD_RECORDS', 'ENTRYPOINT_ADD_RECORDS',
'ENTRYPOINT_DELETE', 'ENTRYPOINT_DELETE',
'ENTRYPOINT_RECORD', 'ENTRYPOINT_RECORD',
'ACCESS_TOKEN',
'REFRESH_TOKEN',
'ACCESS_TOKEN_EXPIRED',
)), )),
('МИС', ( ('МИС', (
'MEDICINE_HOST', 'MEDICINE_HOST',
@ -216,12 +192,17 @@ CELERYBEAT_SCHEDULE = {
}, },
'check_call_requests_task': { 'check_call_requests_task': {
'task': 'appa.tasks.check_call_requests_task', 'task': 'appa.tasks.check_call_requests_task',
'schedule': crontab(minute='*/5'), 'schedule': crontab(minute='*/1'),
'options': {'queue': CELERY_DEFAULT_QUEUE} 'options': {'queue': CELERY_DEFAULT_QUEUE}
}, },
'send_daily_call_request_task': { #'send_daily_call_request_task': {
'task': 'appa.tasks.send_daily_call_request_task', # 'task': 'appa.tasks.send_daily_call_request_task',
'schedule': crontab(hour='10', minute='0'), # 'schedule': crontab(hour='10', minute='0'),
# 'options': {'queue': CELERY_DEFAULT_QUEUE}
#},
'cleanup_task': {
'task': 'appa.tasks.cleanup_task',
'schedule': crontab(hour='0', minute='0'),
'options': {'queue': CELERY_DEFAULT_QUEUE} 'options': {'queue': CELERY_DEFAULT_QUEUE}
}, },
} }
@ -229,13 +210,21 @@ CELERYBEAT_SCHEDULE = {
DATE_FORMAT = 'd.m.Y' DATE_FORMAT = 'd.m.Y'
DATETIME_FORMAT = 'd.m.Y H:i' 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 = [ DATABASE_ROUTERS = [
'appa.medicine.router.MedicineRouter', 'appa.medicine.router.MedicineRouter',
] ]
REDIS_CACHE_DB = env.str('REDIS_CELERY_DB', '8')
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CACHE_DB}",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
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,7 +1,8 @@
import pytz import pytz
import datetime import datetime
from constance import config
from django.contrib.sessions.models import Session
from django.utils import timezone
from appa.celery import app from appa.celery import app
from appa.models import * from appa.models import *
from appa.call_api import api as call_api from appa.call_api import api as call_api
@ -21,7 +22,10 @@ def send_call_request_task(self, ids=None):
) )
for call_request in call_requests: for call_request in call_requests:
try:
call_api.add_call_request(call_request) call_api.add_call_request(call_request)
except Exception as e:
print(e)
@app.task(bind=True, acks_late=True) @app.task(bind=True, acks_late=True)
@ -60,6 +64,9 @@ def update_call_requests_task(self):
if lpu is None or speciality is None: if lpu is None or speciality is None:
continue continue
if not booking['patient']['phone_mobile']:
continue
CallRequest.objects.update_or_create( CallRequest.objects.update_or_create(
defaults=dict( defaults=dict(
date=datetime.datetime.strptime(booking['date_start'].split('T')[0], '%Y-%m-%d').date(), date=datetime.datetime.strptime(booking['date_start'].split('T')[0], '%Y-%m-%d').date(),
@ -83,6 +90,13 @@ def update_call_requests_task(self):
def check_call_requests_task(self): def check_call_requests_task(self):
for call_request in CallRequest.objects.filter( for call_request in CallRequest.objects.filter(
request_status=CallRequest.RequestStatus.SENT, request_status=CallRequest.RequestStatus.SENT,
date=datetime.date.today() date__in=[datetime.date.today(), datetime.date.today() + datetime.timedelta(days=1)]
): ):
call_api.get_record(call_request) call_api.get_record(call_request)
@app.task(bind=True, acks_late=True)
def cleanup_task(self):
tomorrow = timezone.now() + datetime.timedelta(days=1)
RequestLog.objects.filter(created__lte=datetime.datetime.now() - datetime.timedelta(days=3)).delete()
Session.objects.filter(expire_date__lte=tomorrow).delete()

View File

@ -16,6 +16,7 @@ django-constance==3.1.0
django-environ==0.11.2 django-environ==0.11.2
django-ninja==1.2.0 django-ninja==1.2.0
django-picklefield==3.2 django-picklefield==3.2
django-redis==5.4.0
idna==3.8 idna==3.8
kombu==5.4.0 kombu==5.4.0
prompt_toolkit==3.0.47 prompt_toolkit==3.0.47