Files
ACORE/apps/reports/publication.py
T
lokses 32ad359879 Publish IT monthly as editable Word; fix staff order and zones.
Form Word draft for OnlyOffice, publish the file to unlock the month; roster is head-first then by name with Employee responsibility zones.

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

279 lines
9.2 KiB
Python

"""Публикация отчётов отделом и видимость по месяцам."""
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