Files
MES_Core/manufacturing/models.py
2026-04-13 07:36:57 +03:00

226 lines
8.8 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
class Operation(models.Model):
"""Операция техпроцесса.
Комментарий: справочник расширяется без изменений кода.
"""
name = models.CharField('Операция', max_length=200, unique=True)
code = models.SlugField(
'Код',
max_length=64,
unique=True,
help_text='Стабильный идентификатор (например welding, painting, laser_cutting).',
)
workshop = models.ForeignKey(
'shiftflow.Workshop',
on_delete=models.PROTECT,
null=True,
blank=True,
verbose_name='Цех по умолчанию',
)
class Meta:
verbose_name = 'Операция'
verbose_name_plural = 'Операции'
def __str__(self):
return self.name
class ProductEntity(models.Model):
"""Паспорт детали/сборки/изделия (КД).
planned_material:
- материал, заложенный в КД (для расчёта потребности и контроля замен при раскрое).
Нормы расхода (для BOM Explosion и MaterialRequirement):
- для листовой детали: blank_area_m2 (м² на 1 шт)
- для линейной (профиль/труба/круг): blank_length_mm (мм на 1 шт)
Примечание:
- категорию типа (лист/профиль) определяем по planned_material.category.
"""
ENTITY_TYPE = [
('product', 'Готовое изделие'),
('assembly', 'Сборочная единица'),
('part', 'Деталь'),
('purchased', 'Покупное'),
('casting', 'Литьё'),
('outsourced', 'Аутсорс'),
]
name = models.CharField("Наименование", max_length=255)
drawing_number = models.CharField("Обозначение/Чертёж", max_length=100, blank=True, default="")
entity_type = models.CharField("Тип", max_length=15, choices=ENTITY_TYPE, default='part')
planned_material = models.ForeignKey(
'warehouse.Material',
on_delete=models.PROTECT,
null=True,
blank=True,
verbose_name="Заложенный материал",
)
blank_area_m2 = models.FloatField("Норма: площадь заготовки (м²/шт)", null=True, blank=True)
blank_length_mm = models.FloatField("Норма: длина заготовки (мм/шт)", null=True, blank=True)
dxf_file = models.FileField("Исходник (DXF/IGES/STEP)", upload_to="drawings/%Y/%m/", blank=True, null=True)
pdf_main = models.FileField("Чертёж (PDF)", upload_to="drawings_pdf/%Y/%m/", blank=True, null=True)
preview = models.ImageField("Превью", upload_to="previews/%Y/%m/", blank=True, null=True)
passport_filled = models.BooleanField('Паспорт заполнен', default=False)
class Meta:
verbose_name = "КД (изделие/деталь)"
verbose_name_plural = "КД (изделия/детали)"
def __str__(self):
base = f"{self.drawing_number} {self.name}".strip()
return base if base else self.name
class EntityOperation(models.Model):
"""Операции техпроцесса для конкретной сущности (деталь/сборка/изделие)."""
entity = models.ForeignKey(ProductEntity, on_delete=models.CASCADE, related_name='operations', verbose_name='Сущность')
operation = models.ForeignKey(Operation, on_delete=models.PROTECT, verbose_name='Операция')
seq = models.PositiveSmallIntegerField('Порядок', default=1)
class Meta:
verbose_name = 'Операция сущности'
verbose_name_plural = 'Операции сущностей'
ordering = ('entity', 'seq', 'id')
unique_together = ('entity', 'seq')
def __str__(self):
return f"{self.entity}: {self.seq}. {self.operation}"
class BOM(models.Model):
"""Спецификация (BOM): parent состоит из child в количестве quantity."""
parent = models.ForeignKey(
ProductEntity,
related_name='components',
on_delete=models.CASCADE,
verbose_name="Куда входит (сборка)",
)
child = models.ForeignKey(
ProductEntity,
related_name='used_in',
on_delete=models.CASCADE,
verbose_name="Что входит (деталь)",
)
quantity = models.PositiveIntegerField("Кол-во в сборке", default=1)
class Meta:
unique_together = ('parent', 'child')
verbose_name = "Спецификация (BOM)"
verbose_name_plural = "Спецификации (BOM)"
def __str__(self):
return f"{self.parent} -> {self.child} x{self.quantity}"
class AssemblyPassport(models.Model):
entity = models.OneToOneField(ProductEntity, on_delete=models.CASCADE, related_name='assembly_passport')
requires_welding = models.BooleanField('Требуется сварка', default=False)
requires_painting = models.BooleanField('Требуется покраска', default=False)
weight_kg = models.FloatField('Масса, кг', null=True, blank=True)
coating = models.CharField('Покрытие', max_length=200, blank=True, default='')
coating_color = models.CharField('Цвет', max_length=100, blank=True, default='')
coating_area_m2 = models.FloatField('Площадь покрытия, м²', null=True, blank=True)
technical_requirements = models.TextField('Технические требования', blank=True, default='')
class Meta:
verbose_name = 'Паспорт сборки/изделия'
verbose_name_plural = 'Паспорта сборок/изделий'
def __str__(self):
return str(self.entity)
class WeldingSeam(models.Model):
passport = models.ForeignKey(AssemblyPassport, related_name='welding_seams', on_delete=models.CASCADE)
name = models.CharField('Наименование', max_length=255)
leg_mm = models.FloatField('Катет, мм')
length_mm = models.FloatField('Длина, мм')
quantity = models.PositiveIntegerField('Кол-во', default=1)
class Meta:
verbose_name = 'Сварной шов'
verbose_name_plural = 'Сварные швы'
def __str__(self):
return f"{self.name}"
class PartPassport(models.Model):
entity = models.OneToOneField(ProductEntity, on_delete=models.CASCADE, related_name='part_passport')
thickness_mm = models.FloatField('Толщина, мм', null=True, blank=True)
length_mm = models.FloatField('Длина, мм', null=True, blank=True)
mass_kg = models.FloatField('Масса, кг', null=True, blank=True)
cut_length_mm = models.FloatField('Длина реза, мм', null=True, blank=True)
pierce_count = models.PositiveIntegerField('Кол-во врезок', null=True, blank=True)
engraving = models.TextField('Гравировка', blank=True, default='')
technical_requirements = models.TextField('Технические требования', blank=True, default='')
class Meta:
verbose_name = 'Паспорт детали'
verbose_name_plural = 'Паспорта деталей'
def __str__(self):
return str(self.entity)
class PurchasedPassport(models.Model):
entity = models.OneToOneField(ProductEntity, on_delete=models.CASCADE, related_name='purchased_passport')
gost = models.CharField('ГОСТ/ТУ', max_length=255, blank=True, default='')
class Meta:
verbose_name = 'Паспорт покупного'
verbose_name_plural = 'Паспорта покупного'
def __str__(self):
return str(self.entity)
class CastingPassport(models.Model):
entity = models.OneToOneField(ProductEntity, on_delete=models.CASCADE, related_name='casting_passport')
casting_material = models.CharField('Материал литья', max_length=200, blank=True, default='')
mass_kg = models.FloatField('Масса, кг', null=True, blank=True)
class Meta:
verbose_name = 'Паспорт литья'
verbose_name_plural = 'Паспорта литья'
def __str__(self):
return str(self.entity)
class OutsourcedPassport(models.Model):
entity = models.OneToOneField(ProductEntity, on_delete=models.CASCADE, related_name='outsourced_passport')
technical_requirements = models.TextField('Технические требования', blank=True, default='')
notes = models.TextField('Пояснения', blank=True, default='')
class Meta:
verbose_name = 'Паспорт аутсорса'
verbose_name_plural = 'Паспорта аутсорса'
def __str__(self):
return str(self.entity)