diff --git a/config/settings.py b/config/settings.py index 318a684..9d8bcde 100644 --- a/config/settings.py +++ b/config/settings.py @@ -157,27 +157,29 @@ STATICFILES_DIRS = [ MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media' -# Email +# === EMAIL CONFIGURATION === +# Uses Gmail SMTP with an App Password to send payslip PDFs and receipts. +# The App Password is a 16-character code from Google Account settings — +# it lets the app send email through Gmail without your actual password. EMAIL_BACKEND = os.getenv( "EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend" ) -EMAIL_HOST = os.getenv("EMAIL_HOST", "127.0.0.1") +EMAIL_HOST = os.getenv("EMAIL_HOST", "smtp.gmail.com") EMAIL_PORT = int(os.getenv("EMAIL_PORT", "587")) -EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "") -EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "") +EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "konrad@foxfitt.co.za") +EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "cwvhpcwyijneukax") EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", "true").lower() == "true" EMAIL_USE_SSL = os.getenv("EMAIL_USE_SSL", "false").lower() == "true" -DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "no-reply@example.com") +DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "konrad+foxlog@foxfitt.co.za") CONTACT_EMAIL_TO = [ item.strip() for item in os.getenv("CONTACT_EMAIL_TO", DEFAULT_FROM_EMAIL).split(",") if item.strip() ] -# Spark Receipt Email — payslip and receipt PDFs are sent here for accounting -# Set SPARK_RECEIPT_EMAIL in your .env file (e.g. receipts@spark.co.za) -SPARK_RECEIPT_EMAIL = os.getenv("SPARK_RECEIPT_EMAIL", "") +# Spark Receipt Email — payslip and receipt PDFs are sent here for accounting import +SPARK_RECEIPT_EMAIL = os.getenv("SPARK_RECEIPT_EMAIL", "foxfitt-ed9wc+expense@to.sparkreceipt.com") # When both TLS and SSL flags are enabled, prefer SSL explicitly if EMAIL_USE_SSL: diff --git a/core/templates/core/attendance_log.html b/core/templates/core/attendance_log.html index e8a18a1..6903ade 100644 --- a/core/templates/core/attendance_log.html +++ b/core/templates/core/attendance_log.html @@ -215,12 +215,34 @@ document.addEventListener('DOMContentLoaded', function() { // === TEAM AUTO-SELECT === // When a team is chosen from the dropdown, automatically check all workers - // that belong to that team. We do this with a data attribute approach. - const teamSelect = document.querySelector('[name="team"]'); + // that belong to that team. Uses team_workers_json passed from the view. + var teamWorkersMap = JSON.parse('{{ team_workers_json|escapejs }}'); + var teamSelect = document.querySelector('[name="team"]'); if (teamSelect) { teamSelect.addEventListener('change', function() { - // Team auto-select would need team-worker mapping from backend. - // For now, we'll handle this server-side if needed in a future phase. + var teamId = this.value; + + // First, uncheck ALL worker checkboxes + var allBoxes = document.querySelectorAll('input[name="workers"]'); + allBoxes.forEach(function(cb) { + cb.checked = false; + }); + + // Then check workers that belong to the selected team + if (teamId && teamWorkersMap[teamId]) { + var workerIds = teamWorkersMap[teamId]; + workerIds.forEach(function(id) { + var checkbox = document.querySelector('input[name="workers"][value="' + id + '"]'); + if (checkbox) { + checkbox.checked = true; + } + }); + } + + // Recalculate estimated cost if the admin cost calculator exists + if (typeof updateEstimatedCost === 'function') { + updateEstimatedCost(); + } }); } diff --git a/core/views.py b/core/views.py index 3f11e67..9720608 100644 --- a/core/views.py +++ b/core/views.py @@ -223,9 +223,14 @@ def attendance_log(request): if not dates_to_log: messages.warning(request, 'No valid dates in the selected range.') + # Still need team_workers_json for the JS even on error re-render + tw_map = {} + for t in Team.objects.filter(active=True).prefetch_related('workers'): + tw_map[t.id] = list(t.workers.filter(active=True).values_list('id', flat=True)) return render(request, 'core/attendance_log.html', { 'form': form, 'is_admin': is_admin(user), + 'team_workers_json': json.dumps(tw_map), }) # --- Conflict detection --- @@ -250,10 +255,15 @@ def attendance_log(request): conflict_action = request.POST.get('conflict_action', '') if conflicts and not conflict_action: # Show the conflict warning — let user choose Skip or Overwrite + # Still need team_workers_json for the JS even on conflict re-render + tw_map = {} + for t in Team.objects.filter(active=True).prefetch_related('workers'): + tw_map[t.id] = list(t.workers.filter(active=True).values_list('id', flat=True)) return render(request, 'core/attendance_log.html', { 'form': form, 'conflicts': conflicts, 'is_admin': is_admin(user), + 'team_workers_json': json.dumps(tw_map), }) # --- Create work logs --- @@ -323,10 +333,24 @@ def attendance_log(request): for w in Worker.objects.filter(active=True): worker_rates[str(w.id)] = str(w.daily_rate) + # Build team→workers mapping so the JS can auto-check workers when a + # team is selected from the dropdown. Key = team ID, Value = list of worker IDs. + team_workers_map = {} + teams_qs = Team.objects.filter(active=True).prefetch_related('workers') + if not is_admin(user): + # Supervisors only see their own teams + teams_qs = teams_qs.filter(supervisor=user) + for team in teams_qs: + active_worker_ids = list( + team.workers.filter(active=True).values_list('id', flat=True) + ) + team_workers_map[team.id] = active_worker_ids + return render(request, 'core/attendance_log.html', { 'form': form, 'is_admin': is_admin(user), 'worker_rates_json': worker_rates, + 'team_workers_json': json.dumps(team_workers_map), })