38747a93fc
- EquipmentModel (каталог: тип+бренд+модель заводятся раз) отделён от Equipment (экземпляр: серийник+локация); миграция сворачивает данные. Массовый ввод серийников, массовая выдача на локации/сотрудников - Enum-choices (тип/статус/приоритет заявок, вид работ, каналы, расходники, договоры) переведены на core.Directory с quick-add из формы - Фронт-страница /it-manager/: вкладки оборудование/заявки/работы/ расходники/провайдеры/обращения; searchable-select локаций - Тесты (10) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.9 KiB
Python
68 lines
2.9 KiB
Python
from django.db import models
|
|
from django.utils import timezone
|
|
|
|
from core.models.directories import directory_default
|
|
|
|
|
|
class Consumable(models.Model):
|
|
consumable_type = models.ForeignKey(
|
|
'core.Directory', on_delete=models.PROTECT,
|
|
limit_choices_to={'directory_type__code': 'consumable_type', 'is_active': True},
|
|
related_name='consumables_by_type', verbose_name='Тип',
|
|
default=directory_default('consumable_type', 'cartridge'),
|
|
)
|
|
brand = models.CharField('Производитель', max_length=100, blank=True)
|
|
model_number = models.CharField('Модель / артикул', max_length=100)
|
|
color = models.ForeignKey(
|
|
'core.Directory', on_delete=models.PROTECT,
|
|
limit_choices_to={'directory_type__code': 'consumable_color', 'is_active': True},
|
|
related_name='consumables_by_color', verbose_name='Цвет',
|
|
default=directory_default('consumable_color', 'black'),
|
|
)
|
|
status = models.ForeignKey(
|
|
'core.Directory', on_delete=models.PROTECT,
|
|
limit_choices_to={'directory_type__code': 'consumable_status', 'is_active': True},
|
|
related_name='consumables_by_status', verbose_name='Статус',
|
|
default=directory_default('consumable_status', 'in_stock'),
|
|
)
|
|
|
|
equipment = models.ForeignKey(
|
|
'it_manager.Equipment', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='consumables', verbose_name='Устройство',
|
|
limit_choices_to={'equipment_model__equipment_type__code': 'printer'},
|
|
)
|
|
location = models.ForeignKey(
|
|
'locations.Location', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='consumables', verbose_name='Локация хранения',
|
|
)
|
|
|
|
page_yield = models.PositiveIntegerField('Ресурс (страниц)', null=True, blank=True)
|
|
purchase_date = models.DateField('Дата покупки', null=True, blank=True)
|
|
installed_date = models.DateField('Дата установки', null=True, blank=True)
|
|
replaced_date = models.DateField('Дата замены', null=True, blank=True)
|
|
|
|
notes = models.TextField('Примечания', blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
verbose_name = 'Расходник'
|
|
verbose_name_plural = 'Расходники'
|
|
ordering = ['equipment', 'consumable_type__name', 'color__name']
|
|
|
|
def __str__(self):
|
|
parts = [self.consumable_type.name]
|
|
if self.brand:
|
|
parts.append(self.brand)
|
|
parts.append(self.model_number)
|
|
if self.color.code != 'na':
|
|
parts.append(f'({self.color.name})')
|
|
return ' '.join(parts)
|
|
|
|
@property
|
|
def is_low(self):
|
|
"""Нужно заказывать: закончился или заказан."""
|
|
return self.status.code in ('empty', 'ordered')
|