"""Публикация отчётов отделом и видимость по месяцам.""" from __future__ import annotations import re from django.utils import timezone from apps.reports.it_monthly.builder import ( available_report_months, month_id, user_can_manage_it_monthly_report, user_can_view_all_department_reports, user_can_view_it_monthly_report, ) from apps.reports.models import ReportPublication def get_publication( report_type: str, department_id: int, year: int, month: int, ) -> ReportPublication | None: if not department_id: return None return ReportPublication.objects.filter( report_type=report_type, department_id=department_id, year=year, month=month, ).first() def is_month_published( report_type: str, year: int, month: int, *, department_id: int | None = None, ) -> bool: qs = ReportPublication.objects.filter( report_type=report_type, year=year, month=month, status=ReportPublication.STATUS_PUBLISHED, ) if department_id is not None: qs = qs.filter(department_id=department_id) return qs.exists() def published_month_ids( report_type: str, *, department_id: int | None = None, limit: int = 24, ) -> list[str]: qs = ReportPublication.objects.filter( report_type=report_type, status=ReportPublication.STATUS_PUBLISHED, ) if department_id is not None: qs = qs.filter(department_id=department_id) rows = qs.order_by('-year', '-month').values_list('year', 'month')[:limit] return [month_id(y, m) for y, m in rows] def available_months_for_user(user, report_type: str = ReportPublication.REPORT_IT_MONTHLY) -> list[str]: """Месяцы в селекторе: руководитель — все из журнала; остальные — только опубликованные.""" if user_can_manage_it_monthly_report(user): return available_report_months() if user_can_view_all_department_reports(user): return published_month_ids(report_type) dept_id = getattr(user, 'department_id', None) if not dept_id: return [] return published_month_ids(report_type, department_id=dept_id) def user_can_access_it_monthly_month(user, year: int, month: int) -> bool: if not user_can_view_it_monthly_report(user): return False if user_can_manage_it_monthly_report(user): return True if user_can_view_all_department_reports(user): return is_month_published(ReportPublication.REPORT_IT_MONTHLY, year, month) dept_id = getattr(user, 'department_id', None) if not dept_id: return False return is_month_published( ReportPublication.REPORT_IT_MONTHLY, year, month, department_id=dept_id, ) def publication_for_user_month(user, year: int, month: int) -> ReportPublication | None: """Публикация своего отдела (для кнопки руководителя / бейджа).""" dept_id = getattr(user, 'department_id', None) if not dept_id: return None return get_publication(ReportPublication.REPORT_IT_MONTHLY, dept_id, year, month) def publish_it_monthly(user, year: int, month: int) -> ReportPublication: dept_id = getattr(user, 'department_id', None) if not dept_id: raise ValueError('У пользователя не указан отдел.') if not user_can_manage_it_monthly_report(user): raise PermissionError('Публиковать может только руководитель отдела.') now = timezone.now() pub, _created = ReportPublication.objects.update_or_create( report_type=ReportPublication.REPORT_IT_MONTHLY, department_id=dept_id, year=year, month=month, defaults={ 'status': ReportPublication.STATUS_PUBLISHED, 'published_at': now, 'published_by': user, }, ) return pub def unpublish_it_monthly(user, year: int, month: int) -> None: dept_id = getattr(user, 'department_id', None) if not dept_id: raise ValueError('У пользователя не указан отдел.') if not user_can_manage_it_monthly_report(user): raise PermissionError('Снимать публикацию может только руководитель отдела.') ReportPublication.objects.filter( report_type=ReportPublication.REPORT_IT_MONTHLY, department_id=dept_id, year=year, month=month, ).update( status=ReportPublication.STATUS_DRAFT, published_at=None, published_by=None, ) def parse_source_month(source_month: str) -> tuple[int, int] | None: raw = (source_month or '').strip() m = re.fullmatch(r'(\d{4})-(\d{2})', raw) if not m: return None year, month = int(m.group(1)), int(m.group(2)) if not (1 <= month <= 12): return None return year, month def published_snapshot_for_month( *, department_id: int, year: int, month: int, ): """Опубликованный Word-снимок месяца (если есть).""" from apps.reports.models import ReportDocument mid = month_id(year, month) return ( ReportDocument.objects .filter( department_id=department_id, kind=ReportDocument.KIND_SNAPSHOT, source_month=mid, status=ReportDocument.STATUS_PUBLISHED, ) .order_by('-published_at', '-id') .first() ) def sync_month_publication_from_snapshot(user, doc) -> None: """Публикация Word-снимка → флаг месяца; снятие → снять месяц, если нет других снимков.""" from apps.reports.models import ReportDocument if getattr(doc, 'kind', None) != ReportDocument.KIND_SNAPSHOT: return parsed = parse_source_month(getattr(doc, 'source_month', '') or '') if not parsed: return year, month = parsed dept_id = doc.department_id if not dept_id: return if doc.is_published: now = timezone.now() ReportPublication.objects.update_or_create( report_type=ReportPublication.REPORT_IT_MONTHLY, department_id=dept_id, year=year, month=month, defaults={ 'status': ReportPublication.STATUS_PUBLISHED, 'published_at': now, 'published_by': user, }, ) return still_published = ReportDocument.objects.filter( department_id=dept_id, kind=ReportDocument.KIND_SNAPSHOT, source_month=month_id(year, month), status=ReportDocument.STATUS_PUBLISHED, ).exclude(pk=doc.pk).exists() if still_published: return ReportPublication.objects.filter( report_type=ReportPublication.REPORT_IT_MONTHLY, department_id=dept_id, year=year, month=month, ).update( status=ReportPublication.STATUS_DRAFT, published_at=None, published_by=None, ) def unpublish_month_and_snapshots(user, year: int, month: int) -> None: """Снять месяц и все Word-снимки этого периода отдела.""" from apps.reports.models import ReportDocument unpublish_it_monthly(user, year, month) dept_id = user.department_id ReportDocument.objects.filter( department_id=dept_id, kind=ReportDocument.KIND_SNAPSHOT, source_month=month_id(year, month), status=ReportDocument.STATUS_PUBLISHED, ).update( status=ReportDocument.STATUS_DRAFT, published_at=None, published_by=None, ) def report_list_rows(user) -> list[dict]: """Строки списка на /reports/: отчёт, отдел, период, статус публикации.""" from apps.reports.registry import visible_reports dept_labels = {'it': 'IT-отдел'} can_manage = user_can_manage_it_monthly_report(user) can_all = user_can_view_all_department_reports(user) rows = [] for spec in visible_reports(user): months = available_months_for_user(user, spec.code) dept_id = None if can_all else getattr(user, 'department_id', None) published = published_month_ids(spec.code, department_id=dept_id) latest_published = published[0] if published else '' latest_available = months[0] if months else '' if latest_published: status = 'published' status_label = 'Опубликован' elif can_manage: status = 'draft' status_label = 'Черновик' else: status = 'empty' status_label = 'Нет публикаций' rows.append({ 'code': spec.code, 'title': spec.title, 'description': spec.description, 'path': spec.path, 'department': spec.department, 'department_label': dept_labels.get(spec.department, spec.department), 'latest_month': latest_published or latest_available, 'status': status, 'status_label': status_label, 'published_count': len(published), }) return rows