import json from io import BytesIO from django.shortcuts import render, redirect, get_object_or_404 from django.views.generic import ListView, DetailView, CreateView, UpdateView, TemplateView, View from django.urls import reverse_lazy, reverse from django import forms from django.db.models import Count, Q from django.contrib.auth.mixins import LoginRequiredMixin from django.http import HttpResponse from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from .models import FleetUnit, Maintenance, Breakdown, PartRequest, Category, Document, Supplier # Forms class FleetUnitForm(forms.ModelForm): class Meta: model = FleetUnit fields = [ 'name', 'category', 'model_name', 'vin', 'plate_number', 'year', 'photo', 'status', 'commissioning_date', 'notes', 'insurance_company', 'insurance_policy_number', 'insurance_start_date', 'insurance_end_date', 'supplier', 'vehicle_documents' ] widgets = { 'name': forms.TextInput(attrs={'class': 'form-control'}), 'category': forms.Select(attrs={'class': 'form-select'}), 'model_name': forms.TextInput(attrs={'class': 'form-control'}), 'vin': forms.TextInput(attrs={'class': 'form-control'}), 'plate_number': forms.TextInput(attrs={'class': 'form-control'}), 'year': forms.NumberInput(attrs={'class': 'form-control'}), 'photo': forms.FileInput(attrs={'class': 'form-control'}), 'status': forms.Select(attrs={'class': 'form-select'}), 'commissioning_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'insurance_company': forms.TextInput(attrs={'class': 'form-control'}), 'insurance_policy_number': forms.TextInput(attrs={'class': 'form-control'}), 'insurance_start_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'insurance_end_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'supplier': forms.Select(attrs={'class': 'form-select'}), 'vehicle_documents': forms.FileInput(attrs={'class': 'form-control'}), } class SupplierForm(forms.ModelForm): class Meta: model = Supplier fields = ['name', 'representative_name', 'phone', 'email', 'contract_number'] widgets = { 'name': forms.TextInput(attrs={'class': 'form-control'}), 'representative_name': forms.TextInput(attrs={'class': 'form-control'}), 'phone': forms.TextInput(attrs={'class': 'form-control'}), 'email': forms.EmailInput(attrs={'class': 'form-control'}), 'contract_number': forms.TextInput(attrs={'class': 'form-control'}), } class MaintenanceForm(forms.ModelForm): class Meta: model = Maintenance fields = ['fleet_unit', 'm_type', 'planned_date', 'planned_runtime', 'mechanic', 'notes'] widgets = { 'fleet_unit': forms.Select(attrs={'class': 'form-select'}), 'm_type': forms.Select(attrs={'class': 'form-select'}), 'planned_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'planned_runtime': forms.NumberInput(attrs={'class': 'form-control'}), 'mechanic': forms.Select(attrs={'class': 'form-select'}), 'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), } class BreakdownForm(forms.ModelForm): class Meta: model = Breakdown fields = ['fleet_unit', 'system_node', 'description', 'photo', 'status', 'notes'] widgets = { 'fleet_unit': forms.Select(attrs={'class': 'form-select'}), 'system_node': forms.TextInput(attrs={'class': 'form-control'}), 'description': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'photo': forms.FileInput(attrs={'class': 'form-control'}), 'status': forms.Select(attrs={'class': 'form-select'}), 'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), } class PartRequestForm(forms.ModelForm): class Meta: model = PartRequest fields = ['fleet_unit', 'breakdown', 'part_name', 'article_number', 'quantity', 'status', 'photo', 'notes'] widgets = { 'fleet_unit': forms.Select(attrs={'class': 'form-select'}), 'breakdown': forms.Select(attrs={'class': 'form-select'}), 'part_name': forms.TextInput(attrs={'class': 'form-control'}), 'article_number': forms.TextInput(attrs={'class': 'form-control'}), 'quantity': forms.NumberInput(attrs={'class': 'form-control'}), 'status': forms.Select(attrs={'class': 'form-select'}), 'photo': forms.FileInput(attrs={'class': 'form-control'}), 'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), } # Views class IndexView(TemplateView): template_name = 'core/index.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['total_units'] = FleetUnit.objects.count() context['active_units'] = FleetUnit.objects.filter(status='active').count() context['broken_units'] = FleetUnit.objects.filter(status='broken').count() context['repair_units'] = FleetUnit.objects.filter(status='repair').count() context['status_counts'] = FleetUnit.objects.values('status').annotate(total=Count('status')) context['recent_maintenances'] = Maintenance.objects.all().order_by('-planned_date')[:5] context['recent_breakdowns'] = Breakdown.objects.all().order_by('-date')[:5] return context # Fleet Views class FleetListView(ListView): model = FleetUnit template_name = 'core/fleet_list.html' context_object_name = 'units' paginate_by = 12 def get_queryset(self): queryset = super().get_queryset() status = self.request.GET.get('status') if status: queryset = queryset.filter(status=status) search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(name__icontains=search) | Q(plate_number__icontains=search) | Q(vin__icontains=search) ) return queryset class FleetDetailView(DetailView): model = FleetUnit template_name = 'core/fleet_detail.html' context_object_name = 'unit' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['maintenances'] = self.object.maintenances.all().order_by('-planned_date') context['breakdowns'] = self.object.breakdowns.all().order_by('-date') context['part_requests'] = self.object.part_requests.all().order_by('-created_at') return context class FleetCreateView(CreateView): model = FleetUnit template_name = 'core/fleet_form.html' form_class = FleetUnitForm success_url = reverse_lazy('fleet_list') class FleetUpdateView(UpdateView): model = FleetUnit template_name = 'core/fleet_form.html' form_class = FleetUnitForm def get_success_url(self): return reverse_lazy('fleet_detail', kwargs={'pk': self.object.pk}) # Maintenance Views class MaintenanceListView(ListView): model = Maintenance template_name = 'core/maintenance_list.html' context_object_name = 'maintenances' paginate_by = 15 class MaintenanceDetailView(DetailView): model = Maintenance template_name = 'core/maintenance_detail.html' context_object_name = 'maintenance' class MaintenanceCreateView(CreateView): model = Maintenance template_name = 'core/maintenance_form.html' form_class = MaintenanceForm success_url = reverse_lazy('maintenance_list') def get_initial(self): initial = super().get_initial() fleet_id = self.request.GET.get('fleet_unit') if fleet_id: initial['fleet_unit'] = get_object_or_404(FleetUnit, pk=fleet_id) return initial def form_valid(self, form): m_type = form.cleaned_data.get('m_type') checklist = [] if m_type == 'TO-250': checklist = [{"task": "Замена масла в двигателе", "done": False}, {"task": "Замена масляного фильтра", "done": False}] elif m_type == 'TO-500': checklist = [{"task": "Замена масла в двигателе", "done": False}, {"task": "Замена фильтров", "done": False}, {"task": "Осмотр ходовой", "done": False}] else: checklist = [{"task": "Общий осмотр", "done": False}] form.instance.checklist = checklist return super().form_valid(form) class MaintenanceUpdateView(UpdateView): model = Maintenance template_name = 'core/maintenance_form.html' form_class = MaintenanceForm def get_success_url(self): return reverse('maintenance_detail', kwargs={'pk': self.object.pk}) class MaintenanceProcessView(View): def post(self, request, pk): maintenance = get_object_or_404(Maintenance, pk=pk) task_index = request.POST.get('task_index') done = request.POST.get('done') == 'true' if task_index is not None: idx = int(task_index) if 0 <= idx < len(maintenance.checklist): maintenance.checklist[idx]['done'] = done maintenance.save() return redirect('maintenance_detail', pk=pk) class MaintenanceCompleteView(View): def post(self, request, pk): maintenance = get_object_or_404(Maintenance, pk=pk) maintenance.status = 'completed' maintenance.actual_date = request.POST.get('actual_date') or None maintenance.actual_runtime = request.POST.get('actual_runtime') or None maintenance.parts_used = request.POST.get('parts_used') maintenance.save() unit = maintenance.fleet_unit unit.status = 'active' unit.save() return redirect('maintenance_detail', pk=pk) class MaintenancePDFView(View): def get(self, request, pk): maintenance = get_object_or_404(Maintenance, pk=pk) buffer = BytesIO() p = canvas.Canvas(buffer, pagesize=A4) # Register Cyrillic font font_path = "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf" try: pdfmetrics.registerFont(TTFont('LiberationSans', font_path)) p.setFont('LiberationSans', 12) except: pass # Fallback to default if font not found p.drawString(100, 800, f"Акт технического обслуживания - {maintenance.m_type}") p.drawString(100, 780, f"Техника: {maintenance.fleet_unit.name}") p.drawString(100, 760, f"Дата: {maintenance.actual_date}") p.drawString(100, 740, f"Наработка: {maintenance.actual_runtime}") y = 700 p.drawString(100, y, "Чек-лист:") y -= 20 for item in maintenance.checklist: status = "[x]" if item['done'] else "[ ]" p.drawString(120, y, f"{status} {item['task']}") y -= 20 p.showPage() p.save() buffer.seek(0) return HttpResponse(buffer, content_type='application/pdf', headers={'Content-Disposition': f'attachment; filename="maintenance-{pk}.pdf"'}) # Breakdown Views class BreakdownListView(ListView): model = Breakdown template_name = 'core/breakdown_list.html' context_object_name = 'breakdowns' class BreakdownCreateView(CreateView): model = Breakdown form_class = BreakdownForm template_name = 'core/breakdown_form.html' def get_initial(self): initial = super().get_initial() fleet_id = self.request.GET.get('fleet_unit') if fleet_id: initial['fleet_unit'] = get_object_or_404(FleetUnit, pk=fleet_id) return initial def form_valid(self, form): response = super().form_valid(form) unit = self.object.fleet_unit if self.object.status == 'reported': unit.status = 'broken' elif self.object.status == 'need_part': unit.status = 'waiting_parts' unit.save() return response def get_success_url(self): return reverse('fleet_detail', kwargs={'pk': self.object.fleet_unit.pk}) # Part Request Views class PartRequestListView(ListView): model = PartRequest template_name = 'core/part_request_list.html' context_object_name = 'requests' class PartRequestCreateView(CreateView): model = PartRequest form_class = PartRequestForm template_name = 'core/part_request_form.html' def get_initial(self): initial = super().get_initial() breakdown_id = self.request.GET.get('breakdown') if breakdown_id: breakdown = get_object_or_404(Breakdown, pk=breakdown_id) initial['breakdown'] = breakdown initial['fleet_unit'] = breakdown.fleet_unit fleet_id = self.request.GET.get('fleet_unit') if fleet_id: initial['fleet_unit'] = get_object_or_404(FleetUnit, pk=fleet_id) return initial def get_success_url(self): return reverse('fleet_detail', kwargs={'pk': self.object.fleet_unit.pk}) class SupplyListView(ListView): model = PartRequest template_name = 'core/supply_list.html' context_object_name = 'requests' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['suppliers'] = Supplier.objects.all() return context class SupplierCreateView(CreateView): model = Supplier form_class = SupplierForm template_name = 'core/supplier_form.html' success_url = reverse_lazy('supply_list') class SupplierUpdateView(UpdateView): model = Supplier form_class = SupplierForm template_name = 'core/supplier_form.html' success_url = reverse_lazy('supply_list')