182 lines
5.7 KiB
Python
182 lines
5.7 KiB
Python
"""Application commands for document persistence and audience updates."""
|
|
|
|
import logging
|
|
from collections.abc import Mapping, Sequence
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.db import transaction
|
|
from django.db.models import Q
|
|
|
|
from documents.models import Document, DocumentTag
|
|
from documents.notifications.handlers import notify_document_audience
|
|
from employees.models import Department
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
User = get_user_model()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DocumentAudienceUpdate:
|
|
recipient_ids: Sequence[int] | None = None
|
|
department_ids: Sequence[int] | None = None
|
|
acknowledgement_recipient_ids: Sequence[int] | None = None
|
|
acknowledgement_department_ids: Sequence[int] | None = None
|
|
tag_ids: Sequence[int] | None = None
|
|
|
|
|
|
def _set_departments(document: Document, department_ids: Sequence[int]) -> None:
|
|
departments = Department.objects.filter(id__in=set(department_ids))
|
|
document.departments.set(departments)
|
|
logger.debug(
|
|
"Document departments updated",
|
|
extra={
|
|
"event": "document.departments.updated",
|
|
"document_id": document.id,
|
|
"requested_count": len(department_ids),
|
|
"updated_count": departments.count(),
|
|
},
|
|
)
|
|
|
|
|
|
def _set_recipients(document: Document, recipient_ids: Sequence[int]) -> None:
|
|
existing_ids = set(document.recipients.values_list("id", flat=True))
|
|
recipients = User.objects.filter(id__in=set(recipient_ids)).filter(
|
|
Q(is_active=True) | Q(id__in=existing_ids)
|
|
)
|
|
document.recipients.set(recipients)
|
|
logger.debug(
|
|
"Document recipients updated",
|
|
extra={
|
|
"event": "document.recipients.updated",
|
|
"document_id": document.id,
|
|
"requested_count": len(recipient_ids),
|
|
"updated_count": recipients.count(),
|
|
},
|
|
)
|
|
|
|
|
|
def _set_acknowledgement_departments(
|
|
document: Document,
|
|
department_ids: Sequence[int],
|
|
) -> None:
|
|
document.acknowledgement_departments.set(
|
|
Department.objects.filter(id__in=set(department_ids))
|
|
)
|
|
|
|
|
|
def _set_acknowledgement_recipients(
|
|
document: Document,
|
|
recipient_ids: Sequence[int],
|
|
) -> None:
|
|
existing_ids = set(
|
|
document.acknowledgement_recipients.values_list("id", flat=True)
|
|
)
|
|
recipients = User.objects.filter(id__in=set(recipient_ids)).filter(
|
|
Q(is_active=True) | Q(id__in=existing_ids)
|
|
)
|
|
document.acknowledgement_recipients.set(recipients)
|
|
|
|
|
|
def _set_tags(document: Document, tag_ids: Sequence[int]) -> None:
|
|
tags = DocumentTag.objects.filter(id__in=set(tag_ids))
|
|
document.tags.set(tags)
|
|
logger.debug(
|
|
"Document tags updated",
|
|
extra={
|
|
"event": "document.tags.updated",
|
|
"document_id": document.id,
|
|
"requested_count": len(tag_ids),
|
|
"updated_count": tags.count(),
|
|
},
|
|
)
|
|
|
|
|
|
def _apply_audience_update(
|
|
document: Document,
|
|
audience: DocumentAudienceUpdate,
|
|
) -> None:
|
|
if not document.acknowledgement_required or document.acknowledgement_for_all:
|
|
document.acknowledgement_recipients.clear()
|
|
document.acknowledgement_departments.clear()
|
|
else:
|
|
if audience.acknowledgement_department_ids is not None:
|
|
_set_acknowledgement_departments(
|
|
document,
|
|
audience.acknowledgement_department_ids,
|
|
)
|
|
if audience.acknowledgement_recipient_ids is not None:
|
|
_set_acknowledgement_recipients(
|
|
document,
|
|
audience.acknowledgement_recipient_ids,
|
|
)
|
|
|
|
if document.sent_to_all:
|
|
document.recipients.clear()
|
|
document.departments.clear()
|
|
else:
|
|
if audience.department_ids is not None:
|
|
_set_departments(document, audience.department_ids)
|
|
if audience.recipient_ids is not None:
|
|
_set_recipients(document, audience.recipient_ids)
|
|
|
|
if audience.tag_ids is not None:
|
|
_set_tags(document, audience.tag_ids)
|
|
|
|
|
|
@transaction.atomic
|
|
def create_document(
|
|
*,
|
|
uploader,
|
|
attributes: Mapping[str, Any],
|
|
audience: DocumentAudienceUpdate,
|
|
) -> Document:
|
|
"""Create a document, apply its audience and emit one final notification."""
|
|
logger.info(
|
|
"Document creation started",
|
|
extra={
|
|
"event": "document.creation.started",
|
|
"sent_to_all": attributes.get("sent_to_all", True),
|
|
"department_count": len(audience.department_ids or ()),
|
|
"recipient_count": len(audience.recipient_ids or ()),
|
|
"tag_count": len(audience.tag_ids or ()),
|
|
"uploader_id": uploader.id if uploader else None,
|
|
},
|
|
)
|
|
|
|
document = Document(uploaded_by=uploader, **dict(attributes))
|
|
document._suppress_document_notifications = True
|
|
document.save()
|
|
_apply_audience_update(document, audience)
|
|
|
|
document._suppress_document_notifications = False
|
|
notify_document_audience(document)
|
|
logger.info(
|
|
"Document created",
|
|
extra={
|
|
"event": "document.created",
|
|
"document_id": document.id,
|
|
"sent_to_all": document.sent_to_all,
|
|
"uploader_id": uploader.id if uploader else None,
|
|
},
|
|
)
|
|
return document
|
|
|
|
|
|
@transaction.atomic
|
|
def update_document(
|
|
*,
|
|
document_id: int,
|
|
attributes: Mapping[str, Any],
|
|
audience: DocumentAudienceUpdate,
|
|
) -> Document:
|
|
"""Update document fields and all supplied audience relations atomically."""
|
|
document = Document.objects.select_for_update().get(pk=document_id)
|
|
for field, value in attributes.items():
|
|
setattr(document, field, value)
|
|
document.save()
|
|
_apply_audience_update(document, audience)
|
|
return document
|