37882-vm/core/views.py
Flatlogic Bot 9e85e053cc Ver.09
2026-01-27 20:44:04 +00:00

376 lines
15 KiB
Python

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, DeleteView
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, UserPassesTestMixin
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
# Mixins
class StaffRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.is_staff
# 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', 'contract_number', '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'}),
'contract_number': forms.TextInput(attrs={'class': 'form-control'}),
'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})
class FleetUnitDeleteView(LoginRequiredMixin, StaffRequiredMixin, DeleteView):
model = FleetUnit
success_url = reverse_lazy('fleet_list')
# 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 MaintenanceDeleteView(LoginRequiredMixin, StaffRequiredMixin, DeleteView):
model = Maintenance
success_url = reverse_lazy('maintenance_list')
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})
class BreakdownDeleteView(LoginRequiredMixin, StaffRequiredMixin, DeleteView):
model = Breakdown
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 PartRequestDeleteView(LoginRequiredMixin, StaffRequiredMixin, DeleteView):
model = PartRequest
def get_success_url(self):
return reverse('supply_list')
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')
class SupplierDeleteView(LoginRequiredMixin, StaffRequiredMixin, DeleteView):
model = Supplier
success_url = reverse_lazy('supply_list')