from django.contrib.auth import get_user_model from django.db import models from apps.logs.services import build_workspace_log_metadata from apps.logs.services.constants import SECTION_PROJECTS, SECTION_PROJECT_MEMBERS from core.models.base import BaseModel from apps.workspaces.models import Workspace User = get_user_model() class Project(BaseModel): workspace = models.ForeignKey( Workspace, on_delete=models.CASCADE, related_name="projects", ) name = models.CharField(max_length=255) client = models.ForeignKey( "clients.Client", on_delete=models.SET_NULL, null=True, blank=True, related_name="projects", ) description = models.TextField(blank=True) is_archived = models.BooleanField(default=False) color = models.CharField(max_length=7, blank=True) class Meta: db_table = "project" ordering = ("-updated_at", "-created_at") indexes = [ models.Index(fields=["workspace"], name="project_workspace_idx"), ] constraints = [ models.UniqueConstraint( fields=["workspace", "name"], name="unique_project_name_per_workspace", condition=models.Q(is_deleted=False), ) ] def __str__(self): return self.name def get_additional_data(self): return build_workspace_log_metadata( section=SECTION_PROJECTS, workspace_id=self.workspace_id, target_id=self.id, target_label=self.name, extra={"client_id": str(self.client_id) if self.client_id else None}, ) class ProjectMembership(BaseModel): class Role(models.TextChoices): MANAGER = "manager", "Manager" MEMBER = "member", "Member" project = models.ForeignKey( Project, on_delete=models.CASCADE, related_name="memberships", ) user = models.ForeignKey( User, on_delete=models.CASCADE, related_name="project_memberships", ) role = models.CharField( max_length=20, choices=Role.choices, default=Role.MEMBER, ) is_active = models.BooleanField(default=True) class Meta: db_table = "project_membership" ordering = ("-created_at",) indexes = [ models.Index(fields=["project"], name="project_membership_project_idx"), models.Index(fields=["user"], name="project_membership_user_idx"), ] constraints = [ models.UniqueConstraint( fields=["project", "user"], name="unique_project_membership", condition=models.Q(is_deleted=False), ) ] def __str__(self): return f"{self.user} @ {self.project}" def get_additional_data(self): return build_workspace_log_metadata( section=SECTION_PROJECT_MEMBERS, workspace_id=self.project.workspace_id, target_id=self.id, target_label=self.user.full_name or self.user.mobile, extra={ "project_id": str(self.project_id), "member_user_id": str(self.user_id), "role": self.role, }, ) class ProjectRate(BaseModel): project = models.ForeignKey( Project, on_delete=models.CASCADE, related_name="rates", ) hourly_rate = models.DecimalField( max_digits=10, decimal_places=2, ) currency = models.CharField( max_length=3, default="USD", ) effective_from = models.DateTimeField() is_active = models.BooleanField(default=True) class Meta: db_table = "project_rate" ordering = ("-effective_from",) indexes = [ models.Index(fields=["project"], name="project_rate_project_idx"), ] class ProjectUserRate(BaseModel): project = models.ForeignKey( Project, on_delete=models.CASCADE, related_name="user_rates", ) user = models.ForeignKey( User, on_delete=models.CASCADE, related_name="project_rates", ) hourly_rate = models.DecimalField( max_digits=10, decimal_places=2, ) currency = models.CharField( max_length=3, default="USD", ) effective_from = models.DateTimeField() is_active = models.BooleanField(default=True) class Meta: db_table = "project_user_rate" ordering = ("-effective_from",) constraints = [ models.UniqueConstraint( fields=["project", "user", "effective_from"], name="unique_project_user_rate_time", condition=models.Q(is_deleted=False), ) ] indexes = [ models.Index(fields=["project"], name="pur_project_idx"), models.Index(fields=["user"], name="pur_user_idx"), ]