32ad359879
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>
279 lines
9.2 KiB
Python
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
|