Добавил журнал ошибок

This commit is contained in:
2025-12-15 17:54:26 +03:00
parent 480bb60855
commit 1a953cc558
12 changed files with 1162 additions and 7 deletions

View File

@@ -85,6 +85,13 @@ from .source_requests import (
SourceRequestAPIView,
SourceRequestDetailAPIView,
)
from .errors_report import (
ErrorsReportView,
ErrorsReportAPIView,
ErrorsReportSaveAPIView,
ErrorsReportDeleteAPIView,
WeeklyHoursAPIView,
)
__all__ = [
# Base
@@ -170,4 +177,10 @@ __all__ = [
'SourceRequestDeleteView',
'SourceRequestAPIView',
'SourceRequestDetailAPIView',
# Errors Report
'ErrorsReportView',
'ErrorsReportAPIView',
'ErrorsReportSaveAPIView',
'ErrorsReportDeleteAPIView',
'WeeklyHoursAPIView',
]

View File

@@ -0,0 +1,218 @@
import json
from datetime import datetime, timedelta
from decimal import Decimal
from django.contrib.auth.mixins import LoginRequiredMixin
from mainapp.permissions import PermissionRequiredMixin
from django.views.generic import TemplateView, View
from django.http import JsonResponse
from django.db import transaction
from django.db.models import Sum
from mainapp.models import IssueType, DailyReport, DowntimePeriod, IssueMark
class ErrorsReportView(TemplateView, LoginRequiredMixin, PermissionRequiredMixin):
"""Страница отчётов об ошибках"""
template_name = 'mainapp/errors_report.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['issue_types'] = IssueType.objects.all().order_by('category', 'name')
context['errors'] = context['issue_types'].filter(category='error')
context['malfunctions'] = context['issue_types'].filter(category='malfunction')
context['full_width_page'] = True
return context
class ErrorsReportAPIView(View, LoginRequiredMixin, PermissionRequiredMixin):
"""API для получения данных отчётов"""
def get(self, request):
# Получаем параметры фильтрации
date_from = request.GET.get('date_from')
date_to = request.GET.get('date_to')
reports = DailyReport.objects.all()
if date_from:
reports = reports.filter(date__gte=date_from)
if date_to:
reports = reports.filter(date__lte=date_to)
reports = reports.prefetch_related('downtime_periods', 'issue_marks__issue_type')
# Получаем все типы ошибок/неисправностей
issue_types = IssueType.objects.all().order_by('category', 'name')
data = []
for report in reports:
# Формируем строку простоев
downtimes = []
for dt in report.downtime_periods.all():
downtimes.append({
'id': dt.id,
'start': dt.start_time.strftime('%H:%M'),
'end': dt.end_time.strftime('%H:%M'),
'reason': dt.reason
})
# Формируем отметки
marks_dict = {m.issue_type_id: m.is_present for m in report.issue_marks.all()}
row = {
'id': report.id,
'date': report.date.isoformat(),
'downtimes': downtimes,
'downtimes_display': '; '.join([f"{d['start']}-{d['end']} ({d['reason']})" for d in downtimes]),
'daily_work_hours': float(report.daily_work_hours),
'weekly_work_hours': float(report.weekly_work_hours),
'explanation': report.explanation or '',
'comment': report.comment or '',
}
# Добавляем отметки по каждому типу
for it in issue_types:
row[f'issue_{it.id}'] = marks_dict.get(it.id, False)
data.append(row)
# Формируем информацию о колонках
columns = []
for it in issue_types:
columns.append({
'id': it.id,
'name': it.name,
'category': it.category,
'field': f'issue_{it.id}'
})
return JsonResponse({
'data': data,
'columns': columns
})
class ErrorsReportSaveAPIView(View):
"""API для сохранения отчёта"""
def post(self, request):
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({'success': False, 'error': 'Invalid JSON'}, status=400)
date_str = data.get('date')
if not date_str:
return JsonResponse({'success': False, 'error': 'Дата обязательна'}, status=400)
try:
report_date = datetime.strptime(date_str, '%Y-%m-%d').date()
except ValueError:
return JsonResponse({'success': False, 'error': 'Неверный формат даты'}, status=400)
report_id = data.get('id')
# Проверка на дублирование даты при создании новой записи
if not report_id:
if DailyReport.objects.filter(date=report_date).exists():
return JsonResponse({
'success': False,
'error': f'Запись за {report_date.strftime("%d.%m.%Y")} уже существует'
}, status=400)
with transaction.atomic():
if report_id:
# Обновление существующей записи
try:
report = DailyReport.objects.get(id=report_id)
report.date = report_date
report.daily_work_hours = Decimal(str(data.get('daily_work_hours', 0)))
report.explanation = data.get('explanation', '')
report.comment = data.get('comment', '')
report.save()
except DailyReport.DoesNotExist:
return JsonResponse({'success': False, 'error': 'Запись не найдена'}, status=404)
else:
# Создание новой записи
report = DailyReport.objects.create(
date=report_date,
daily_work_hours=Decimal(str(data.get('daily_work_hours', 0))),
explanation=data.get('explanation', ''),
comment=data.get('comment', ''),
created_by=request.user if request.user.is_authenticated else None,
)
# Обновляем периоды простоя
downtimes = data.get('downtimes', [])
report.downtime_periods.all().delete()
for dt in downtimes:
if dt.get('start') and dt.get('end'):
DowntimePeriod.objects.create(
report=report,
start_time=dt['start'],
end_time=dt['end'],
reason=dt.get('reason', '')
)
# Обновляем отметки
issue_marks = data.get('issue_marks', {})
for issue_id_str, is_present in issue_marks.items():
issue_id = int(issue_id_str)
IssueMark.objects.update_or_create(
report=report,
issue_type_id=issue_id,
defaults={'is_present': is_present}
)
return JsonResponse({'success': True, 'id': report.id})
class ErrorsReportDeleteAPIView(View):
"""API для удаления отчёта"""
def post(self, request):
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({'success': False, 'error': 'Invalid JSON'}, status=400)
report_id = data.get('id')
if not report_id:
return JsonResponse({'success': False, 'error': 'ID отчёта обязателен'}, status=400)
try:
report = DailyReport.objects.get(id=report_id)
report.delete()
return JsonResponse({'success': True})
except DailyReport.DoesNotExist:
return JsonResponse({'success': False, 'error': 'Отчёт не найден'}, status=404)
class WeeklyHoursAPIView(View):
"""API для расчёта часов за неделю"""
def get(self, request):
date_str = request.GET.get('date')
if not date_str:
return JsonResponse({'error': 'Дата обязательна'}, status=400)
try:
target_date = datetime.strptime(date_str, '%Y-%m-%d').date()
except ValueError:
return JsonResponse({'error': 'Неверный формат даты'}, status=400)
# Находим начало недели (понедельник)
week_start = target_date - timedelta(days=target_date.weekday())
week_end = week_start + timedelta(days=6)
# Считаем сумму часов за неделю
total = DailyReport.objects.filter(
date__gte=week_start,
date__lte=target_date
).aggregate(total=Sum('daily_work_hours'))['total'] or 0
return JsonResponse({
'week_start': week_start.isoformat(),
'week_end': week_end.isoformat(),
'total_hours': float(total)
})

View File

@@ -59,6 +59,7 @@ class UserPermissionsEditView(LoginRequiredMixin, PermissionRequiredMixin, View)
'Транспондеры': [],
'Тех. анализ': [],
'Отметки': [],
'Журнал ошибок': [],
'Прочее': [],
}
@@ -85,6 +86,8 @@ class UserPermissionsEditView(LoginRequiredMixin, PermissionRequiredMixin, View)
permission_groups['Тех. анализ'].append(perm_data)
elif code.startswith('mark_'):
permission_groups['Отметки'].append(perm_data)
elif code.startswith('errors_report_'):
permission_groups['Журнал ошибок'].append(perm_data)
else:
permission_groups['Прочее'].append(perm_data)