250 lines
7.4 KiB
Python
250 lines
7.4 KiB
Python
from django.core.cache import cache
|
||
from django.db.models import Count
|
||
from django.db.models import Q
|
||
|
||
|
||
UNREAD_SUMMARY_TIMEOUT = 60
|
||
|
||
|
||
def unread_summary_cache_key(user_id):
|
||
return f"notifications:unread_summary:user:{user_id}"
|
||
|
||
|
||
def _coerce_positive_int(value):
|
||
try:
|
||
integer_value = int(value)
|
||
except (TypeError, ValueError):
|
||
return None
|
||
return integer_value if integer_value > 0 else None
|
||
|
||
|
||
def _build_procurement_request_counts(user_id):
|
||
from django.contrib.contenttypes.models import ContentType
|
||
from procurement.models import ProcurementRequest
|
||
from .models import Notification
|
||
|
||
request_content_type = ContentType.objects.get_for_model(
|
||
ProcurementRequest,
|
||
)
|
||
request_content_type_id = request_content_type.id
|
||
counts = {}
|
||
notifications = (
|
||
Notification.objects.filter(
|
||
recipient_id=user_id,
|
||
unread=True,
|
||
deleted=False,
|
||
)
|
||
.filter(
|
||
Q(verb__startswith="procurement_")
|
||
| Q(verb__startswith="equipment_")
|
||
)
|
||
.only(
|
||
"data",
|
||
"target_content_type_id",
|
||
"target_object_id",
|
||
"action_object_content_type_id",
|
||
"action_object_object_id",
|
||
)
|
||
)
|
||
|
||
for notification in notifications:
|
||
data = notification.data if isinstance(notification.data, dict) else {}
|
||
request_id = _coerce_positive_int(data.get("request_id"))
|
||
|
||
if request_id is None and (
|
||
notification.target_content_type_id == request_content_type_id
|
||
):
|
||
request_id = _coerce_positive_int(notification.target_object_id)
|
||
|
||
if request_id is None and (
|
||
notification.action_object_content_type_id == request_content_type_id
|
||
):
|
||
request_id = _coerce_positive_int(
|
||
notification.action_object_object_id,
|
||
)
|
||
|
||
if request_id is not None:
|
||
counts[request_id] = counts.get(request_id, 0) + 1
|
||
|
||
return [
|
||
{"request_id": request_id, "unread": unread}
|
||
for request_id, unread in sorted(counts.items())
|
||
]
|
||
|
||
|
||
def _build_task_board_counts(user_id):
|
||
from django.contrib.contenttypes.models import ContentType
|
||
|
||
from tasks.models import Task, TaskBoard
|
||
|
||
from .models import Notification
|
||
|
||
task_content_type_id = ContentType.objects.get_for_model(Task).id
|
||
board_content_type_id = ContentType.objects.get_for_model(TaskBoard).id
|
||
counts = {}
|
||
unresolved_task_ids = set()
|
||
unresolved_notifications = []
|
||
notifications = Notification.objects.filter(
|
||
recipient_id=user_id,
|
||
unread=True,
|
||
deleted=False,
|
||
verb__startswith="task_",
|
||
).only(
|
||
"data",
|
||
"target_content_type_id",
|
||
"target_object_id",
|
||
"action_object_content_type_id",
|
||
"action_object_object_id",
|
||
)
|
||
|
||
for notification in notifications:
|
||
data = notification.data if isinstance(notification.data, dict) else {}
|
||
board_id = _coerce_positive_int(data.get("board_id"))
|
||
|
||
if board_id is None and (
|
||
notification.action_object_content_type_id == board_content_type_id
|
||
):
|
||
board_id = _coerce_positive_int(
|
||
notification.action_object_object_id,
|
||
)
|
||
|
||
if board_id is not None:
|
||
counts[board_id] = counts.get(board_id, 0) + 1
|
||
continue
|
||
|
||
task_id = None
|
||
if notification.target_content_type_id == task_content_type_id:
|
||
task_id = _coerce_positive_int(notification.target_object_id)
|
||
elif notification.action_object_content_type_id == task_content_type_id:
|
||
task_id = _coerce_positive_int(
|
||
notification.action_object_object_id,
|
||
)
|
||
|
||
if task_id is not None:
|
||
unresolved_task_ids.add(task_id)
|
||
unresolved_notifications.append(task_id)
|
||
|
||
if unresolved_task_ids:
|
||
task_boards = dict(
|
||
Task.objects.filter(id__in=unresolved_task_ids).values_list(
|
||
"id",
|
||
"board_id",
|
||
)
|
||
)
|
||
for task_id in unresolved_notifications:
|
||
board_id = task_boards.get(task_id)
|
||
if board_id is not None:
|
||
counts[board_id] = counts.get(board_id, 0) + 1
|
||
|
||
return [
|
||
{"board_id": board_id, "unread": unread}
|
||
for board_id, unread in sorted(counts.items())
|
||
]
|
||
|
||
|
||
def _build_regulation_counts(user_id):
|
||
"""Группирует непрочитанные уведомления о регламентах по отделам."""
|
||
from .models import Notification
|
||
|
||
department_counts = {}
|
||
company_unread = 0
|
||
personal_unread = 0
|
||
notifications = Notification.objects.filter(
|
||
recipient_id=user_id,
|
||
unread=True,
|
||
deleted=False,
|
||
verb__startswith="regulation_",
|
||
).only("data")
|
||
|
||
for notification in notifications:
|
||
data = notification.data if isinstance(notification.data, dict) else {}
|
||
raw_department_ids = data.get("regulation_department_ids")
|
||
department_ids = set()
|
||
if isinstance(raw_department_ids, list):
|
||
for value in raw_department_ids:
|
||
department_id = _coerce_positive_int(value)
|
||
if department_id is not None:
|
||
department_ids.add(department_id)
|
||
|
||
for department_id in department_ids:
|
||
department_counts[department_id] = (
|
||
department_counts.get(department_id, 0) + 1
|
||
)
|
||
|
||
if not department_ids:
|
||
if data.get("regulation_scope") == "company":
|
||
company_unread += 1
|
||
else:
|
||
personal_unread += 1
|
||
|
||
return {
|
||
"departments": [
|
||
{"department_id": department_id, "unread": unread}
|
||
for department_id, unread in sorted(department_counts.items())
|
||
],
|
||
"company_unread": company_unread,
|
||
"personal_unread": personal_unread,
|
||
}
|
||
|
||
|
||
def build_unread_summary(user_id):
|
||
from .models import Notification
|
||
|
||
rows = (
|
||
Notification.objects.filter(
|
||
recipient_id=user_id,
|
||
unread=True,
|
||
deleted=False,
|
||
)
|
||
.values("verb")
|
||
.annotate(unread=Count("id"))
|
||
.order_by()
|
||
)
|
||
verbs = [
|
||
{"verb": row["verb"], "unread": row["unread"]}
|
||
for row in rows
|
||
]
|
||
regulation_counts = _build_regulation_counts(user_id)
|
||
return {
|
||
"total": sum(item["unread"] for item in verbs),
|
||
"verbs": verbs,
|
||
"procurement_requests": _build_procurement_request_counts(user_id),
|
||
"task_boards": _build_task_board_counts(user_id),
|
||
"regulation_departments": regulation_counts["departments"],
|
||
"regulation_company_unread": regulation_counts["company_unread"],
|
||
"regulation_personal_unread": regulation_counts["personal_unread"],
|
||
}
|
||
|
||
|
||
def get_unread_summary(user_id):
|
||
key = unread_summary_cache_key(user_id)
|
||
|
||
try:
|
||
cached = cache.get(key)
|
||
except Exception:
|
||
cached = None
|
||
|
||
if cached is not None:
|
||
return cached
|
||
|
||
summary = build_unread_summary(user_id)
|
||
try:
|
||
cache.set(key, summary, UNREAD_SUMMARY_TIMEOUT)
|
||
except Exception:
|
||
pass
|
||
return summary
|
||
|
||
|
||
def get_unread_count(user_id):
|
||
return get_unread_summary(user_id)["total"]
|
||
|
||
|
||
def invalidate_unread_summary(user_id):
|
||
if not user_id:
|
||
return
|
||
|
||
try:
|
||
cache.delete(unread_summary_cache_key(user_id))
|
||
except Exception:
|
||
pass
|