Search code examples
pythonpython-3.xdjangodjango-annotatedjango-aggregation

How to find the percentage of employee scores using django


I have a table called Task. It captures task, task score and the employee it is assigned to. I want to calculate the performance of the employee using his total task scores for the week (duration should be dynamic. preferably input field) and the total scores allocated. I am thinking of aggregating the actual scores then dividing buy total score then multiply by 100 to get the percentage. My major challenge is where to perform this calculation. Should I create a model called performance and use a function? Or if can anyone show me how to go about this?

Here is the model;

class Task(models.Model):
    title = model.CharField(max_length=200)
    scores = models.IntegerField(null=True, blank=True)

    def __str__(self):
        return self.name

Here is the formula I want to use. Performance(%) = actual score / total score * 100. Total score should be input field.

UPDATE Here are the relevant models. I am using User model as Employee.

**USER OR EMPLOYEE MODEL**
 
class User(AbstractUser):
    ROLE_CHOICES = (
    ("STAFF", "Staff"),
    ("HOD", "Hod"),
    ("CEO", "Ceo"),
    )

    GENDER_CHOICES = (
    ("Male", "Male"),
    ("Female", "Female")
    )

    CONFIRMATION_CHOICES = (
        ("YES", "yes"),
        ("NO", "no")
    )

    UNDERTAKING_CHOICES = (
        ("YES", "yes"),
        ("NOT YET", "Not yet")
    )

    GENDER_CHOICES = (
        ("Male", "Male"),
        ("Female", "Female")
    )

    EMPLOYEE_TYPE_CHOICES = (
        ("Permanent", "Permanent"),
        ("Contract", "Contract")
    )

    first_name = models.CharField(max_length=100, blank=True)
    last_name = models.CharField(max_length=100, blank=True)
    middle_name = models.CharField(max_length=100, null=True, blank=True)
    email = models.EmailField(null=True)
    avatar =  models.ImageField(upload_to='images/avatar', null=True, blank=True)
    department = models.ForeignKey(Department, related_name='userdept', on_delete=models.CASCADE, null=True)
    role = models.ForeignKey(Role, related_name='userrol', on_delete=models.CASCADE, null=True, blank=True)
    user_role = models.CharField(max_length=100, choices=ROLE_CHOICES, null=True)
    avatar = models.ImageField(upload_to="avatars", null=True, blank=True)
    is_hod = models.BooleanField(default=False)
    is_ceo = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
    gender = models.CharField(max_length=100, choices=GENDER_CHOICES, null=True, blank=True)
    cell_phone = models.CharField(max_length=20, blank=True, null=True)
    employee_id = models.CharField(max_length=200, null=True, blank=True)
    supervisor = models.CharField(max_length=200, null=True, blank=True)
    work_email = models.EmailField(null=True, blank=True)
    work_phone = models.CharField(max_length=20, blank=True)
    start_date = models.CharField(max_length=20, blank=True)
    salary = models.CharField(max_length=20, blank=True, null=True)
    work_location = models.CharField(max_length=20, null=True, blank=True)
    probation_length = models.CharField(max_length=20, null=True, blank=True)
    pay_slip_type = models.CharField(max_length=20, null=True, blank=True)
    can_terminate = models.CharField(max_length=20, null=True, blank=True)
    dob = models.CharField(max_length=20, null=True, blank=True)
    employee_type = models.CharField(max_length=20, choices=EMPLOYEE_TYPE_CHOICES, null=True, blank=True)
    comfirmed = models.CharField(max_length=3, choices=CONFIRMATION_CHOICES, null=True)
    undertaking = models.CharField(max_length=8, choices=UNDERTAKING_CHOICES, null=True)
    leave_days = models.IntegerField(null=True, blank=True) 
    line_manager = models.CharField(max_length=20, null=True, blank=True)   
    last_promotion_date = models.CharField(max_length=20, null=True, blank=True)
    
    
    def __str__(self):
        return self.first_name+' '+self.last_name


**TASK MODEL**

from django.db import models
from departments.models import Department
from akps.models import Akp
from users.models import User
from goals.models import OrganisationGoal, OrganisationSubGoal, DepartmentGoal
from django.template.loader import render_to_string
from django.core.mail import send_mail
from django.core.mail import EmailMultiAlternatives
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.text import slugify
# from django.template.loader import render_to_string
from django.core.mail import send_mail
from django.core.mail import EmailMultiAlternatives
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.text import slugify
from django.core.exceptions import ValidationError


