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') location_places = request.GET.getlist('location_place') error_filters = request.GET.getlist('error_filter') malfunction_filters = request.GET.getlist('malfunction_filter') reports = DailyReport.objects.all() if date_from: reports = reports.filter(date__gte=date_from) if date_to: reports = reports.filter(date__lte=date_to) if location_places: reports = reports.filter(location_place__in=location_places) # Фильтрация по ошибкам/неисправностям if error_filters: reports = reports.filter(issue_marks__issue_type_id__in=error_filters, issue_marks__is_present=True) if malfunction_filters: reports = reports.filter(issue_marks__issue_type_id__in=malfunction_filters, issue_marks__is_present=True) reports = reports.prefetch_related('downtime_periods', 'issue_marks__issue_type').distinct() # Получаем все типы ошибок/неисправностей 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 '', 'location_place': report.location_place or '', 'location_place_display': report.get_location_place_display() if report.location_place else '', } # Добавляем отметки по каждому типу 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.location_place = data.get('location_place', 'kr') 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', ''), location_place=data.get('location_place', 'kr'), 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) })