# === FORMS === # Django form classes for the app. # - AttendanceLogForm: daily work log creation with date ranges and conflict detection # - PayrollAdjustmentForm: adding bonuses, deductions, overtime, and loan adjustments from django import forms from .models import WorkLog, Project, Team, Worker, PayrollAdjustment 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 class PayrollAdjustmentForm(forms.ModelForm): """ Form for adding/editing payroll adjustments (bonuses, deductions, etc.). Business rule: A project is required for Overtime, Bonus, Deduction, and Advance Payment types. Loan and Loan Repayment are worker-level (no project). """ class Meta: model = PayrollAdjustment fields = ['type', 'project', 'worker', 'amount', 'date', 'description'] widgets = { 'type': forms.Select(attrs={'class': 'form-select'}), 'project': forms.Select(attrs={'class': 'form-select'}), 'worker': forms.Select(attrs={'class': 'form-select'}), 'amount': forms.NumberInput(attrs={ 'class': 'form-control', 'step': '0.01', 'min': '0.01' }), 'date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'description': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 2, 'placeholder': 'Reason for this adjustment...' }), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['project'].queryset = Project.objects.filter(active=True) self.fields['project'].required = False self.fields['worker'].queryset = Worker.objects.filter(active=True) def clean(self): """Validate that project-required types have a project selected.""" cleaned_data = super().clean() adj_type = cleaned_data.get('type', '') project = cleaned_data.get('project') # These types must have a project — they're tied to specific work project_required_types = ('Overtime', 'Bonus', 'Deduction', 'Advance Payment') if adj_type in project_required_types and not project: self.add_error('project', 'A project must be selected for this adjustment type.') return cleaned_data