IT-менеджер: каталог моделей оборудования + перевод choices на справочники

- EquipmentModel (каталог: тип+бренд+модель заводятся раз) отделён от
  Equipment (экземпляр: серийник+локация); миграция сворачивает данные.
  Массовый ввод серийников, массовая выдача на локации/сотрудников
- Enum-choices (тип/статус/приоритет заявок, вид работ, каналы, расходники,
  договоры) переведены на core.Directory с quick-add из формы
- Фронт-страница /it-manager/: вкладки оборудование/заявки/работы/
  расходники/провайдеры/обращения; searchable-select локаций
- Тесты (10)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 17:31:52 +03:00
parent f342d67f97
commit 38747a93fc
23 changed files with 3051 additions and 229 deletions
+14 -24
View File
@@ -2,18 +2,10 @@ from django.db import models
from django.conf import settings
from django.utils import timezone
from core.models.directories import directory_default
class WorkLog(models.Model):
WORK_TYPES = [
('maintenance', 'Техническое обслуживание'),
('installation', 'Установка / настройка'),
('repair', 'Ремонт'),
('network', 'Сетевые работы'),
('software', 'Программное обеспечение'),
('backup', 'Резервное копирование'),
('other', 'Другое'),
]
date = models.DateField('Дата', default=timezone.localdate, db_index=True)
technician = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
@@ -24,7 +16,11 @@ class WorkLog(models.Model):
null=True, blank=True,
related_name='work_logs', verbose_name='Локация',
)
work_type = models.CharField('Вид работ', max_length=20, choices=WORK_TYPES, db_index=True)
work_type = models.ForeignKey(
'core.Directory', on_delete=models.PROTECT,
limit_choices_to={'directory_type__code': 'worklog_type', 'is_active': True},
related_name='worklogs_by_type', verbose_name='Вид работ',
)
description = models.TextField('Описание работ')
result = models.TextField('Результат', blank=True)
hours_spent = models.DecimalField('Затрачено часов', max_digits=5, decimal_places=1,
@@ -50,22 +46,13 @@ class WorkLog(models.Model):
ordering = ['-date', '-created_at']
indexes = [
models.Index(fields=['date', 'technician']),
models.Index(fields=['work_type']),
]
def __str__(self):
return f'{self.get_work_type_display()} — {self.technician} — {self.date:%d.%m.%Y}'
return f'{self.work_type.name} — {self.technician} — {self.date:%d.%m.%Y}'
class ServiceCall(models.Model):
CHANNEL_CHOICES = [
('phone', 'Телефон'),
('chat', 'Чат / мессенджер'),
('email', 'Email'),
('personal', 'Личный визит'),
('other', 'Другое'),
]
date_time = models.DateTimeField('Дата и время', default=timezone.now, db_index=True)
caller = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
@@ -76,7 +63,11 @@ class ServiceCall(models.Model):
null=True, blank=True,
related_name='service_calls_handled', verbose_name='Принял обращение',
)
channel = models.CharField('Канал', max_length=20, choices=CHANNEL_CHOICES, db_index=True)
channel = models.ForeignKey(
'core.Directory', on_delete=models.PROTECT,
limit_choices_to={'directory_type__code': 'servicecall_channel', 'is_active': True},
related_name='servicecalls_by_channel', verbose_name='Канал',
)
location = models.ForeignKey(
'locations.Location', on_delete=models.SET_NULL,
null=True, blank=True,
@@ -100,11 +91,10 @@ class ServiceCall(models.Model):
ordering = ['-date_time']
indexes = [
models.Index(fields=['date_time', 'handler']),
models.Index(fields=['channel']),
]
def __str__(self):
return (f'{self.get_channel_display()} — {self.caller} — '
return (f'{self.channel.name} — {self.caller} — '
f'{self.date_time:%d.%m.%Y %H:%M}')
@property