Files
ProdMan/bom_manager/models.py
2026-02-14 09:32:46 +03:00

147 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.db import models
from django.urls import reverse
from mptt.models import MPTTModel, TreeForeignKey
# Create your models here.
class EntityType(models.TextChoices):
"""Тип изделия"""
UNIT = 'UNIT', 'Изделие'
ASSEMBLY = 'ASSY', 'Сборка'
PART = 'PART', 'Деталь'
STANDARD = 'STD', 'Стандартное изделие'
COMPLEX = 'CPLX', 'Комплекс'
class OperationType(models.TextChoices):
"""Тип операции"""
LRL = 'LRL', 'Лазерная резка листа (ЛРЛ)'
LRT = 'LRT', 'Лазерная резка трубы (ЛРТ)'
TO = 'TO', 'Токарная обработка (ТО)'
WELD = 'WELD', 'Сварка'
PAINT = 'PAINT', 'Покраска'
BEND = 'BEND', 'Гибка'
CLEAN = 'CLEAN', 'Зачистка'
BANDSAW = 'BANDSAW', 'Лентопильный станок'
MOVE = 'MOVE', 'Перемещение'
class WorkCenter(models.Model):
"""Справочник станков или участков"""
name = models.CharField("Название станка/участка", max_length=100)
rate_per_hour = models.DecimalField("Стоимость часа", max_digits=10, decimal_places=2, default=0)
class Meta:
verbose_name = "Станок/участок"
verbose_name_plural = "Станки/участки"
def __str__(self):
return self.name
class Item(models.Model):
# Децимальный номер
designation = models.CharField(max_length=30, verbose_name="Децимальный номер", db_index=True, blank=True, null=True)
# Обозначение
title = models.CharField(max_length=100, verbose_name="Обозначение", blank=False, null=False)
# Тип изделия
entity_type = models.CharField(max_length=4, choices=EntityType.choices, default=EntityType.PART)
# Флаг сборки
is_assembly = models.BooleanField("Сборка", default=False)
# Технические данные
drawing = models.FileField("Чертеж", upload_to='drawings/', blank=True, null=True)
class Meta:
verbose_name = "Компонент"
verbose_name_plural = "Компоненты"
def __str__(self):
if self.designation:
return f"{self.designation} {self.title}"
return self.title # Если номера нет, выводим только название без None
def get_absolute_url(self):
return reverse("item_detail", kwargs={"pk": self.pk})
class BOMNode(MPTTModel):
# Связь с компонентом
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='bom_nodes')
# Связь с родительским компонентом
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
# Количество
quantity = models.IntegerField(default=1, verbose_name="Количество")
# Разрешаем добавление компонентов только в сборки и изделия
def clean(self):
if self.parent and self.parent.item.entity_type not in [EntityType.UNIT, EntityType.ASSEMBLY, EntityType.COMPLEX]:
raise ValidationError(
f"Нельзя добавлять компоненты в '{self.parent.item.title}', так как это не сборка или изделие!"
)
class MPTTMeta:
order_insertion_by = [] # todo: add order_insertion_by
verbose_name = "Дерево материала"
verbose_name_plural = "Деревья материала"
class Meta:
verbose_name = "Изделие"
verbose_name_plural = "Изделия"
def __str__(self):
res = f"{self.item.designation or ''} {self.item.title}"
if self.parent:
res += f"{self.parent.item.title})"
else:
res += " (Корень изделия)"
return res
def get_absolute_url(self):
return reverse("bom_node_detail", kwargs={"pk": self.pk})
class RoutingStep(models.Model):
"""Технологическая операция"""
# Связь с компонентом
item = models.ForeignKey('Item', on_delete=models.CASCADE, related_name='routing_steps', verbose_name="Деталь/Сборка")
# Тип операции
operation_type = models.CharField("Тип операции", max_length=10, choices=OperationType.choices)
# Связь с станком
work_center = models.ForeignKey(WorkCenter, on_delete=models.SET_NULL, null=True, verbose_name="Станок/Участок")
# Номер операции
order = models.PositiveIntegerField("Номер операции", default=10, help_text="Например: 10, 20, 30...")
# Файлы
drawing_file = models.FileField("Тех. файл (DXF/IGES)", upload_to='tech_files/%Y/%m', null=True, blank=True)
# Гибкие данные (JSON)
# Сюда будем писать: {"cut_length": 1500, "pierces": 20} или {"welds": [{"leg": 5, "length": 100}]}
tech_params = models.JSONField("Технологические параметры", default=dict, blank=True)
# Общие поля для всех операций
setup_time = models.DurationField("Время наладки", null=True, blank=True)
cycle_time = models.DurationField("Время цикла (на 1 шт)", null=True, blank=True)
def clean(self):
if self.operation_type == OperationType.LRL:
if 'cut_length' not in self.tech_params:
raise ValidationError("Для ЛРЛ обязательно укажите 'cut_length' в параметрах!")
if 'pierces' not in self.tech_params:
raise ValidationError("Для ЛРЛ обязательно укажите 'pierces' в параметрах!")
if self.operation_type == OperationType.LRT:
if 'cut_length' not in self.tech_params:
raise ValidationError("Для ЛРТ обязательно укажите 'cut_length' в параметрах!")
class Meta:
ordering = ['order']
verbose_name = "Технологическая операция"
verbose_name_plural = "Технологический маршрут"
def __str__(self):
return f"{self.order}. {self.get_operation_type_display()}"