31bd4c8fa7
Категория/работа/required_details вместо свободного work_type; seed Directory, отчёт и тесты. Co-authored-by: Cursor <cursoragent@cursor.com>
126 lines
5.1 KiB
Python
126 lines
5.1 KiB
Python
from django.db import models
|
|
from django.conf import settings
|
|
from django.utils import timezone
|
|
|
|
|
|
class WorkLog(models.Model):
|
|
date = models.DateField('Дата', default=timezone.localdate, db_index=True)
|
|
technician = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
|
|
related_name='work_logs', verbose_name='Исполнитель',
|
|
)
|
|
location = models.ForeignKey(
|
|
'locations.Location', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='work_logs', verbose_name='Локация',
|
|
)
|
|
# Устаревший грубый вид (~7 типов); для новых записей не заполняется.
|
|
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='Вид работ',
|
|
null=True, blank=True,
|
|
)
|
|
catalog_work = models.ForeignKey(
|
|
'core.Directory', on_delete=models.PROTECT,
|
|
limit_choices_to={'directory_type__code': 'it_work', 'is_active': True},
|
|
related_name='worklogs_by_catalog', verbose_name='Типовая работа',
|
|
null=True, blank=True,
|
|
)
|
|
details = models.JSONField('Уточнения', default=dict, blank=True)
|
|
description = models.TextField('Комментарий', blank=True)
|
|
result = models.TextField('Результат', blank=True)
|
|
hours_spent = models.DecimalField('Затрачено часов', max_digits=5, decimal_places=1,
|
|
null=True, blank=True)
|
|
|
|
equipment = models.ForeignKey(
|
|
'it_manager.Equipment', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='work_logs', verbose_name='Оборудование',
|
|
)
|
|
ticket = models.ForeignKey(
|
|
'it_manager.Ticket', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='work_logs', verbose_name='Заявка',
|
|
)
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
verbose_name = 'Выполненная работа'
|
|
verbose_name_plural = 'Журнал выполненных работ'
|
|
ordering = ['-date', '-created_at']
|
|
indexes = [
|
|
models.Index(fields=['date', 'technician']),
|
|
]
|
|
|
|
def __str__(self):
|
|
if self.catalog_work_id:
|
|
title = self.catalog_work.name
|
|
elif self.work_type_id:
|
|
title = self.work_type.name
|
|
else:
|
|
title = (self.description or '—')[:40]
|
|
return f'{title} — {self.technician} — {self.date:%d.%m.%Y}'
|
|
|
|
@property
|
|
def display_title(self) -> str:
|
|
from apps.it_manager.work_directory import worklog_display_title
|
|
return worklog_display_title(self)
|
|
|
|
|
|
class ServiceCall(models.Model):
|
|
date_time = models.DateTimeField('Дата и время', default=timezone.now, db_index=True)
|
|
caller = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
|
|
related_name='service_calls_made', verbose_name='Кто обратился',
|
|
)
|
|
handler = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='service_calls_handled', verbose_name='Принял обращение',
|
|
)
|
|
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,
|
|
related_name='service_calls', verbose_name='Локация',
|
|
)
|
|
description = models.TextField('Описание обращения')
|
|
resolution = models.TextField('Решение', blank=True)
|
|
minutes_spent = models.PositiveSmallIntegerField('Затрачено минут', null=True, blank=True)
|
|
|
|
ticket = models.ForeignKey(
|
|
'it_manager.Ticket', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='service_calls', verbose_name='Связанная заявка',
|
|
help_text='Если обращение переросло в тикет',
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
verbose_name = 'Обращение'
|
|
verbose_name_plural = 'Обращения'
|
|
ordering = ['-date_time']
|
|
indexes = [
|
|
models.Index(fields=['date_time', 'handler']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return (f'{self.channel.name} — {self.caller} — '
|
|
f'{self.date_time:%d.%m.%Y %H:%M}')
|
|
|
|
@property
|
|
def duration_display(self):
|
|
if not self.minutes_spent:
|
|
return '—'
|
|
if self.minutes_spent < 60:
|
|
return f'{self.minutes_spent} мин.'
|
|
h, m = divmod(self.minutes_spent, 60)
|
|
return f'{h}ч {m}м' if m else f'{h}ч'
|