All checks were successful
Deploy MES Core / deploy (push) Successful in 11s
104 lines
5.8 KiB
Python
104 lines
5.8 KiB
Python
from django.db import models
|
||
from django.contrib.auth.models import User
|
||
|
||
class MaterialCategory(models.Model):
|
||
"""Категория сырья (Лист, Труба, Круг)."""
|
||
name = models.CharField("Название категории", max_length=100, unique=True)
|
||
gost_standard = models.CharField("ГОСТ", max_length=255, blank=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Категория материала"
|
||
verbose_name_plural = "Категории материалов"
|
||
|
||
def __str__(self): return self.name
|
||
|
||
class SteelGrade(models.Model):
|
||
"""Марка стали."""
|
||
name = models.CharField("Марка стали", max_length=100, unique=True)
|
||
gost_standard = models.CharField("ГОСТ/ТУ", max_length=255, blank=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Марка стали"; verbose_name_plural = "Марки стали"
|
||
|
||
def __str__(self): return self.name
|
||
|
||
class Material(models.Model):
|
||
"""
|
||
Справочник закупаемого сырья (Номенклатура).
|
||
Логика: Это только "идея" материала, а не физический объект на полке.
|
||
Для листа заполняем thickness, для трубы width (сечение), для всех length (стандартная длина).
|
||
"""
|
||
category = models.ForeignKey(MaterialCategory, on_delete=models.PROTECT, verbose_name="Категория")
|
||
steel_grade = models.ForeignKey(SteelGrade, on_delete=models.PROTECT, null=True, blank=True)
|
||
name = models.CharField("Наименование", max_length=255)
|
||
|
||
thickness = models.FloatField("Толщина (S), мм", null=True, blank=True)
|
||
width = models.FloatField("Ширина/Сечение (B), мм", null=True, blank=True)
|
||
length = models.FloatField("Длина (L), мм", null=True, blank=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Номенклатура (Сырье)"; verbose_name_plural = "Номенклатура (Сырье)"
|
||
|
||
def __str__(self): return f"{self.category.name} {self.name} {self.steel_grade.name if self.steel_grade else ''}"
|
||
|
||
class Location(models.Model):
|
||
"""Склады и участки (Центральный, Лазер, Сварка, СГП)."""
|
||
name = models.CharField("Место хранения", max_length=100, unique=True)
|
||
is_production_area = models.BooleanField("Это производственный участок", default=False)
|
||
|
||
class Meta:
|
||
verbose_name = "Склад/Участок"; verbose_name_plural = "Склады и участки"
|
||
|
||
def __str__(self): return self.name
|
||
|
||
class StockItem(models.Model):
|
||
"""
|
||
Универсальная физическая единица на складе.
|
||
Логика Вьюх:
|
||
1. Если это сырье: заполнен material, пусто entity.
|
||
2. Если это готовая деталь: заполнен entity, пусто material.
|
||
3. Если is_remnant=True, то current_length/width показывают реальный размер куска.
|
||
При списании в CuttingSession количество здесь уменьшается. Если 0 - можно удалять или скрывать.
|
||
"""
|
||
material = models.ForeignKey(Material, on_delete=models.PROTECT, null=True, blank=True, verbose_name="Сырье")
|
||
entity = models.ForeignKey('manufacturing.ProductEntity', on_delete=models.PROTECT, null=True, blank=True, verbose_name="Произведенная сущность")
|
||
|
||
location = models.ForeignKey(Location, on_delete=models.PROTECT, verbose_name="Где находится")
|
||
quantity = models.FloatField("Количество (шт/м/кг)")
|
||
|
||
# Для деловых остатков
|
||
is_remnant = models.BooleanField("Деловой остаток", default=False)
|
||
current_length = models.FloatField("Текущая длина, мм", null=True, blank=True)
|
||
current_width = models.FloatField("Текущая ширина, мм", null=True, blank=True)
|
||
unique_id = models.CharField("ID/Маркировка (для ДО)", max_length=50, unique=True, null=True, blank=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Единица на складе"; verbose_name_plural = "Остатки на складах"
|
||
|
||
def __str__(self):
|
||
obj = self.entity if self.entity else self.material
|
||
return f"{obj} | {self.quantity} ед. | {self.location}"
|
||
|
||
class TransferRecord(models.Model):
|
||
"""
|
||
Документ перемещения (Вариант Б: строгий учет).
|
||
Логика Вьюх:
|
||
Создается "Отправителем" (статус sent).
|
||
"Получатель" видит его в своем интерфейсе и жмет "Принять" (статус received).
|
||
В этот момент у связанных StockItem меняется location на to_location.
|
||
"""
|
||
STATUS_CHOICES = [('sent', 'В пути'), ('received', 'Принято'), ('discrepancy', 'Расхождение')]
|
||
|
||
items = models.ManyToManyField(StockItem, verbose_name="Перемещаемые объекты")
|
||
from_location = models.ForeignKey(Location, related_name='outgoing', on_delete=models.PROTECT)
|
||
to_location = models.ForeignKey(Location, related_name='incoming', on_delete=models.PROTECT)
|
||
|
||
sender = models.ForeignKey(User, related_name='sent_transfers', on_delete=models.PROTECT)
|
||
receiver = models.ForeignKey(User, related_name='received_transfers', on_delete=models.PROTECT, null=True, blank=True)
|
||
status = models.CharField("Статус", max_length=20, choices=STATUS_CHOICES, default='sent')
|
||
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
received_at = models.DateTimeField(null=True, blank=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Перемещение"; verbose_name_plural = "Перемещения" |