+
{{ form.team }}
{% if form.team.errors %}
@@ -93,22 +107,22 @@
-
The following workers have already been logged for {{ conflict_date }}:
-
+
The following duplicate entries were found:
+
- {% for worker in conflicting_workers %}
- - {{ worker.name }}
+ {% for conflict in conflicting_workers %}
+ - {{ conflict.name }}
{% endfor %}
How would you like to proceed?
- Skip: Log only the new workers. Keep existing logs as they are.
- Overwrite: Remove these workers from previous logs for this date and add them here.
+ Skip: Log only the new entries. Existing logs remain unchanged.
+ Overwrite: Update existing logs for these dates with the new project/team selection.
@@ -139,9 +153,14 @@
const teamId = this.value;
if (teamId && teamWorkersMap[teamId]) {
const workerIds = teamWorkersMap[teamId];
- // Select workers belonging to the team
+ // Uncheck all first? No, maybe append. Let's append as per common expectations unless explicit clear needed.
+ // Actually, if I change team, I probably expect to select THAT team's workers.
+ // Let's clear and select.
+ // But maybe I want to mix teams.
+ // User didn't specify. Previous logic was: select workers belonging to team.
+ // Let's stick to "select", don't uncheck others.
+
workerIds.forEach(function(id) {
- // Find the checkbox for this worker ID
const checkbox = document.querySelector(`input[name="workers"][value="${id}"]`);
if (checkbox) {
checkbox.checked = true;
diff --git a/core/views.py b/core/views.py
index 7f3606f..05ee53e 100644
--- a/core/views.py
+++ b/core/views.py
@@ -79,65 +79,108 @@ def log_attendance(request):
if request.method == 'POST':
form = WorkLogForm(request.POST, user=request.user)
if form.is_valid():
- date = form.cleaned_data['date']
+ start_date = form.cleaned_data['date']
+ end_date = form.cleaned_data.get('end_date')
+ include_sat = form.cleaned_data.get('include_saturday')
+ include_sun = form.cleaned_data.get('include_sunday')
selected_workers = form.cleaned_data['workers']
+ project = form.cleaned_data['project']
+ notes = form.cleaned_data['notes']
conflict_action = request.POST.get('conflict_action')
- # Check for existing logs for these workers on this date
- # We want to find workers who ARE in selected_workers AND have a WorkLog on 'date'
- conflicting_workers = Worker.objects.filter(
- work_logs__date=date,
- id__in=selected_workers.values_list('id', flat=True)
- ).distinct()
-
- if conflicting_workers.exists() and not conflict_action:
- context = {
- 'form': form,
- 'team_workers_json': json.dumps(team_workers_map),
- 'conflicting_workers': conflicting_workers,
- 'is_conflict': True,
- 'conflict_date': date,
- }
- return render(request, 'core/log_attendance.html', context)
-
- # If we are here, either no conflicts or action is chosen
- workers_to_save = list(selected_workers)
-
- if conflict_action == 'skip':
- # Exclude conflicting workers
- conflicting_ids = conflicting_workers.values_list('id', flat=True)
- workers_to_save = [w for w in selected_workers if w.id not in conflicting_ids]
-
- if not workers_to_save:
- messages.warning(request, "No new workers to log (all skipped).")
- return redirect('home')
-
- messages.success(request, f"Logged {len(workers_to_save)} workers (skipped {conflicting_workers.count()} duplicates).")
-
- elif conflict_action == 'overwrite':
- # Remove conflicting workers from their OLD logs
- for worker in conflicting_workers:
- old_logs = WorkLog.objects.filter(date=date, workers=worker)
- for log in old_logs:
- log.workers.remove(worker)
- # Cleanup empty logs
- if log.workers.count() == 0:
- log.delete()
- messages.success(request, f"Logged {len(workers_to_save)} workers (overwrote {conflicting_workers.count()} previous entries).")
-
+ # Generate Target Dates
+ target_dates = []
+ if end_date and end_date >= start_date:
+ curr = start_date
+ while curr <= end_date:
+ # 5 = Saturday, 6 = Sunday
+ if (curr.weekday() == 5 and not include_sat) or (curr.weekday() == 6 and not include_sun):
+ curr += timedelta(days=1)
+ continue
+ target_dates.append(curr)
+ curr += timedelta(days=1)
else:
- # No conflicts initially
- messages.success(request, "Work log saved successfully.")
+ target_dates = [start_date]
- # Save the new log
- work_log = form.save(commit=False)
- if request.user.is_authenticated:
- work_log.supervisor = request.user
- work_log.save()
+ if not target_dates:
+ messages.warning(request, "No valid dates selected (check weekends).")
+ return render(request, 'core/log_attendance.html', {
+ 'form': form, 'team_workers_json': json.dumps(team_workers_map)
+ })
+
+ # Check Conflicts - Scan all target dates
+ if not conflict_action:
+ conflicts = []
+ for d in target_dates:
+ # Find workers who already have a log on this date
+ existing_logs = WorkLog.objects.filter(date=d, workers__in=selected_workers).distinct()
+ for log in existing_logs:
+ # Which of the selected workers are in this log?
+ for w in log.workers.all():
+ if w in selected_workers:
+ # Avoid adding duplicates if multiple logs exist for same worker/day (rare but possible)
+ conflict_entry = {'name': f"{w.name} ({d.strftime('%Y-%m-%d')})"}
+ if conflict_entry not in conflicts:
+ conflicts.append(conflict_entry)
+
+ if conflicts:
+ context = {
+ 'form': form,
+ 'team_workers_json': json.dumps(team_workers_map),
+ 'conflicting_workers': conflicts,
+ 'is_conflict': True,
+ }
+ return render(request, 'core/log_attendance.html', context)
- # Manually set workers
- work_log.workers.set(workers_to_save)
+ # Execution Phase
+ created_count = 0
+ skipped_count = 0
+ overwritten_count = 0
+ for d in target_dates:
+ # Find conflicts for this specific day
+ day_conflicts = Worker.objects.filter(
+ work_logs__date=d,
+ id__in=selected_workers.values_list('id', flat=True)
+ ).distinct()
+
+ workers_to_save = list(selected_workers)
+
+ if day_conflicts.exists():
+ if conflict_action == 'skip':
+ conflicting_ids = day_conflicts.values_list('id', flat=True)
+ workers_to_save = [w for w in selected_workers if w.id not in conflicting_ids]
+ skipped_count += day_conflicts.count()
+
+ elif conflict_action == 'overwrite':
+ # Remove conflicting workers from their OLD logs
+ for worker in day_conflicts:
+ old_logs = WorkLog.objects.filter(date=d, workers=worker)
+ for log in old_logs:
+ log.workers.remove(worker)
+ if log.workers.count() == 0:
+ log.delete()
+ overwritten_count += day_conflicts.count()
+ # workers_to_save remains full list
+
+ if workers_to_save:
+ # Create Log
+ log = WorkLog.objects.create(
+ date=d,
+ project=project,
+ notes=notes,
+ supervisor=request.user if request.user.is_authenticated else None
+ )
+ log.workers.set(workers_to_save)
+ created_count += len(workers_to_save)
+
+ msg = f"Logged {created_count} entries."
+ if skipped_count:
+ msg += f" Skipped {skipped_count} duplicates."
+ if overwritten_count:
+ msg += f" Overwrote {overwritten_count} previous entries."
+
+ messages.success(request, msg)
return redirect('home')
else:
form = WorkLogForm(user=request.user if request.user.is_authenticated else None)
@@ -567,4 +610,4 @@ def add_adjustment(request):
)
messages.success(request, f"{adj_type} of R{amount} added for {worker.name}.")
- return redirect('payroll_dashboard')
\ No newline at end of file
+ return redirect('payroll_dashboard')
diff --git a/static/css/custom.css b/static/css/custom.css
index 13343f9..8767672 100644
--- a/static/css/custom.css
+++ b/static/css/custom.css
@@ -23,6 +23,12 @@ h1, h2, h3, .heading-font {
font-weight: 700;
}
+.navbar {
+ position: sticky;
+ top: 0;
+ z-index: 1000;
+}
+
.dashboard-header {
background: linear-gradient(135deg, var(--primary-color) 0%, #334155 100%);
color: var(--white);