234 lines
9.8 KiB
Python
234 lines
9.8 KiB
Python
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)
|
||
})
|