- Attendance form: date range (start+end), Sat/Sun checkboxes, conflict detection with Skip/Overwrite, supervisor auto-set, estimated cost card - Work history: filter by worker/project/payment status, CSV export, payment status badges (Paid/Unpaid) - Supervisor dashboard: stat cards for projects, teams, workers count - Forms: supervisor filtering (non-admins only see their projects/workers) - Navbar: History link now works, cleaned up inline styles in base.html - Management command: setup_groups creates Admin + Work Logger groups - No model/migration changes — database is untouched Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
103 lines
4.2 KiB
Python
103 lines
4.2 KiB
Python
# === FORMS ===
|
|
# Django form classes for the attendance logging page.
|
|
# The AttendanceLogForm handles daily work log creation with support for
|
|
# date ranges, supervisor filtering, and conflict detection.
|
|
|
|
from django import forms
|
|
from .models import WorkLog, Project, Team, Worker
|
|
|
|
|
|
class AttendanceLogForm(forms.ModelForm):
|
|
"""
|
|
Form for logging daily worker attendance.
|
|
|
|
Extra fields (not on the WorkLog model):
|
|
- end_date: optional end date for logging multiple days at once
|
|
- include_saturday: whether to include Saturdays in a date range
|
|
- include_sunday: whether to include Sundays in a date range
|
|
|
|
The supervisor field is NOT shown on the form — it gets set automatically
|
|
in the view to whoever is logged in.
|
|
"""
|
|
|
|
# --- Extra fields for date range logging ---
|
|
# These aren't on the WorkLog model, they're only used by the form
|
|
end_date = forms.DateField(
|
|
required=False,
|
|
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
label='End Date',
|
|
help_text='Leave blank to log a single day'
|
|
)
|
|
include_saturday = forms.BooleanField(
|
|
required=False,
|
|
initial=False,
|
|
label='Include Saturdays',
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
include_sunday = forms.BooleanField(
|
|
required=False,
|
|
initial=False,
|
|
label='Include Sundays',
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
class Meta:
|
|
model = WorkLog
|
|
# Supervisor is NOT included — it gets set in the view automatically
|
|
fields = ['date', 'project', 'team', 'workers', 'overtime_amount', 'notes']
|
|
widgets = {
|
|
'date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'project': forms.Select(attrs={'class': 'form-select'}),
|
|
'team': forms.Select(attrs={'class': 'form-select'}),
|
|
'workers': forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'}),
|
|
'overtime_amount': forms.Select(attrs={'class': 'form-select'}),
|
|
'notes': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Any notes about the day...'
|
|
}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
# Pop 'user' from kwargs so we can filter based on who's logged in
|
|
self.user = kwargs.pop('user', None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# --- Supervisor filtering ---
|
|
# If the user is NOT an admin, they can only see:
|
|
# - Projects they're assigned to (via project.supervisors M2M)
|
|
# - Workers in teams they supervise
|
|
if self.user and not (self.user.is_staff or self.user.is_superuser):
|
|
# Only show projects this supervisor is assigned to
|
|
self.fields['project'].queryset = Project.objects.filter(
|
|
active=True,
|
|
supervisors=self.user
|
|
)
|
|
# Only show workers from teams this supervisor manages
|
|
supervised_teams = Team.objects.filter(supervisor=self.user, active=True)
|
|
self.fields['workers'].queryset = Worker.objects.filter(
|
|
active=True,
|
|
teams__in=supervised_teams
|
|
).distinct()
|
|
# Only show teams this supervisor manages
|
|
self.fields['team'].queryset = supervised_teams
|
|
else:
|
|
# Admins see everything
|
|
self.fields['workers'].queryset = Worker.objects.filter(active=True)
|
|
self.fields['project'].queryset = Project.objects.filter(active=True)
|
|
self.fields['team'].queryset = Team.objects.filter(active=True)
|
|
|
|
# Make team optional (it already is on the model, but make the form match)
|
|
self.fields['team'].required = False
|
|
|
|
def clean(self):
|
|
"""Validate the date range makes sense."""
|
|
cleaned_data = super().clean()
|
|
start_date = cleaned_data.get('date')
|
|
end_date = cleaned_data.get('end_date')
|
|
|
|
if start_date and end_date and end_date < start_date:
|
|
raise forms.ValidationError('End date cannot be before start date.')
|
|
|
|
return cleaned_data
|