38747a93fc
- EquipmentModel (каталог: тип+бренд+модель заводятся раз) отделён от Equipment (экземпляр: серийник+локация); миграция сворачивает данные. Массовый ввод серийников, массовая выдача на локации/сотрудников - Enum-choices (тип/статус/приоритет заявок, вид работ, каналы, расходники, договоры) переведены на core.Directory с quick-add из формы - Фронт-страница /it-manager/: вкладки оборудование/заявки/работы/ расходники/провайдеры/обращения; searchable-select локаций - Тесты (10) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
4.5 KiB
Python
103 lines
4.5 KiB
Python
from django.db import models
|
||
from django.utils import timezone
|
||
|
||
from core.models.directories import directory_default
|
||
|
||
|
||
class InternetProvider(models.Model):
|
||
name = models.CharField('Название', max_length=200, unique=True)
|
||
phone = models.CharField('Телефон', max_length=50, blank=True)
|
||
email = models.EmailField('Email', blank=True)
|
||
website = models.URLField('Сайт', blank=True)
|
||
support_phone = models.CharField('Телефон техподдержки', max_length=50, blank=True)
|
||
support_email = models.EmailField('Email техподдержки', blank=True)
|
||
notes = models.TextField('Примечания', blank=True)
|
||
is_active = models.BooleanField('Активен', default=True)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
|
||
class Meta:
|
||
verbose_name = 'Провайдер'
|
||
verbose_name_plural = 'Провайдеры'
|
||
ordering = ['name']
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
@property
|
||
def active_contracts_count(self):
|
||
return self.contracts.filter(status__code='active').count()
|
||
|
||
|
||
class InternetContract(models.Model):
|
||
provider = models.ForeignKey(
|
||
InternetProvider, on_delete=models.PROTECT,
|
||
related_name='contracts', verbose_name='Провайдер',
|
||
)
|
||
location = models.ForeignKey(
|
||
'locations.Location', on_delete=models.PROTECT,
|
||
related_name='internet_contracts', verbose_name='Локация',
|
||
)
|
||
|
||
contract_number = models.CharField('Номер договора', max_length=100)
|
||
service_type = models.ForeignKey(
|
||
'core.Directory', on_delete=models.PROTECT,
|
||
limit_choices_to={'directory_type__code': 'contract_service_type', 'is_active': True},
|
||
related_name='contracts_by_service_type', verbose_name='Тип услуги',
|
||
default=directory_default('contract_service_type', 'internet'),
|
||
)
|
||
connection_type = models.ForeignKey(
|
||
'core.Directory', on_delete=models.SET_NULL,
|
||
limit_choices_to={'directory_type__code': 'contract_connection_type', 'is_active': True},
|
||
related_name='contracts_by_connection_type', verbose_name='Технология подключения',
|
||
null=True, blank=True,
|
||
)
|
||
status = models.ForeignKey(
|
||
'core.Directory', on_delete=models.PROTECT,
|
||
limit_choices_to={'directory_type__code': 'contract_status', 'is_active': True},
|
||
related_name='contracts_by_status', verbose_name='Статус',
|
||
default=directory_default('contract_status', 'active'),
|
||
)
|
||
|
||
start_date = models.DateField('Дата начала')
|
||
end_date = models.DateField('Дата окончания', null=True, blank=True)
|
||
|
||
monthly_cost = models.DecimalField('Стоимость в месяц', max_digits=10, decimal_places=2,
|
||
null=True, blank=True)
|
||
bandwidth_down = models.PositiveIntegerField('Скорость вниз (Мбит/с)', null=True, blank=True)
|
||
bandwidth_up = models.PositiveIntegerField('Скорость вверх (Мбит/с)', null=True, blank=True)
|
||
|
||
static_ips = models.TextField('Статические IP', blank=True,
|
||
help_text='По одному на строку')
|
||
account_number = models.CharField('Лицевой счёт', max_length=100, 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 = ['location', 'provider']
|
||
unique_together = [('provider', 'contract_number')]
|
||
|
||
def __str__(self):
|
||
return f'{self.provider} — {self.location} (№{self.contract_number})'
|
||
|
||
@property
|
||
def is_expiring_soon(self):
|
||
if not self.end_date or self.status.code != 'active':
|
||
return False
|
||
return (self.end_date - timezone.now().date()).days <= 30
|
||
|
||
@property
|
||
def bandwidth_display(self):
|
||
if self.bandwidth_down and self.bandwidth_up:
|
||
return f'{self.bandwidth_down}↓ / {self.bandwidth_up}↑ Мбит/с'
|
||
if self.bandwidth_down:
|
||
return f'{self.bandwidth_down} Мбит/с'
|
||
return '—'
|
||
|
||
@property
|
||
def static_ip_list(self):
|
||
return [ip.strip() for ip in self.static_ips.splitlines() if ip.strip()]
|