class Task(models.Model):
    STATUS_CHOICES = (
    ("COMPLETED", "completed"),
    ("FAILED", "failed"),
    )
    department = models.ForeignKey(Department, related_name='soussdepartmentt', on_delete=models.CASCADE, null=True)
    akp = models.ForeignKey(Akp, related_name='sousakpp', on_delete=models.CASCADE, null=True)
    organisationgoal = models.ForeignKey(OrganisationGoal, on_delete=models.CASCADE, null=True)
    organisationsubgoal = models.ForeignKey(OrganisationSubGoal, on_delete=models.CASCADE, null=True, blank=True)
    departmentgoal = models.ForeignKey(DepartmentGoal, on_delete=models.CASCADE, related_name='departgoal', null=True, blank=True)
    name = models.CharField(max_length=300, null=True)
    description = models.TextField(max_length=1000, null=True)
    assignedby = models.ForeignKey(User, on_delete=models.CASCADE, related_name='taskassignb', null=True, blank=True)
    assignedto = models.ForeignKey(User, on_delete=models.CASCADE, related_name='taskassignt', null=True, blank=True)
    user = models.ForeignKey(User, related_name='usertas', on_delete=models.CASCADE, null=True)
    scores = models.IntegerField(null=True, blank=True)
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, null=True, blank=True)
    email = models.EmailField(null=True)
    due_date = models.DateField(null=True)
    created_at = models.DateTimeField(auto_now_add=True, null=True)
    updated_at = models.DateTimeField(auto_now=True, null=True)


    def __str__(self):
        return self.name


    def clean(self):
        other_tasks = Task.objects.exclude(pk=self.pk) \
            .filter(departmentgoal=self.departmentgoal)

        if other_tasks.count() > 0:
            contributed = (
                other_tasks.values("departmentgoal")
                .annotate(total=models.Sum("scores"))
                .values_list("total", flat=True)[0]
            )
        else:
            contributed = 0

        if self.scores and self.scores + contributed > self.departmentgoal.scores:
            raise ValidationError({"scores": "Score is too much"})

Here is a scenario;

  • user A has completed 8 out of 10 weekly task. He scored a total of 18 point. NOTE: Weekly scores can not exceed 20 points.

  • The process above is repeated every week. Lets say the second week, he scored 20 points.

  • After either 1 month, 4 months, 6 months or 1 year, I want to calculate his performance and display on his dashboard. The maths is actual score / total score * 100. I believe this can be achieve with the user and task model but i dont know how to implement this.

USER AND DASHBOARD VIEW

from tasks.models import Task
from users.models import User
from goals.models import DepartmentGoal, OrganisationGoal
from django.utils import timezone
from datetime import timedelta

def dashboard(request):
    
    completed_tasks = Task.objects.filter(status="COMPLETED", assignedto=request.user).count()
    failed_tasks = Task.objects.filter(status="FAILED", assignedto=request.user).count()

    debt_complt_goal = DepartmentGoal.objects.filter(status="COMPLETED", department=request.user.department).count()
    debt_failed_goal = DepartmentGoal.objects.filter(status="FAILED", department=request.user.department).count()

    complt_org_goals = OrganisationGoal.objects.filter(status="COMPLETED").count()
    failed_org_goals = OrganisationGoal.objects.filter(status="FAILED").count()

    return render (request, 'home.html', {
        'completed_tasks':completed_tasks, 
        'failed_tasks':failed_tasks, 
        'debt_complt_goal':debt_complt_goal,
        'debt_failed_goal':debt_failed_goal,
        'complt_org_goals':complt_org_goals,
        'failed_org_goals':failed_org_goals,
    })



from django.shortcuts import render, redirect, get_object_or_404, HttpResponse
from .models import Profile, User
from .forms import ProfileForm, CreateUserForm, UserSetPasswordForm
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import authenticate, login, logout
from django.contrib import messages


def list_users_view(request):
    department = request.user.department
    dept_users = User.objects.filter(department=department)
    users = User.objects.all()
    context = {
        'users':users,
        'dept_users':dept_users
        }
    return render(request, 'users/list-users.html', context)

Solution

  • Here is the criteria that I interpreted:

    1. A task is associated with a user using the assignedto field.
    2. The task has a due date which will be used against the time range parameters when calculating performance.
    3. The task is either completed or failed.
    4. If the user completes the task, the user will acquire the score associated with the task.

    Solution

    Add a custom method on your User model to calculate the performance. The method will ask for a start and end date arguments to calculate the performance between a time range.

    from django.db.models import Sum, Q
    
    
    class User(AbstractUser):
        ...
    
        def performance(self, start, end):
            actual = Sum("scores", filter=Q(status="completed"))
    
            q = self.taskassignt.filter(
                due_date__gte=start,
                due_date__lt=end  # `end` is excluded from the range
            ).annotate(actual=actual, total=Sum("scores"))
    
            return (q[0].actual / q[0].total) * 100
    

    Note the time range implementation excludes end from the range since it uses the lt (less than) operator.

    Usage of the method is:

    from django.utils import timezone
    from datetime import timedelta
    
    
    # user is a `User` instance
    user.performance(timezone.now() + timedelta(days=-7), timezone.now())