218 lines
11 KiB
Python
218 lines
11 KiB
Python
import qrcode
|
||
from io import BytesIO
|
||
from django.core.files import File
|
||
from django.db import models
|
||
from django.urls import reverse
|
||
from django.contrib.auth.models import User
|
||
from django.conf import settings
|
||
|
||
class Category(models.Model):
|
||
name = models.CharField(max_length=100, verbose_name="Наименование")
|
||
|
||
class Meta:
|
||
verbose_name = "Категория"
|
||
verbose_name_plural = "Категории"
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
class Supplier(models.Model):
|
||
name = models.CharField(max_length=255, verbose_name="Наименование компании")
|
||
representative_name = models.CharField(max_length=255, blank=True, null=True, verbose_name="ФИО представителя/менеджера")
|
||
phone = models.CharField(max_length=50, blank=True, null=True, verbose_name="Телефон")
|
||
email = models.EmailField(blank=True, null=True, verbose_name="Электронная почта")
|
||
contract_number = models.CharField(max_length=100, blank=True, null=True, verbose_name="Номер договора")
|
||
|
||
class Meta:
|
||
verbose_name = "Поставщик / Контрагент"
|
||
verbose_name_plural = "Поставщики / Контрагенты"
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
class FleetUnit(models.Model):
|
||
STATUS_CHOICES = [
|
||
('active', 'В работе'),
|
||
('idle', 'Простаивает'),
|
||
('broken', 'Сломана'),
|
||
('repair', 'В ремонте'),
|
||
('waiting_parts', 'Ждёт деталь'),
|
||
]
|
||
|
||
name = models.CharField(max_length=255, verbose_name="Наименование")
|
||
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Категория")
|
||
model_name = models.CharField(max_length=255, verbose_name="Модель")
|
||
vin = models.CharField(max_length=100, unique=True, verbose_name="VIN / Серийный номер")
|
||
plate_number = models.CharField(max_length=50, blank=True, null=True, verbose_name="Госномер")
|
||
year = models.PositiveIntegerField(verbose_name="Год выпуска")
|
||
photo = models.ImageField(upload_to='fleet_photos/', blank=True, null=True, verbose_name="Фото")
|
||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active', verbose_name="Статус")
|
||
commissioning_date = models.DateField(verbose_name="Дата ввода в эксплуатацию")
|
||
notes = models.TextField(blank=True, null=True, verbose_name="Примечания")
|
||
qr_code = models.ImageField(upload_to='qrcodes/', blank=True, null=True, verbose_name="QR-код")
|
||
|
||
# Insurance
|
||
insurance_company = models.CharField(max_length=255, blank=True, null=True, verbose_name="Страховая компания")
|
||
insurance_policy_number = models.CharField(max_length=100, blank=True, null=True, verbose_name="Номер страхового полиса")
|
||
insurance_start_date = models.DateField(blank=True, null=True, verbose_name="Дата начала страховки")
|
||
insurance_end_date = models.DateField(blank=True, null=True, verbose_name="Дата окончания страховки")
|
||
|
||
# Supplier
|
||
supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Поставщик / Контрагент")
|
||
supplier_name = models.CharField(max_length=255, blank=True, null=True, verbose_name="Поставщик (старое)")
|
||
supplier_contacts = models.TextField(blank=True, null=True, verbose_name="Контакты поставщика (старое)")
|
||
vehicle_documents = models.FileField(upload_to="fleet_docs/", blank=True, null=True, verbose_name="Документы на авто")
|
||
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Техника"
|
||
verbose_name_plural = "Техника"
|
||
ordering = ['-created_at']
|
||
|
||
def __str__(self):
|
||
return f"{self.name} ({self.plate_number or self.vin})"
|
||
|
||
def get_absolute_url(self):
|
||
return reverse('fleet_detail', kwargs={'pk': self.pk})
|
||
|
||
def get_status_color(self):
|
||
colors = {
|
||
'active': 'success',
|
||
'idle': 'secondary',
|
||
'broken': 'danger',
|
||
'repair': 'warning',
|
||
'waiting_parts': 'info',
|
||
}
|
||
return colors.get(self.status, 'primary')
|
||
|
||
def save(self, *args, **kwargs):
|
||
is_new = self.pk is None
|
||
super().save(*args, **kwargs)
|
||
if is_new or not self.qr_code:
|
||
self.generate_qr_code()
|
||
|
||
def generate_qr_code(self):
|
||
path = self.get_absolute_url()
|
||
qr_data = path
|
||
|
||
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
||
qr.add_data(qr_data)
|
||
qr.make(fit=True)
|
||
img = qr.make_image(fill='black', back_color='white')
|
||
|
||
buffer = BytesIO()
|
||
img.save(buffer, format='PNG')
|
||
filename = f'qr-{self.pk}.png'
|
||
self.qr_code.save(filename, File(buffer), save=False)
|
||
super().save(update_fields=['qr_code'])
|
||
|
||
class Maintenance(models.Model):
|
||
TYPE_CHOICES = [
|
||
('TO-250', 'ТО-250'),
|
||
('TO-500', 'ТО-500'),
|
||
('seasonal', 'Сезонное'),
|
||
('special', 'Спец'),
|
||
]
|
||
STATUS_CHOICES = [
|
||
('planned', 'Планируется'),
|
||
('in_progress', 'В процессе'),
|
||
('completed', 'Выполнено'),
|
||
]
|
||
|
||
fleet_unit = models.ForeignKey(FleetUnit, on_delete=models.CASCADE, related_name='maintenances', verbose_name="Техника")
|
||
m_type = models.CharField(max_length=50, choices=TYPE_CHOICES, verbose_name="Тип ТО")
|
||
planned_date = models.DateField(verbose_name="Плановая дата")
|
||
planned_runtime = models.PositiveIntegerField(verbose_name="Плановый пробег / моточасы")
|
||
checklist = models.JSONField(default=list, blank=True, verbose_name="Чек-лист")
|
||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='planned', verbose_name="Статус")
|
||
|
||
mechanic = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='assigned_maintenances', verbose_name="Исполнитель")
|
||
actual_date = models.DateField(null=True, blank=True, verbose_name="Фактическая дата")
|
||
actual_runtime = models.PositiveIntegerField(null=True, blank=True, verbose_name="Фактический пробег / моточасы")
|
||
parts_used = models.TextField(blank=True, null=True, verbose_name="Использованные запчасти")
|
||
photo = models.ImageField(upload_to='maintenance_photos/', blank=True, null=True, verbose_name="Фото")
|
||
|
||
notes = models.TextField(blank=True, null=True, verbose_name="Примечания")
|
||
|
||
class Meta:
|
||
verbose_name = "ТО"
|
||
verbose_name_plural = "ТО"
|
||
|
||
def __str__(self):
|
||
return f"{self.m_type} - {self.fleet_unit.name} ({self.planned_date})"
|
||
|
||
class Breakdown(models.Model):
|
||
STATUS_CHOICES = [
|
||
('reported', 'Заявлено'),
|
||
('repaired', 'Отремонтировано'),
|
||
('need_part', 'Нужна деталь'),
|
||
]
|
||
|
||
fleet_unit = models.ForeignKey(FleetUnit, on_delete=models.CASCADE, related_name='breakdowns', verbose_name="Техника")
|
||
date = models.DateField(auto_now_add=True, verbose_name="Дата")
|
||
system_node = models.CharField(max_length=255, verbose_name="Узел / система")
|
||
description = models.TextField(verbose_name="Описание")
|
||
photo = models.ImageField(upload_to='breakdown_photos/', blank=True, null=True, verbose_name="Фото")
|
||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='reported', verbose_name="Статус")
|
||
repair_date = models.DateField(null=True, blank=True, verbose_name="Дата ремонта")
|
||
notes = models.TextField(blank=True, null=True, verbose_name="Примечания")
|
||
cost = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Стоимость")
|
||
|
||
class Meta:
|
||
verbose_name = "Поломка"
|
||
verbose_name_plural = "Поломки"
|
||
|
||
def __str__(self):
|
||
return f"Поломка: {self.fleet_unit.name} - {self.system_node}"
|
||
|
||
class PartRequest(models.Model):
|
||
STATUS_CHOICES = [
|
||
('draft', 'Черновик'),
|
||
('sent', 'Отправлено'),
|
||
('ordered', 'Заказано'),
|
||
('delivered', 'Доставлено'),
|
||
]
|
||
|
||
fleet_unit = models.ForeignKey(FleetUnit, on_delete=models.CASCADE, related_name='part_requests', verbose_name="Техника")
|
||
breakdown = models.ForeignKey(Breakdown, on_delete=models.SET_NULL, null=True, blank=True, related_name='part_requests', verbose_name="Поломка")
|
||
part_name = models.CharField(max_length=255, verbose_name="Наименование детали")
|
||
article_number = models.CharField(max_length=100, blank=True, null=True, verbose_name="Артикул")
|
||
quantity = models.PositiveIntegerField(default=1, verbose_name="Количество")
|
||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft', verbose_name="Статус")
|
||
photo = models.ImageField(upload_to='part_photos/', blank=True, null=True, verbose_name="Фото")
|
||
notes = models.TextField(blank=True, null=True, verbose_name="Примечание")
|
||
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Заявка на запчасть"
|
||
verbose_name_plural = "Заявки на запчасти"
|
||
|
||
def __str__(self):
|
||
return f"{self.part_name} for {self.fleet_unit.name}"
|
||
|
||
class Document(models.Model):
|
||
TYPE_CHOICES = [
|
||
('passport', 'Паспорт'),
|
||
('maintenance_act', 'Акт ТО'),
|
||
('photo', 'Фото'),
|
||
('invoice', 'Счет'),
|
||
]
|
||
|
||
doc_type = models.CharField(max_length=50, choices=TYPE_CHOICES, verbose_name="Тип документа")
|
||
fleet_unit = models.ForeignKey(FleetUnit, on_delete=models.CASCADE, related_name='documents', null=True, blank=True)
|
||
maintenance = models.ForeignKey(Maintenance, on_delete=models.CASCADE, related_name='documents', null=True, blank=True)
|
||
breakdown = models.ForeignKey(Breakdown, on_delete=models.CASCADE, related_name='documents', null=True, blank=True)
|
||
part_request = models.ForeignKey(PartRequest, on_delete=models.CASCADE, related_name='documents', null=True, blank=True)
|
||
|
||
file = models.FileField(upload_to='documents/', verbose_name="Файл")
|
||
uploaded_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата добавления")
|
||
|
||
class Meta:
|
||
verbose_name = "Документ"
|
||
verbose_name_plural = "Документы"
|
||
|
||
def __str__(self):
|
||
return f"{self.get_doc_type_display()} - {self.uploaded_at}" |