|
|
|
@ -12,22 +12,25 @@ 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
|
|
|
|
|
|
|
|
|
@ -45,8 +48,10 @@ class CustomerProfile(models.Model):
|
|
|
|
|
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
|
|
|
|
@ -77,7 +82,6 @@ class CustomerProfile(models.Model):
|
|
|
|
|
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)
|
|
|
|
@ -90,12 +94,13 @@ class Business(models.Model):
|
|
|
|
|
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
|
|
|
|
@ -107,35 +112,37 @@ class Business(models.Model):
|
|
|
|
|
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
|
|
|
|
@ -170,8 +177,6 @@ class StaffProfile(models.Model):
|
|
|
|
|
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)
|
|
|
|
@ -182,11 +187,11 @@ class StaffPosition(models.Model):
|
|
|
|
|
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)
|
|
|
|
@ -199,8 +204,10 @@ class Project(models.Model):
|
|
|
|
|
end_date = models.DateField(null=True, blank=True)
|
|
|
|
|
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
|
|
|
|
@ -210,8 +217,10 @@ class Project(models.Model):
|
|
|
|
|
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)
|
|
|
|
@ -229,6 +238,7 @@ class Project(models.Model):
|
|
|
|
|
'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()
|
|
|
|
@ -236,8 +246,6 @@ class Project(models.Model):
|
|
|
|
|
return Task.objects.filter(project=self, assigned_to=user.staffprofile).exclude(status='Closed').count()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectStatus(models.Model):
|
|
|
|
|
STATUS_CHOICES = (
|
|
|
|
|
('In Progress', 'In Progress'),
|
|
|
|
@ -250,13 +258,11 @@ class ProjectStatus(models.Model):
|
|
|
|
|
date = models.DateTimeField()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PinnedProject(models.Model):
|
|
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
|
|
|
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Epic(models.Model):
|
|
|
|
|
title = models.CharField(max_length=150)
|
|
|
|
|
STATUS_CHOICES = (
|
|
|
|
@ -268,22 +274,22 @@ class Epic(models.Model):
|
|
|
|
|
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 Milestone(models.Model):
|
|
|
|
|
name = 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.name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserStory(models.Model):
|
|
|
|
|
milestone = models.ForeignKey(Milestone, on_delete=models.SET_NULL, null=True)
|
|
|
|
|
content = models.CharField(max_length=350)
|
|
|
|
@ -292,6 +298,7 @@ class UserStory(models.Model):
|
|
|
|
|
added_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
|
|
|
|
confirmed = models.BooleanField(default=True)
|
|
|
|
|
completed = models.BooleanField(default=False)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self.content
|
|
|
|
|
|
|
|
|
@ -301,14 +308,12 @@ class ProjectFileAlbum(models.Model):
|
|
|
|
|
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)
|
|
|
|
@ -344,6 +349,7 @@ class Task(models.Model):
|
|
|
|
|
userstory = models.ForeignKey(UserStory, on_delete=models.SET_NULL, null=True, blank=True)
|
|
|
|
|
milestone = models.ForeignKey(Milestone, 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
|
|
|
|
@ -374,8 +380,6 @@ class Task(models.Model):
|
|
|
|
|
return total_time_hours, total_time_minutes, total_time_seconds
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Point(models.Model):
|
|
|
|
|
text = models.TextField(blank=True)
|
|
|
|
|
STATUS_CHOICES = (
|
|
|
|
@ -386,6 +390,7 @@ class Point(models.Model):
|
|
|
|
|
)
|
|
|
|
|
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():
|
|
|
|
@ -397,12 +402,12 @@ class Point(models.Model):
|
|
|
|
|
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
|
|
|
|
@ -430,8 +435,10 @@ class Status(models.Model):
|
|
|
|
|
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:
|
|
|
|
@ -470,6 +477,7 @@ class Status(models.Model):
|
|
|
|
|
return None
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=Status)
|
|
|
|
|
def new_ticket_update_handler(sender, instance, created, **kwargs):
|
|
|
|
|
if created:
|
|
|
|
@ -481,14 +489,12 @@ def new_ticket_update_handler(sender, instance, created, **kwargs):
|
|
|
|
|
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)
|
|
|
|
@ -496,12 +502,9 @@ class DailyReport(models.Model):
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|