from django.db import models from django.contrib.auth.models import User from colorfield.fields import ColorField from datetime import datetime from django.db.models import Max from django.utils import timezone from django.db.models import Sum, F from datetime import timedelta from django.db.models.signals import post_save from django.dispatch import receiver from asgiref.sync import async_to_sync from channels.layers import get_channel_layer from datetime import timedelta # Create your models here. class Reference(models.Model): name = models.CharField(max_length=50) date = models.DateField() def __str__(self): return self.name class Tag(models.Model): name = models.CharField(max_length=50) def __str__(self): return self.name class BusinessType(models.Model): name = models.CharField(max_length=50) def __str__(self): return self.name class CustomerProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, blank=True) mobile_number = models.CharField(max_length=50, blank=True) personal_website = models.URLField(null=True, blank=True) STATUS_CHOICES = ( ('Pending', 'Pending'), ('Active', 'Active'), ('Suspended', 'Suspended'), ('Terminated', 'Terminated'), ) status = models.CharField(max_length=200, choices=STATUS_CHOICES, default='Active', blank=True) reference = models.ForeignKey(Reference, on_delete=models.CASCADE, null=True, blank=True) customer_id = models.CharField(max_length=20, null=True, blank=True) def __str__(self): return self.user.username def save(self, *args, **kwargs): if not self.customer_id: # Get the last two digits of the current year current_year = str(datetime.now().year)[-2:] # Find the maximum project ID in the database and increment it max_id = CustomerProfile.objects.aggregate(models.Max('customer_id'))['customer_id__max'] new_id = str(int(max_id[-4:]) + 1).zfill(4) if max_id else '0001' # If no existing records, start with '0001' self.customer_id = current_year + new_id # Add 'p' prefix super(CustomerProfile, self).save(*args, **kwargs) @property def get_last_seen(self): connection = Connection.objects.filter(user=self.user).last() if not connection: return "Not seen yet" if connection.online: return "Online" last_seen_time = connection.last_seen now = timezone.now() time_diff = now - last_seen_time if time_diff < timedelta(days=1): if last_seen_time.date() == now.date(): return f"last seen today at {last_seen_time.strftime('%I:%M %p')}" else: return f"last seen yesterday at {last_seen_time.strftime('%I:%M %p')}" else: return f"last seen on {last_seen_time.strftime('%b %d at %I:%M %p')}" class Business(models.Model): name = models.CharField(max_length=50) email = models.EmailField(unique=True) financial_number = models.CharField(max_length=50) vat = models.BooleanField(default=False) commercial_registration = models.CharField(max_length=50) phone_number = models.CharField(max_length=50) website = models.URLField(null=True) customer = models.ForeignKey(CustomerProfile, on_delete=models.CASCADE, null=True, blank=True) type = models.ForeignKey(BusinessType, on_delete=models.CASCADE, null=True, blank=True) logo = models.ImageField(upload_to='uploaded_images') business_id = models.CharField(max_length=20, null=True, blank=True) def __str__(self): return self.name class Meta: verbose_name_plural = u'Businesses' def __str__(self): return self.name def save(self, *args, **kwargs): if not self.business_id: # Get the last two digits of the current year current_year = str(datetime.now().year)[-2:] # Find the maximum project ID in the database and increment it max_id = Business.objects.aggregate(models.Max('business_id'))['business_id__max'] new_id = str(int(max_id[-4:]) + 1).zfill(4) if max_id else '0001' # If no existing records, start with '0001' self.business_id = 'B' + current_year + new_id # Add 'p' prefix super(Business, self).save(*args, **kwargs) class Department(models.Model): name = models.CharField(max_length=100) def __str__(self): return self.name def get_staff(self): # Retrieve all staff profiles associated with positions in this department staff_profiles = StaffProfile.objects.filter(staffposition__position__department=self) return staff_profiles class JobPosition(models.Model): name = models.CharField(max_length=100) department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True) def __str__(self): return self.name class StaffProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) image = models.ImageField(upload_to='uploaded_images', null=True, blank=True) mobile_number = models.CharField(max_length=50) intern = models.BooleanField(default=False) staff_id = models.CharField(max_length=20, null=True, blank=True) # Allow null and blank for initial creation def __str__(self): return self.user.username def save(self, *args, **kwargs): if not self.staff_id: # Get the last two digits of the current year current_year = str(datetime.now().year)[-2:] # Find the maximum project ID in the database and increment it max_id = StaffProfile.objects.aggregate(models.Max('staff_id'))['staff_id__max'] new_id = str(int(max_id[-4:]) + 1).zfill(4) if max_id else '0001' # If no existing records, start with '0001' self.staff_id = 'O' + current_year + new_id # Add 'p' prefix super(StaffProfile, self).save(*args, **kwargs) @property def get_last_seen(self): connection = Connection.objects.filter(user=self.user).last() if not connection: return "Not seen yet" if connection.online: return "Online" last_seen_time = connection.last_seen now = timezone.now() time_diff = now - last_seen_time if time_diff < timedelta(days=1): if last_seen_time.date() == now.date(): return f"last seen today at {last_seen_time.strftime('%I:%M %p')}" else: return f"last seen yesterday at {last_seen_time.strftime('%I:%M %p')}" else: return f"last seen on {last_seen_time.strftime('%b %d at %I:%M %p')}" @property def active(self): return self.staffposition_set.filter(end_date__isnull=True).exists() class StaffPosition(models.Model): staff = models.ForeignKey(StaffProfile, on_delete=models.CASCADE) position = models.ForeignKey(JobPosition, null=True, on_delete=models.SET_NULL) start_date = models.DateField() end_date = models.DateField(null=True, blank=True) class ProjectType(models.Model): name = models.CharField(max_length=50) department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True) def __str__(self): return self.name class Project(models.Model): name = models.CharField(max_length=50) logo = models.ImageField(null=True, blank=True) customer = models.ForeignKey(CustomerProfile, on_delete=models.CASCADE, null=True) manager = models.ForeignKey(StaffProfile, on_delete=models.CASCADE, null=True) project_type = models.ManyToManyField('ProjectType', default='', related_name='manager_project') details = models.TextField() members = models.ManyToManyField('StaffProfile', default='', related_name='members_project') start_date = models.DateField() end_date = models.DateField() active = models.BooleanField(default=True, null=True) project_id = models.CharField(max_length=20, null=True, blank=True) def __str__(self): return self.name def save(self, *args, **kwargs): if not self.project_id: # Get the last two digits of the current year current_year = str(datetime.now().year)[-2:] # Find the maximum project ID in the database and increment it max_id = Project.objects.aggregate(models.Max('project_id'))['project_id__max'] new_id = str(int(max_id[-4:]) + 1).zfill(4) if max_id else '0001' # If no existing records, start with '0001' self.project_id = 'P' + current_year + new_id # Add 'p' prefix super(Project, self).save(*args, **kwargs) def is_pinned(self, user): return PinnedProject.objects.filter(user=user, project=self).exists() def total_time_worked(self, user): total_time_seconds = 0 tasks = self.task_set.all() if user.is_superuser else self.task_set.filter(assigned_to=user.staffprofile) for task in tasks: total_time_hours, total_time_minutes, total_time_seconds_task = task.total_task_time() total_time_seconds += (total_time_hours * 3600) + (total_time_minutes * 60) + total_time_seconds_task total_time_hours = total_time_seconds // 3600 total_time_minutes = (total_time_seconds % 3600) // 60 total_time_seconds = total_time_seconds % 60 return { 'hours': total_time_hours, 'minutes': total_time_minutes, 'seconds': total_time_seconds } def open_tasks_count(self, user): if user.is_superuser: return Task.objects.filter(project=self).exclude(status='Closed').count() else: return Task.objects.filter(project=self, assigned_to=user.staffprofile).exclude(status='Closed').count() class ProjectStatus(models.Model): STATUS_CHOICES = ( ('In Progress', 'In Progress'), ('Completed', 'Completed'), ('Pending', 'Pending'), ('Cancelled', 'Cancelled'), ) project = models.ForeignKey(Project, on_delete=models.CASCADE) status = models.CharField(max_length=200, choices=STATUS_CHOICES) date = models.DateTimeField() class PinnedProject(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) project = models.ForeignKey(Project, on_delete=models.CASCADE) class Milestone(models.Model): title = models.CharField(max_length=150) description = models.TextField() project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True) start_date = models.DateField() end_date = models.DateField() def __str__(self): return self.title class Epic(models.Model): title = models.CharField(max_length=150) STATUS_CHOICES = ( ('Open', 'Open'), ('Closed', 'Closed') ) status = models.CharField(max_length=200, choices=STATUS_CHOICES, null=True) description = models.TextField() project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True) start_date = models.CharField(max_length=200) end_date = models.CharField(max_length=200) def __str__(self): return self.title class ProjectRequirement(models.Model): content = models.CharField(max_length=350) project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True) date = models.DateField(null=True, auto_now=True) added_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) def __str__(self): return self.content class ProjectFileAlbum(models.Model): name = models.CharField(max_length=350, null=True) project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True) class ProjectFile(models.Model): album = models.ForeignKey(ProjectFileAlbum, on_delete=models.CASCADE, null=True) file = models.FileField(null=True, upload_to='project_files') date_added = models.DateTimeField(null=True) class ProjectCredential(models.Model): identifier = models.CharField(max_length=350) password = models.CharField(max_length=350) description = models.TextField(null=True, blank=True) project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True) date_added = models.DateTimeField(null=True) class Note(models.Model): text = models.TextField(blank=True) date = models.DateTimeField(null=True,blank=True) user = models.ForeignKey(User, on_delete=models.CASCADE, null=True,blank=True) color = ColorField(default='#FF0000',blank=True) project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True, blank=True) class Task(models.Model): name = models.CharField(max_length=250) project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True) epic = models.ForeignKey(Epic, on_delete=models.CASCADE, null=True) STATUS_CHOICES = ( ('Open', 'Open'), ('Working On', 'Working On'), ('Closed', 'Closed') ) status = models.CharField(max_length=200, choices=STATUS_CHOICES, null=True) status_date = models.DateTimeField(default=timezone.now) extra = models.BooleanField(default=False) description = models.TextField() start_date = models.DateField(null=True) end_date = models.DateField(null=True) assigned_to = models.ForeignKey(StaffProfile, on_delete=models.CASCADE, null=True) requirement = models.ForeignKey(ProjectRequirement, on_delete=models.SET_NULL, null=True, blank=True) task_id = models.CharField(max_length=20, null=True, blank=True) def save(self, *args, **kwargs): if not self.task_id: # Get the last two digits of the current year current_year = str(datetime.now().year)[-2:] # Find the maximum project ID in the database and increment it max_id = Task.objects.aggregate(models.Max('task_id'))['task_id__max'] new_id = str(int(max_id[-4:]) + 1).zfill(4) if max_id else '0001' # If no existing records, start with '0001' self.task_id = 'T' + current_year + new_id # Add 'p' prefix if self.pk is not None: original_task = Task.objects.get(pk=self.pk) if original_task.status != self.status: self.status_date = timezone.now() super(Task, self).save(*args, **kwargs) def total_task_time(self): total_time_seconds = 0 for point in self.point_set.all(): if point: point_hours, point_minutes, point_seconds = point.total_activity_time() total_time_seconds += (point_hours * 3600) + (point_minutes * 60) + point_seconds total_time_hours = total_time_seconds // 3600 total_time_minutes = (total_time_seconds % 3600) // 60 total_time_seconds = total_time_seconds % 60 return total_time_hours, total_time_minutes, total_time_seconds class Point(models.Model): text = models.TextField(blank=True) STATUS_CHOICES = ( ('Not Completed', 'Not Completed'), ('Working On', 'Working On'), ('Paused', 'Paused'), ('Completed', 'Completed') ) status = models.CharField(max_length=200, choices=STATUS_CHOICES, null=True) task = models.ForeignKey(Task, on_delete=models.CASCADE, null=True) def total_activity_time(self): total_time_seconds = 0 for activity in self.pointactivity_set.all(): if activity.total_time: total_time_seconds += activity.total_time.total_seconds() total_time_hours = int(total_time_seconds // 3600) total_time_minutes = int((total_time_seconds % 3600) // 60) total_time_seconds = int(total_time_seconds % 60) return total_time_hours, total_time_minutes, total_time_seconds class PointActivity(models.Model): point = models.ForeignKey(Point, on_delete=models.CASCADE, null=True) start_time = models.DateTimeField() end_time = models.DateTimeField(null=True, blank=True) total_time = models.DurationField(null=True, blank=True) def save(self, *args, **kwargs): if self.start_time and not self.end_time: self.total_time = timezone.now() - self.start_time elif self.start_time and self.end_time: self.total_time = self.end_time - self.start_time super(PointActivity, self).save(*args, **kwargs) def total_time_in_hours_minutes_seconds(self): if self.total_time: total_seconds = self.total_time.total_seconds() hours = int(total_seconds // 3600) minutes = int((total_seconds % 3600) // 60) seconds = int(total_seconds % 60) return hours, minutes, seconds return 0, 0, 0 class Status(models.Model): TYPE_CHOICES = ( ('Task', 'Task'), ('Daily Report', 'Daily Report'), ) text = models.TextField(blank=True) staff = models.ForeignKey(StaffProfile, on_delete=models.CASCADE, null=True,blank=True, related_name='staff') type = models.CharField(max_length=200, choices=TYPE_CHOICES, null=True, blank=True) type_id = models.IntegerField(null=True, blank=True) date_time = models.DateTimeField(null=True, blank=True) def __str__(self): return self.text @property def time_ago(self): if not self.date_time: return "No date provided" now = timezone.now() diff = now - self.date_time if diff < timedelta(hours=1): minutes = int(diff.total_seconds() / 60) return f"{minutes}min ago" elif diff < timedelta(days=1): hours = int(diff.total_seconds() / 3600) minutes = int((diff.total_seconds() % 3600) / 60) return f"{hours}hr {minutes}min ago" else: return self.date_time.strftime('%d %m %Y, %I:%M %p') @property def project(self): if self.type == 'Task' and self.type_id: try: task = Task.objects.get(id=self.type_id) return task.project.id except Task.DoesNotExist: return None return None @receiver(post_save, sender=Status) def new_ticket_update_handler(sender, instance, created, **kwargs): if created: channel_layer = get_channel_layer() event = { 'type': 'new_status_event', 'status_id': instance.id, } async_to_sync(channel_layer.group_send)("new_status_group", event) class Reaction(models.Model): status = models.ForeignKey(Status, on_delete=models.CASCADE) emoji = models.CharField(max_length=15) user = models.ForeignKey(User, on_delete=models.CASCADE) class DailyReport(models.Model): text = models.TextField(blank=True) date = models.CharField(max_length=40) time = models.CharField(max_length=40) staff = models.ForeignKey(StaffProfile, on_delete=models.CASCADE, null=True,blank=True, related_name='dailyreport_staff') class Connection(models.Model): date = models.DateTimeField(null=True) user = models.OneToOneField(User, on_delete=models.CASCADE) online = models.BooleanField(default=True) last_seen = models.DateTimeField(null=True, blank=True) disconnected = models.BooleanField(default=False)