56 lines
2.9 KiB
Python
56 lines
2.9 KiB
Python
from decimal import Decimal, ROUND_HALF_UP
|
|
|
|
from django.conf import settings
|
|
from django.core.exceptions import ValidationError
|
|
from django.core.validators import MinValueValidator
|
|
from django.db import models
|
|
|
|
from apps.projects.models import Project, ProjectMember
|
|
|
|
class Task(models.Model):
|
|
class Status(models.TextChoices):
|
|
COMPLETED = "completed", "Выполнена"
|
|
CANCELLED = "cancelled", "Отменена"
|
|
|
|
project = models.ForeignKey(Project, on_delete=models.PROTECT, related_name="tasks")
|
|
assignee = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="tasks")
|
|
title = models.CharField("Задача", max_length=200)
|
|
description = models.TextField("Описание выполненной работы")
|
|
work_date = models.DateField("Дата выполнения")
|
|
duration_minutes = models.PositiveIntegerField("Продолжительность, минут", validators=[MinValueValidator(1)])
|
|
hourly_rate = models.DecimalField("Ставка", max_digits=12, decimal_places=2, validators=[MinValueValidator(Decimal("0"))])
|
|
amount = models.DecimalField("Сумма", max_digits=14, decimal_places=2, validators=[MinValueValidator(Decimal("0"))])
|
|
status = models.CharField("Статус", max_length=16, choices=Status.choices, default=Status.COMPLETED)
|
|
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="created_tasks")
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
ordering = ("-work_date", "-id")
|
|
indexes = [
|
|
models.Index(fields=("assignee", "work_date")),
|
|
models.Index(fields=("project", "work_date")),
|
|
models.Index(fields=("status", "work_date")),
|
|
]
|
|
|
|
@staticmethod
|
|
def calculate_amount(duration_minutes, hourly_rate):
|
|
return (Decimal(duration_minutes) * Decimal(hourly_rate) / Decimal(60)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
|
|
def clean(self):
|
|
super().clean()
|
|
if not self.project_id or not self.assignee_id:
|
|
return
|
|
if not ProjectMember.objects.filter(project_id=self.project_id, user_id=self.assignee_id, is_active=True).exists():
|
|
raise ValidationError({"assignee": "Пользователь не является активным участником проекта."})
|
|
if not self.project.is_active or not self.project.company.is_active:
|
|
raise ValidationError({"project": "Компания и проект должны быть активны."})
|
|
|
|
def apply_current_rate(self):
|
|
membership = ProjectMember.objects.get(project=self.project, user=self.assignee, is_active=True)
|
|
self.hourly_rate = membership.hourly_rate
|
|
self.amount = self.calculate_amount(self.duration_minutes, self.hourly_rate)
|
|
|
|
def __str__(self):
|
|
return f"{self.work_date}: {self.title}"
|