diff --git a/app/routes/parking.py b/app/routes/parking.py index 4aca29f..e4700bd 100644 --- a/app/routes/parking.py +++ b/app/routes/parking.py @@ -443,29 +443,34 @@ def get_eligible_users(assignment_id: str, db: Session = Depends(get_db), curren return result -from typing import Optional +from typing import Optional, Union class TestEmailRequest(BaseModel): - date: Optional[date] = None + date: Optional[str] = None office_id: str + bulk_send: bool = False # New flag @router.post("/test-email") def send_test_email_tool(data: TestEmailRequest, db: Session = Depends(get_db), current_user=Depends(require_manager_or_admin)): - """Send a test email to the current user (Test Tool)""" - from services.notifications import send_email - from database.models import OfficeClosingDay, OfficeWeeklyClosingDay - from datetime import timedelta - + """Send a test email to the current user OR bulk reminder to all (Test Tool)""" + from services.notifications import send_email, send_daily_parking_reminder + from database.models import OfficeClosingDay, OfficeWeeklyClosingDay, User + from datetime import timedelta, datetime # Verify office access if current_user.role == UserRole.MANAGER and current_user.office_id != data.office_id: raise HTTPException(status_code=403, detail="Not authorized for this office") - target_date = data.date + target_date = None + if data.date: + try: + target_date = datetime.strptime(data.date, "%Y-%m-%d").date() + except ValueError: + raise HTTPException(status_code=422, detail="Invalid date format. Use YYYY-MM-DD") + if not target_date: - # Find next open day - # Start from tomorrow (or today? Prompt says "dopo il giorno corrente" -> after today) + # Find next open day logic (same as before) check_date = date.today() + timedelta(days=1) # Load closing rules @@ -479,15 +484,11 @@ def send_test_email_tool(data: TestEmailRequest, db: Session = Depends(get_db), OfficeClosingDay.date >= check_date ).all() - # Max lookahead 30 days to avoid infinite loop found = False for _ in range(30): - # Check weekly if check_date.weekday() in weekly_closed_set: check_date += timedelta(days=1) continue - - # Check specific is_specific = False for d in specific_closed: s = d.date @@ -495,31 +496,89 @@ def send_test_email_tool(data: TestEmailRequest, db: Session = Depends(get_db), if s <= check_date <= e: is_specific = True break - if is_specific: check_date += timedelta(days=1) continue - found = True break - if found: - target_date = check_date - else: - # Fallback - target_date = date.today() + timedelta(days=1) - - # Send Email - subject = f"Test Email - Parking System ({target_date})" + target_date = check_date if found else date.today() + timedelta(days=1) + + # BULK MODE + if data.bulk_send: + # Get all assignments for this date/office + assignments = db.query(DailyParkingAssignment).filter( + DailyParkingAssignment.office_id == data.office_id, + DailyParkingAssignment.date == target_date, + DailyParkingAssignment.user_id.isnot(None) + ).all() + + count = 0 + failed = 0 + + # Convert date to datetime for the existing function signature if needed, or update function + # send_daily_parking_reminder takes datetime + target_datetime = datetime.combine(target_date, datetime.min.time().replace(hour=8)) # default 8am + + for assignment in assignments: + user = db.query(User).filter(User.id == assignment.user_id).first() + if user: + # Force send (bypass preference check? User said "invia mail uguale a quella di promemoria") + # We'll use the existing function but maybe bypass checks? + # send_daily_parking_reminder checks preferences & log. + # Let's bypass log check by deleting log first? Or just implement direct send here. + + # User wants to "accertarsi che il sistmea funziona", so using the real function is best. + # BUT we must ensure it sends even if already sent? + # Let's clean logs for this specific test run first to ensure send? + # No, better just call send_email directly with the template logic to strictly test EMAIL sending, + # or use the function to test full LOGIC. + # "invia una mail uguale a quella di promemoria" -> Use logic. + + # To force send, we can modify the function or just build the email here manually like the function does. + # Manual build is safer for a "Test Tool" to avoid messing with production logs state. + + spot_name = get_spot_display_name(assignment.spot_id, assignment.office_id, db) + day_name = target_date.strftime("%d/%m/%Y") + + subject = f"Promemoria Parcheggio - {day_name} (Test)" + body_html = f""" + + +

Promemoria Parcheggio Giornaliero

+

Ciao {user.name},

+

Hai un posto auto assegnato per il giorno {day_name}:

+

Posto {spot_name}

+

Cordiali saluti,
Team Parking Manager

+

(Email di test inviata manualmente dall'amministrazione)

+ + + """ + if send_email(user.email, subject, body_html): + count += 1 + else: + failed += 1 + + return { + "success": True, + "mode": "BULK", + "count": count, + "failed": failed, + "date": target_date + } + + # SINGLE USER TEST MODE (Existing) + formatted_date = target_date.strftime("%d/%m/%Y") + subject = f"Test Email - Sistema Parcheggi ({formatted_date})" body_html = f""" -

Parking System Test Email

-

Hi {current_user.name},

-

This is a test email triggered from the Parking Manager Test Tools.

-

Selected Date: {target_date}

-

SMTP Status: {'Enabled' if config.SMTP_ENABLED else 'Disabled (File Logging)'}

-

If you received this, the notification system is working.

+

Email di Test - Sistema Parcheggi

+

Ciao {current_user.name},

+

Questa è una email di test inviata dagli strumenti di amministrazione.

+

Data Selezionata: {formatted_date}

+

Stato SMTP: {'Abilitato' if config.SMTP_ENABLED else 'Disabilitato (File Logging)'}

+

Se hai ricevuto questa email, il sistema di notifiche funziona correttamente.

""" diff --git a/frontend/js/parking-settings.js b/frontend/js/parking-settings.js index 00632e4..d357d0d 100644 --- a/frontend/js/parking-settings.js +++ b/frontend/js/parking-settings.js @@ -288,11 +288,7 @@ function setupEventListeners() { office_id: currentOffice.id }); - if (res && res.status !== 403 && res.status !== 500 && res.ok !== false) { - // API wrapper usually returns response object or parses JSON? - // api.post returns response object if 200-299, but wrapper handles some. - // Let's assume standard fetch response or check wrapper. - // api.js Wrapper returns fetch Response. + if (res && res.status >= 200 && res.status < 300) { const data = await res.json(); if (data.success) { @@ -306,7 +302,9 @@ function setupEventListeners() { } } else { const err = res ? await res.json() : {}; - utils.showMessage('Errore: ' + (err.detail || 'Invio fallito'), 'error'); + console.error("Test Email Error:", err); + const errMsg = err.detail ? (typeof err.detail === 'object' ? JSON.stringify(err.detail) : err.detail) : 'Invio fallito'; + utils.showMessage('Errore: ' + errMsg, 'error'); } } catch (e) { console.error(e); @@ -314,4 +312,51 @@ function setupEventListeners() { } }); } + + const bulkEmailBtn = document.getElementById('bulkEmailBtn'); + if (bulkEmailBtn) { + bulkEmailBtn.addEventListener('click', async () => { + const dateVal = document.getElementById('testEmailDate').value; + + // Validate office + if (!currentOffice || !currentOffice.id) { + return utils.showMessage('Errore: Nessun ufficio selezionato', 'error'); + } + + if (!dateVal) { + return utils.showMessage('Per il test a TUTTI è obbligatorio selezionare una data specifica.', 'error'); + } + + if (!confirm(`Sei sicuro di voler inviare una mail di promemoria a TUTTI gli utenti con parcheggio assegnato per il giorno ${dateVal}?\n\nQuesta azione invierà vere email.`)) return; + + utils.showMessage('Invio mail massive in corso...', 'warning'); + + try { + const res = await api.post('/api/parking/test-email', { + date: dateVal, + office_id: currentOffice.id, + bulk_send: true + }); + + if (res && res.status >= 200 && res.status < 300) { + const data = await res.json(); + if (data.success) { + let msg = `Processo completato per il ${data.date}. Inviate: ${data.count || 0}, Fallite: ${data.failed || 0}.`; + if (data.mode === 'BULK' && (data.count || 0) === 0) msg += " (Nessun assegnatario trovato)"; + utils.showMessage(msg, 'success'); + } else { + utils.showMessage('Errore durante l\'invio.', 'error'); + } + } else { + const err = res ? await res.json() : {}; + console.error("Bulk Test Email Error:", err); + const errMsg = err.detail ? (typeof err.detail === 'object' ? JSON.stringify(err.detail) : err.detail) : 'Invio fallito'; + utils.showMessage('Errore: ' + errMsg, 'error'); + } + } catch (e) { + console.error(e); + utils.showMessage('Errore di comunicazione col server: ' + e.message, 'error'); + } + }); + } } diff --git a/frontend/pages/parking-settings.html b/frontend/pages/parking-settings.html index 8dbe295..67f0b18 100644 --- a/frontend/pages/parking-settings.html +++ b/frontend/pages/parking-settings.html @@ -157,7 +157,10 @@ + diff --git a/services/notifications.py b/services/notifications.py index da2c665..164d11e 100644 --- a/services/notifications.py +++ b/services/notifications.py @@ -100,17 +100,17 @@ def notify_parking_assigned(user: "User", assignment_date: date, spot_name: str) if not user.notify_parking_changes: return - day_name = assignment_date.strftime("%A, %B %d") + day_name = assignment_date.strftime("%d/%m/%Y") - subject = f"Parking spot assigned for {day_name}" + subject = f"Assegnazione Posto Auto - {day_name}" body_html = f""" -

Parking Spot Assigned

-

Hi {user.name},

-

You have been assigned a parking spot for {day_name}:

-

Spot {spot_name}

-

Best regards,
Parking Manager

+

Posto Auto Assegnato

+

Ciao {user.name},

+

Ti è stato assegnato un posto auto per il giorno {day_name}:

+

Posto {spot_name}

+

Cordiali saluti,
Team Parking Manager

""" @@ -122,16 +122,16 @@ def notify_parking_released(user: "User", assignment_date: date, spot_name: str) if not user.notify_parking_changes: return - day_name = assignment_date.strftime("%A, %B %d") + day_name = assignment_date.strftime("%d/%m/%Y") - subject = f"Parking spot released for {day_name}" + subject = f"Rilascio Posto Auto - {day_name}" body_html = f""" -

Parking Spot Released

-

Hi {user.name},

-

Your parking spot (Spot {spot_name}) for {day_name} has been released.

-

Best regards,
Parking Manager

+

Posto Auto Rilasciato

+

Ciao {user.name},

+

Il tuo posto auto (Posto {spot_name}) per il giorno {day_name} è stato rilasciato.

+

Cordiali saluti,
Team Parking Manager

""" @@ -143,16 +143,16 @@ def notify_parking_reassigned(user: "User", assignment_date: date, spot_name: st if not user.notify_parking_changes: return - day_name = assignment_date.strftime("%A, %B %d") + day_name = assignment_date.strftime("%d/%m/%Y") - subject = f"Parking spot reassigned for {day_name}" + subject = f"Riassegnazione Posto Auto - {day_name}" body_html = f""" -

Parking Spot Reassigned

-

Hi {user.name},

-

Your parking spot (Spot {spot_name}) for {day_name} has been reassigned to {new_user_name}.

-

Best regards,
Parking Manager

+

Posto Auto Riassegnato

+

Ciao {user.name},

+

Il tuo posto auto (Posto {spot_name}) per il giorno {day_name} è stato riassegnato a {new_user_name}.

+

Cordiali saluti,
Team Parking Manager

""" @@ -188,19 +188,19 @@ def send_presence_reminder(user: "User", next_week_dates: List[date], db: "Sessi return False # Send reminder - start_date = next_week_dates[0].strftime("%B %d") - end_date = next_week_dates[-1].strftime("%B %d, %Y") + start_date = next_week_dates[0].strftime("%d/%m/%Y") + end_date = next_week_dates[-1].strftime("%d/%m/%Y") - subject = f"Reminder: Please fill your presence for {start_date} - {end_date}" + subject = f"Promemoria Presenze - Settimana {start_date} - {end_date}" body_html = f""" -

Presence Reminder

-

Hi {user.name},

-

This is a friendly reminder to fill your presence for the upcoming week +

Promemoria Compilazione Presenze

+

Ciao {user.name},

+

Ti ricordiamo di compilare le tue presenze per la prossima settimana ({start_date} - {end_date}).

-

Please log in to the Parking Manager to mark your presence.

-

Best regards,
Parking Manager

+

Accedi al Parking Manager per segnare le tue presenze.

+

Cordiali saluti,
Team Parking Manager

""" @@ -254,17 +254,17 @@ def send_daily_parking_reminder(user: "User", date_obj: datetime, db: "Session") return False spot_name = get_spot_display_name(assignment.spot_id, assignment.office_id, db) - day_name = date_obj.strftime("%A, %B %d") + day_name = date_obj.strftime("%d/%m/%Y") - subject = f"Parking reminder for {day_name}" + subject = f"Promemoria Parcheggio - {day_name}" body_html = f""" -

Daily Parking Reminder

-

Hi {user.name},

-

You have a parking spot assigned for today ({day_name}):

-

Spot {spot_name}

-

Best regards,
Parking Manager

+

Promemoria Parcheggio Giornaliero

+

Ciao {user.name},

+

Hai un posto auto assegnato per oggi ({day_name}):

+

Posto {spot_name}

+

Cordiali saluti,
Team Parking Manager

""" diff --git a/utils/auth_middleware.py b/utils/auth_middleware.py index fa65da3..a9bf465 100644 --- a/utils/auth_middleware.py +++ b/utils/auth_middleware.py @@ -99,7 +99,7 @@ def get_current_user( remote_name = request.headers.get(config.AUTHELIA_HEADER_NAME, "") remote_groups = request.headers.get(config.AUTHELIA_HEADER_GROUPS, "") - print(f"[Authelia] Headers: user={remote_user}, email={remote_email}, name={remote_name}, groups={remote_groups}") + remote_groups = request.headers.get(config.AUTHELIA_HEADER_GROUPS, "") if not remote_user: raise HTTPException(