Files
ACORE/apps/tasks/models.py
T
lokses 87cb9de005 i18n: перевод модуля задач (tasks/task-detail/task-edit) ru/uk/en
- Task.STATUS_CHOICES/PRIORITY_CHOICES переведены на русские msgid
  (консистентно с ru-базой); миграция choices
- task_detail_view: метки ролей через gettext
- Шаблоны tasks/task-detail/task-edit размечены {% trans %}; статусы,
  приоритеты, чек-лист, комментарии, участники, кнопки действий — uk/en

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 14:55:48 +03:00

168 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.conf import settings
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from core.models.base import BaseModel
class TaskQuerySet(models.QuerySet):
def for_user(self, user):
"""Задачи, видимые сотруднику: створив сам, він відповідальний,
співвиконавець або спостерігач."""
q = (
models.Q(creator=user)
| models.Q(responsible=user)
| models.Q(co_executors=user)
| models.Q(observers=user)
)
return self.filter(q).distinct()
class Task(BaseModel):
STATUS_CHOICES = [
('pending', _('Ожидает')),
('in_progress', _('В работе')),
('done', _('Выполнено')),
('cancelled', _('Отменено')),
]
PRIORITY_CHOICES = [
('low', _('Низкий')),
('medium', _('Средний')),
('high', _('Высокий')),
]
PRIORITY_BADGES = {'low': 'badge-muted', 'medium': 'badge-warning', 'high': 'badge-danger'}
title = models.CharField(_('Назва'), max_length=250)
description = models.TextField(_('Опис'), blank=True)
status = models.CharField(_('Статус'), max_length=20,
choices=STATUS_CHOICES, default='pending', db_index=True)
priority = models.CharField(_('Пріоритет'), max_length=10,
choices=PRIORITY_CHOICES, default='medium', db_index=True)
due_date = models.DateField(_('Термін'), null=True, blank=True)
creator = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
null=True, related_name='created_tasks', verbose_name=_('Постановник'),
)
# Ролі учасників — за зразком Бітрікс: відповідальний і співвиконавці
# виконують задачу (можуть міняти статус), спостерігачі лише бачать
# і коментують.
responsible = models.ManyToManyField(
settings.AUTH_USER_MODEL, blank=True,
related_name='responsible_tasks', verbose_name=_('Відповідальний'),
)
co_executors = models.ManyToManyField(
settings.AUTH_USER_MODEL, blank=True,
related_name='co_executor_tasks', verbose_name=_('Співвиконавці'),
)
observers = models.ManyToManyField(
settings.AUTH_USER_MODEL, blank=True,
related_name='observed_tasks', verbose_name=_('Спостерігачі'),
)
completed_at = models.DateTimeField(_('Виконано в'), null=True, blank=True)
objects = TaskQuerySet.as_manager()
class Meta:
verbose_name = _('Задача')
verbose_name_plural = _('Задачі')
ordering = ['-created_at']
indexes = [
models.Index(fields=['status', '-created_at']),
models.Index(fields=['due_date']),
]
def __str__(self):
return self.title
@property
def priority_badge(self):
return self.PRIORITY_BADGES.get(self.priority, 'badge-muted')
@property
def is_overdue(self):
return bool(
self.due_date
and self.status in ('pending', 'in_progress')
and self.due_date < timezone.localdate()
)
def is_participant(self, user) -> bool:
"""Может менять статус: автор, відповідальний або співвиконавець.
Спостерігачі — лише бачать і коментують, статус міняти не можуть."""
if self.creator_id == user.pk:
return True
if self.responsible.filter(pk=user.pk).exists():
return True
if self.co_executors.filter(pk=user.pk).exists():
return True
return False
def set_status(self, status: str):
self.status = status
self.completed_at = timezone.now() if status == 'done' else None
self.save(update_fields=['status', 'completed_at', 'updated_at'])
@property
def checklist_total(self) -> int:
return self.checklist.count()
@property
def checklist_done(self) -> int:
return self.checklist.filter(is_done=True).count()
class TaskComment(BaseModel):
task = models.ForeignKey(
Task, on_delete=models.CASCADE,
related_name='comments', verbose_name=_('Задача'),
)
author = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
null=True, related_name='task_comments', verbose_name=_('Автор'),
)
text = models.TextField(_('Коментар'))
class Meta:
verbose_name = _('Коментар до задачі')
verbose_name_plural = _('Коментарі до задач')
ordering = ['created_at']
def __str__(self):
return f'{self.author}: {self.text[:50]}'
class TaskChecklistItem(BaseModel):
"""Пункт чек-листа задачі — виконавець просто відмічає зроблене
(напр. «Додати в 1С Бухгалтерія» при видачі доступів новому співробітнику)."""
task = models.ForeignKey(
Task, on_delete=models.CASCADE,
related_name='checklist', verbose_name=_('Задача'),
)
label = models.CharField(_('Пункт'), max_length=300)
is_done = models.BooleanField(_('Виконано'), default=False)
done_at = models.DateTimeField(_('Виконано в'), null=True, blank=True)
done_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
null=True, blank=True, related_name='+', verbose_name=_('Відмітив'),
)
order = models.PositiveSmallIntegerField(_('Порядок'), default=0)
class Meta:
verbose_name = _('Пункт чек-листа')
verbose_name_plural = _('Чек-лист задачі')
ordering = ['order', 'id']
def __str__(self):
return self.label
def set_done(self, done: bool, user=None):
self.is_done = done
self.done_at = timezone.now() if done else None
self.done_by = user if done else None
self.save(update_fields=['is_done', 'done_at', 'done_by', 'updated_at'])