Fix attendance start date, history worker filter, and add Amount column
1. Attendance form: Force start date to blank by clearing Django 5.x auto-fill from model default (default=timezone.now). Added self.fields['date'].initial=None in AttendanceLogForm.__init__(). 2. History list view: When filtering by a specific worker, show only that worker's name in the Workers column (not all workers on the log). Uses filtered_worker_obj passed from the view. 3. History list view: Added Amount column (admin-only) showing daily cost. When filtering by worker, shows that worker's daily_rate. When unfiltered, shows total via new WorkLog.display_amount property (sum of all workers' daily_rate, uses prefetch cache for efficiency). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7fd32a0aee
commit
b6fca98c17
@ -92,6 +92,11 @@ class AttendanceLogForm(forms.ModelForm):
|
|||||||
# Make team optional (it already is on the model, but make the form match)
|
# Make team optional (it already is on the model, but make the form match)
|
||||||
self.fields['team'].required = False
|
self.fields['team'].required = False
|
||||||
|
|
||||||
|
# Force start date to be blank — don't pre-fill with today's date.
|
||||||
|
# Django 5.x auto-fills form fields from model defaults (default=timezone.now),
|
||||||
|
# but we want the user to consciously pick a date every time.
|
||||||
|
self.fields['date'].initial = None
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Validate the date range makes sense."""
|
"""Validate the date range makes sense."""
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|||||||
@ -77,6 +77,12 @@ class WorkLog(models.Model):
|
|||||||
overtime_amount = models.DecimalField(max_digits=3, decimal_places=2, choices=OVERTIME_CHOICES, default=Decimal('0.00'))
|
overtime_amount = models.DecimalField(max_digits=3, decimal_places=2, choices=OVERTIME_CHOICES, default=Decimal('0.00'))
|
||||||
priced_workers = models.ManyToManyField(Worker, related_name='priced_overtime_logs', blank=True)
|
priced_workers = models.ManyToManyField(Worker, related_name='priced_overtime_logs', blank=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_amount(self):
|
||||||
|
"""Total daily cost for all workers on this log (sum of daily_rate).
|
||||||
|
Works efficiently with prefetch_related('workers')."""
|
||||||
|
return sum(w.daily_rate for w in self.workers.all())
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.date} - {self.project.name}"
|
return f"{self.date} - {self.project.name}"
|
||||||
|
|
||||||
|
|||||||
@ -592,6 +592,7 @@
|
|||||||
<th scope="col">Workers</th>
|
<th scope="col">Workers</th>
|
||||||
<th scope="col">Overtime</th>
|
<th scope="col">Overtime</th>
|
||||||
<th scope="col">Status</th>
|
<th scope="col">Status</th>
|
||||||
|
{% if is_admin %}<th scope="col">Amount</th>{% endif %}
|
||||||
<th scope="col" class="pe-4">Supervisor</th>
|
<th scope="col" class="pe-4">Supervisor</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -601,11 +602,16 @@
|
|||||||
<td class="ps-4 align-middle">{{ log.date }}</td>
|
<td class="ps-4 align-middle">{{ log.date }}</td>
|
||||||
<td class="align-middle"><strong>{{ log.project.name }}</strong></td>
|
<td class="align-middle"><strong>{{ log.project.name }}</strong></td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{# Show worker names as comma-separated list #}
|
{# When filtering by a specific worker, show only that worker.
|
||||||
{% for w in log.workers.all %}
|
Otherwise show all workers on this log. #}
|
||||||
{{ w.name }}{% if not forloop.last %}, {% endif %}
|
{% if filtered_worker_obj %}
|
||||||
{% endfor %}
|
{{ filtered_worker_obj.name }}
|
||||||
<span class="badge bg-secondary ms-1">{{ log.workers.count }}</span>
|
{% else %}
|
||||||
|
{% for w in log.workers.all %}
|
||||||
|
{{ w.name }}{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<span class="badge bg-secondary ms-1">{{ log.workers.count }}</span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{% if log.overtime_amount > 0 %}
|
{% if log.overtime_amount > 0 %}
|
||||||
@ -622,6 +628,17 @@
|
|||||||
<span class="badge bg-danger bg-opacity-75"><i class="fas fa-clock me-1"></i>Unpaid</span>
|
<span class="badge bg-danger bg-opacity-75"><i class="fas fa-clock me-1"></i>Unpaid</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
{% if is_admin %}
|
||||||
|
<td class="align-middle">
|
||||||
|
{# Daily cost — when filtering by worker show that worker's rate,
|
||||||
|
otherwise show the total for all workers on the log #}
|
||||||
|
{% if filtered_worker_obj %}
|
||||||
|
<span class="text-success fw-semibold">R {{ filtered_worker_obj.daily_rate }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-success fw-semibold">R {{ log.display_amount }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td class="pe-4 align-middle">
|
<td class="pe-4 align-middle">
|
||||||
{% if log.supervisor %}
|
{% if log.supervisor %}
|
||||||
{{ log.supervisor.get_full_name|default:log.supervisor.username }}
|
{{ log.supervisor.get_full_name|default:log.supervisor.username }}
|
||||||
@ -632,7 +649,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="text-center py-5 text-muted">
|
<td colspan="{% if is_admin %}7{% else %}6{% endif %}" class="text-center py-5 text-muted">
|
||||||
<i class="fas fa-inbox fa-2x mb-3 d-block opacity-50"></i>
|
<i class="fas fa-inbox fa-2x mb-3 d-block opacity-50"></i>
|
||||||
No work history found.
|
No work history found.
|
||||||
{% if selected_worker or selected_project or selected_status %}
|
{% if selected_worker or selected_project or selected_status %}
|
||||||
|
|||||||
@ -413,6 +413,12 @@ def work_history(request):
|
|||||||
# Count filtered results BEFORE adding joins (more efficient SQL)
|
# Count filtered results BEFORE adding joins (more efficient SQL)
|
||||||
filtered_log_count = logs.count() if has_active_filters else 0
|
filtered_log_count = logs.count() if has_active_filters else 0
|
||||||
|
|
||||||
|
# If filtering by worker, look up the Worker object so the template can
|
||||||
|
# show just that worker's name instead of all workers on the log.
|
||||||
|
filtered_worker_obj = None
|
||||||
|
if worker_filter:
|
||||||
|
filtered_worker_obj = Worker.objects.filter(id=worker_filter).first()
|
||||||
|
|
||||||
# Add related data and order by date (newest first)
|
# Add related data and order by date (newest first)
|
||||||
logs = logs.select_related(
|
logs = logs.select_related(
|
||||||
'project', 'supervisor'
|
'project', 'supervisor'
|
||||||
@ -458,6 +464,7 @@ def work_history(request):
|
|||||||
'has_active_filters': has_active_filters,
|
'has_active_filters': has_active_filters,
|
||||||
'total_log_count': total_log_count,
|
'total_log_count': total_log_count,
|
||||||
'filtered_log_count': filtered_log_count,
|
'filtered_log_count': filtered_log_count,
|
||||||
|
'filtered_worker_obj': filtered_worker_obj,
|
||||||
}
|
}
|
||||||
|
|
||||||
# === CALENDAR MODE ===
|
# === CALENDAR MODE ===
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user