Files
ACORE/apps/reports/documents.py
T
lokses bf5310e489 Hide peer logins on profiles and restrict view_all to published reports.
Login stays visible only to self/manager/HR; draft report docs stay department-head only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 15:20:49 +03:00

150 lines
6.0 KiB
Python

"""Документы отдела на /reports/: доступ, путь в Nextcloud, создание файлов."""
from __future__ import annotations
from django.utils import timezone
from django.utils.text import slugify
from apps.nextcloud_connector.access import safe_filename
from apps.nextcloud_connector.services.sync_service import safe_component
from apps.reports.it_monthly.builder import (
user_can_manage_it_monthly_report,
user_can_view_all_department_reports,
)
from apps.reports.models import ReportDocument
from apps.reports.registry import visible_reports
from core.portal_access import user_can_access
REPORTS_DAV_ROOT = '06_Отчёты'
ALLOWED_UPLOAD_EXTS = frozenset({'docx', 'xlsx'})
def user_can_open_reports_hub(user) -> bool:
if not getattr(user, 'is_authenticated', False):
return False
if user_can_access(user, 'reports'):
return True
return bool(visible_reports(user))
def user_can_view_report_document(user, doc: ReportDocument) -> bool:
if not user_can_open_reports_hub(user):
return False
# Черновики — только руководителю своего отдела.
if user_can_manage_it_monthly_report(user) and getattr(user, 'department_id', None) == doc.department_id:
return True
# view_all и обычные сотрудники — только опубликованные.
if not doc.is_published:
return False
if user_can_view_all_department_reports(user):
return True
return getattr(user, 'department_id', None) == doc.department_id
def user_can_edit_report_document(user, doc: ReportDocument) -> bool:
if not user_can_open_reports_hub(user):
return False
if user_can_manage_it_monthly_report(user) and getattr(user, 'department_id', None) == doc.department_id:
return True
# view_all может править/снимать только уже опубликованные (черновики чужих отделов не видит).
if user_can_view_all_department_reports(user):
return doc.is_published
return False
def user_can_create_report_document(user) -> bool:
if not user_can_open_reports_hub(user):
return False
if not getattr(user, 'department_id', None):
return False
return user_can_manage_it_monthly_report(user) or user_can_view_all_department_reports(user)
def user_can_manage_report_documents(user) -> bool:
"""Руководитель / view_all — создавать, грузить, публиковать."""
return user_can_create_report_document(user)
def visible_report_documents(user):
if not user_can_open_reports_hub(user):
return ReportDocument.objects.none()
qs = ReportDocument.objects.select_related('department', 'created_by', 'published_by')
dept_id = getattr(user, 'department_id', None)
manage_own = user_can_manage_it_monthly_report(user) and bool(dept_id)
if user_can_view_all_department_reports(user):
# Все отделы — только опубликованные; свои черновики — если ещё и руководитель.
if manage_own:
from django.db.models import Q
return qs.filter(
Q(status=ReportDocument.STATUS_PUBLISHED) | Q(department_id=dept_id)
).distinct()
return qs.filter(status=ReportDocument.STATUS_PUBLISHED)
if not dept_id:
return ReportDocument.objects.none()
qs = qs.filter(department_id=dept_id)
if manage_own:
return qs
return qs.filter(status=ReportDocument.STATUS_PUBLISHED)
def report_document_relpath(doc: ReportDocument) -> str:
folder = safe_component(doc.department.code or doc.department.name)
return f'{REPORTS_DAV_ROOT}/{folder}/{doc.filename}'
def unique_filename(department_id: int, title: str, ext: str) -> str:
base = slugify(title, allow_unicode=True) or 'document'
candidate = safe_filename(f'{base}.{ext}') or f'document.{ext}'
if not ReportDocument.objects.filter(department_id=department_id, filename=candidate).exists():
return candidate
n = 2
while True:
name = safe_filename(f'{base}-{n}.{ext}') or f'document-{n}.{ext}'
if not ReportDocument.objects.filter(department_id=department_id, filename=name).exists():
return name
n += 1
def unique_upload_filename(department_id: int, original_name: str) -> str | None:
"""Сохранить исходное имя файла, при коллизии — суффикс -2, -3…"""
name = safe_filename(original_name)
if not name:
return None
if '.' in name:
stem, ext = name.rsplit('.', 1)
ext = ext.lower()
else:
stem, ext = name, 'docx'
if ext not in ALLOWED_UPLOAD_EXTS:
return None
candidate = safe_filename(f'{stem}.{ext}') or f'document.{ext}'
if not ReportDocument.objects.filter(department_id=department_id, filename=candidate).exists():
return candidate
n = 2
while True:
cand = safe_filename(f'{stem}-{n}.{ext}') or f'document-{n}.{ext}'
if not ReportDocument.objects.filter(department_id=department_id, filename=cand).exists():
return cand
n += 1
def publish_report_document(user, doc: ReportDocument) -> ReportDocument:
if not user_can_edit_report_document(user, doc):
raise PermissionError('Недостаточно прав для публикации.')
doc.status = ReportDocument.STATUS_PUBLISHED
doc.published_at = timezone.now()
doc.published_by = user
doc.save(update_fields=['status', 'published_at', 'published_by', 'updated_at'])
return doc
def unpublish_report_document(user, doc: ReportDocument) -> ReportDocument:
if not user_can_edit_report_document(user, doc):
raise PermissionError('Недостаточно прав для снятия с публикации.')
doc.status = ReportDocument.STATUS_DRAFT
doc.published_at = None
doc.published_by = None
doc.save(update_fields=['status', 'published_at', 'published_by', 'updated_at'])
return doc