SciPost Code Repository

Skip to content
Snippets Groups Projects
Commit 4817cff8 authored by George Katsikas's avatar George Katsikas :goat:
Browse files

add monthly salary to timesheet view

refactor worklog filtering code
change log form to use crispy
parent 0128ef92
No related branches found
No related tags found
1 merge request!46Add monthly salary to timesheet view
...@@ -211,16 +211,36 @@ class LogsFilterForm(forms.Form): ...@@ -211,16 +211,36 @@ class LogsFilterForm(forms.Form):
employee = UserModelChoiceField( employee = UserModelChoiceField(
queryset=get_user_model().objects.filter(work_logs__isnull=False).distinct(), queryset=get_user_model().objects.filter(work_logs__isnull=False).distinct(),
required=False, required=False,
empty_label="Show all", empty_label="All",
) )
start = forms.DateField(required=True, widget=forms.SelectDateWidget()) start = forms.DateField(
end = forms.DateField(required=True, widget=forms.SelectDateWidget()) required=True, widget=forms.TextInput(attrs={"type": "date"})
)
end = forms.DateField(required=True, widget=forms.TextInput(attrs={"type": "date"}))
hourly_rate = forms.FloatField(min_value=0, initial=WorkLog.HOURLY_RATE)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
today = timezone.now().date() today = timezone.now().date()
self.initial["start"] = today.today()
self.initial["end"] = today.today() if not any(self.fields[field].initial for field in ["start", "end"]):
current_month = datetime.date.today().replace(day=1)
last_month_end = current_month - datetime.timedelta(days=1)
last_month_start = last_month_end.replace(day=1)
self.fields["start"].initial = last_month_start
self.fields["end"].initial = last_month_end
self.helper = FormHelper()
self.helper.layout = Layout(
Div(
Div(FloatingField("employee"), css_class="col-9 col-md"),
Div(FloatingField("hourly_rate"), css_class="col-3 col-md-2"),
Div(FloatingField("start"), css_class="col-6 col-md-auto col-lg-2"),
Div(FloatingField("end"), css_class="col-6 col-md-auto col-lg-2"),
css_class="row mb-0 mt-2",
),
Submit("submit", "Filter"),
)
def clean(self): def clean(self):
if self.is_valid(): if self.is_valid():
...@@ -249,6 +269,7 @@ class LogsFilterForm(forms.Form): ...@@ -249,6 +269,7 @@ class LogsFilterForm(forms.Form):
) )
else: else:
user_qs = get_user_model().objects.filter(work_logs__isnull=False) user_qs = get_user_model().objects.filter(work_logs__isnull=False)
user_qs = user_qs.filter( user_qs = user_qs.filter(
work_logs__work_date__gte=self.cleaned_data["start"], work_logs__work_date__gte=self.cleaned_data["start"],
work_logs__work_date__lte=self.cleaned_data["end"], work_logs__work_date__lte=self.cleaned_data["end"],
...@@ -280,24 +301,49 @@ class LogsFilterForm(forms.Form): ...@@ -280,24 +301,49 @@ class LogsFilterForm(forms.Form):
) )
else: else:
user_qs = get_user_model().objects.filter(work_logs__isnull=False) user_qs = get_user_model().objects.filter(work_logs__isnull=False)
user_qs = user_qs.filter( user_qs = user_qs.filter(
work_logs__work_date__gte=self.cleaned_data["start"], work_logs__work_date__gte=self.cleaned_data["start"],
work_logs__work_date__lte=self.cleaned_data["end"], work_logs__work_date__lte=self.cleaned_data["end"],
).distinct() ).distinct()
work_log_qs = WorkLog.objects.filter(
work_date__gte=self.cleaned_data["start"],
work_date__lte=self.cleaned_data["end"],
user__in=user_qs,
)
output = [] output = []
for user in user_qs: for user in user_qs:
# If logs exists for given filters # If logs exists for given filters
total_time_per_month = [
work_log_qs.filter(
work_date__year=dt.year, work_date__month=dt.month, user=user
).aggregate(Sum("duration"))["duration__sum"]
for dt in self.get_months()
]
if self.cleaned_data["hourly_rate"]:
salary_per_month = [
duration.total_seconds()
/ 3600 # Convert to hours
* self.cleaned_data["hourly_rate"]
if duration is not None
else 0
for duration in total_time_per_month
]
else:
salary_per_month = []
output.append( output.append(
{ {
"logs": [], "monthly_data": zip(
self.get_months(),
total_time_per_month,
salary_per_month,
),
"user": user, "user": user,
} }
) )
for dt in self.get_months():
output[-1]["logs"].append(
user.work_logs.filter(
work_date__year=dt.year, work_date__month=dt.month
).aggregate(total=Sum("duration"))["total"]
)
return output return output
...@@ -286,6 +286,8 @@ class SubsidyAttachment(models.Model): ...@@ -286,6 +286,8 @@ class SubsidyAttachment(models.Model):
class WorkLog(models.Model): class WorkLog(models.Model):
HOURLY_RATE = 22.0
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
comments = models.TextField(blank=True) comments = models.TextField(blank=True)
log_type = models.CharField(max_length=128, blank=True) log_type = models.CharField(max_length=128, blank=True)
......
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
<span class="breadcrumb-item">Team timesheets</span> <span class="breadcrumb-item">Team timesheets</span>
{% endblock %} {% endblock %}
{% block pagetitle %}: Team timesheets{% endblock pagetitle %} {% block pagetitle %}
: Team timesheets
{% endblock pagetitle %}
{% load bootstrap %} {% load crispy_forms_tags %}
{% load scipost_extras %} {% load scipost_extras %}
{% block content %} {% block content %}
...@@ -20,20 +22,7 @@ ...@@ -20,20 +22,7 @@
<br> <br>
<form method="get"> <form method="get">
{{ form.employee|bootstrap }} {% crispy form %}
<label>Date from</label>
<div class="form-row">
{{ form.start }}
</div>
<label>Date until</label>
<div class="form-row">
{{ form.end }}
</div>
<br>
<input type="submit" class="btn btn-primary" value="Filter">
</form> </form>
</div> </div>
</div> </div>
...@@ -43,25 +32,26 @@ ...@@ -43,25 +32,26 @@
{% if form.is_bound and form.is_valid %} {% if form.is_bound and form.is_valid %}
<h2 class="mb-2 mt-4">Team timesheets</h2> <h2 class="mb-2 mt-4">Team timesheets</h2>
<h4 class="mb-1">{{ user_log.user.first_name }} {{ user_log.user.last_name }}</h4> <h4 class="mb-1">{{ user_log.user.first_name }} {{ user_log.user.last_name }}</h4>
<table class="table table-hover"> <table class="table table-hover text-nowrap">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th>Employee</th> <th>Employee</th>
{% for month in form.get_months %} {% for month in form.get_months %}<th colspan="2" scope="colgroup">{{ month|date:'N Y' }}</th>{% endfor %}
<th>{{ month|date:'N Y' }}</th>
{% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for user_log in form.filter_per_month %} {% for user_data in form.filter_per_month %}
<tr> <tr>
<td>{{ user_log.user.last_name }}, {{ user_log.user.first_name }}</td> <td>{{ user_data.user.contributor }}</td>
{% for log in user_log.logs %} {% for _, total_time, monthly_salary in user_data.monthly_data %}
<td>{{ log|duration }}</td> <td>{{ total_time|duration }}</td>
<td>€{{ monthly_salary|floatformat:0 }}</td>
{% endfor %} {% endfor %}
</tr> </tr>
{% empty %} {% empty %}
<tr><td colspan="5">No logs found.</td></tr> <tr>
<td colspan="5">No logs found.</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
......
...@@ -6,9 +6,11 @@ ...@@ -6,9 +6,11 @@
<span class="breadcrumb-item">Detailed timesheets</span> <span class="breadcrumb-item">Detailed timesheets</span>
{% endblock %} {% endblock %}
{% block pagetitle %}: Team timesheets{% endblock pagetitle %} {% block pagetitle %}
: Team timesheets
{% endblock pagetitle %}
{% load bootstrap %} {% load crispy_forms_tags %}
{% load scipost_extras %} {% load scipost_extras %}
{% block content %} {% block content %}
...@@ -19,20 +21,7 @@ ...@@ -19,20 +21,7 @@
<br> <br>
<form method="get"> <form method="get">
{{form.employee|bootstrap }} {% crispy form %}
<label>Date from</label>
<div class="form-row">
{{ form.start }}
</div>
<label>Date until</label>
<div class="form-row">
{{ form.end }}
</div>
<br>
<input type="submit" class="btn btn-primary" value="Filter">
</form> </form>
</div> </div>
</div> </div>
...@@ -65,7 +54,9 @@ ...@@ -65,7 +54,9 @@
{% endfor %} {% endfor %}
<tr> <tr>
<td colspan="4" class="text-end">Total:</td> <td colspan="4" class="text-end">Total:</td>
<td><strong>{{ user_log.duration.total|duration }}</strong></td> <td>
<strong>{{ user_log.duration.total|duration }}</strong>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment