fix mail sand 2
This commit is contained in:
@@ -443,29 +443,34 @@ def get_eligible_users(assignment_id: str, db: Session = Depends(get_db), curren
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
class TestEmailRequest(BaseModel):
|
class TestEmailRequest(BaseModel):
|
||||||
date: Optional[date] = None
|
date: Optional[str] = None
|
||||||
office_id: str
|
office_id: str
|
||||||
|
bulk_send: bool = False # New flag
|
||||||
|
|
||||||
|
|
||||||
@router.post("/test-email")
|
@router.post("/test-email")
|
||||||
def send_test_email_tool(data: TestEmailRequest, db: Session = Depends(get_db), current_user=Depends(require_manager_or_admin)):
|
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)"""
|
"""Send a test email to the current user OR bulk reminder to all (Test Tool)"""
|
||||||
from services.notifications import send_email
|
from services.notifications import send_email, send_daily_parking_reminder
|
||||||
from database.models import OfficeClosingDay, OfficeWeeklyClosingDay
|
from database.models import OfficeClosingDay, OfficeWeeklyClosingDay, User
|
||||||
from datetime import timedelta
|
from datetime import timedelta, datetime
|
||||||
|
|
||||||
|
|
||||||
# Verify office access
|
# Verify office access
|
||||||
if current_user.role == UserRole.MANAGER and current_user.office_id != data.office_id:
|
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")
|
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:
|
if not target_date:
|
||||||
# Find next open day
|
# Find next open day logic (same as before)
|
||||||
# Start from tomorrow (or today? Prompt says "dopo il giorno corrente" -> after today)
|
|
||||||
check_date = date.today() + timedelta(days=1)
|
check_date = date.today() + timedelta(days=1)
|
||||||
|
|
||||||
# Load closing rules
|
# Load closing rules
|
||||||
@@ -479,15 +484,11 @@ def send_test_email_tool(data: TestEmailRequest, db: Session = Depends(get_db),
|
|||||||
OfficeClosingDay.date >= check_date
|
OfficeClosingDay.date >= check_date
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
# Max lookahead 30 days to avoid infinite loop
|
|
||||||
found = False
|
found = False
|
||||||
for _ in range(30):
|
for _ in range(30):
|
||||||
# Check weekly
|
|
||||||
if check_date.weekday() in weekly_closed_set:
|
if check_date.weekday() in weekly_closed_set:
|
||||||
check_date += timedelta(days=1)
|
check_date += timedelta(days=1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check specific
|
|
||||||
is_specific = False
|
is_specific = False
|
||||||
for d in specific_closed:
|
for d in specific_closed:
|
||||||
s = d.date
|
s = d.date
|
||||||
@@ -495,31 +496,89 @@ def send_test_email_tool(data: TestEmailRequest, db: Session = Depends(get_db),
|
|||||||
if s <= check_date <= e:
|
if s <= check_date <= e:
|
||||||
is_specific = True
|
is_specific = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if is_specific:
|
if is_specific:
|
||||||
check_date += timedelta(days=1)
|
check_date += timedelta(days=1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if found:
|
target_date = check_date if found else date.today() + timedelta(days=1)
|
||||||
target_date = check_date
|
|
||||||
else:
|
|
||||||
# Fallback
|
|
||||||
target_date = date.today() + timedelta(days=1)
|
|
||||||
|
|
||||||
# Send Email
|
# BULK MODE
|
||||||
subject = f"Test Email - Parking System ({target_date})"
|
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"""
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h2>Promemoria Parcheggio Giornaliero</h2>
|
||||||
|
<p>Ciao {user.name},</p>
|
||||||
|
<p>Hai un posto auto assegnato per il giorno {day_name}:</p>
|
||||||
|
<p style="font-size: 18px; font-weight: bold;">Posto {spot_name}</p>
|
||||||
|
<p>Cordiali saluti,<br>Team Parking Manager</p>
|
||||||
|
<p><em>(Email di test inviata manualmente dall'amministrazione)</em></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
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"""
|
body_html = f"""
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h2>Parking System Test Email</h2>
|
<h2>Email di Test - Sistema Parcheggi</h2>
|
||||||
<p>Hi {current_user.name},</p>
|
<p>Ciao {current_user.name},</p>
|
||||||
<p>This is a test email triggered from the Parking Manager Test Tools.</p>
|
<p>Questa è una email di test inviata dagli strumenti di amministrazione.</p>
|
||||||
<p><strong>Selected Date:</strong> {target_date}</p>
|
<p><strong>Data Selezionata:</strong> {formatted_date}</p>
|
||||||
<p><strong>SMTP Status:</strong> {'Enabled' if config.SMTP_ENABLED else 'Disabled (File Logging)'}</p>
|
<p><strong>Stato SMTP:</strong> {'Abilitato' if config.SMTP_ENABLED else 'Disabilitato (File Logging)'}</p>
|
||||||
<p>If you received this, the notification system is working.</p>
|
<p>Se hai ricevuto questa email, il sistema di notifiche funziona correttamente.</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -288,11 +288,7 @@ function setupEventListeners() {
|
|||||||
office_id: currentOffice.id
|
office_id: currentOffice.id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res && res.status !== 403 && res.status !== 500 && res.ok !== false) {
|
if (res && res.status >= 200 && res.status < 300) {
|
||||||
// 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.
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
@@ -306,7 +302,9 @@ function setupEventListeners() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const err = res ? await res.json() : {};
|
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) {
|
} catch (e) {
|
||||||
console.error(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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,10 @@
|
|||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<button id="testEmailBtn" class="btn btn-secondary">
|
<button id="testEmailBtn" class="btn btn-secondary">
|
||||||
Test Invio Mail
|
Test (Solo a Me)
|
||||||
|
</button>
|
||||||
|
<button id="bulkEmailBtn" class="btn btn-warning" title="Invia mail reale a tutti gli assegnatari">
|
||||||
|
Test (A Tutti)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -100,17 +100,17 @@ def notify_parking_assigned(user: "User", assignment_date: date, spot_name: str)
|
|||||||
if not user.notify_parking_changes:
|
if not user.notify_parking_changes:
|
||||||
return
|
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"""
|
body_html = f"""
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h2>Parking Spot Assigned</h2>
|
<h2>Posto Auto Assegnato</h2>
|
||||||
<p>Hi {user.name},</p>
|
<p>Ciao {user.name},</p>
|
||||||
<p>You have been assigned a parking spot for {day_name}:</p>
|
<p>Ti è stato assegnato un posto auto per il giorno {day_name}:</p>
|
||||||
<p style="font-size: 18px; font-weight: bold;">Spot {spot_name}</p>
|
<p style="font-size: 18px; font-weight: bold;">Posto {spot_name}</p>
|
||||||
<p>Best regards,<br>Parking Manager</p>
|
<p>Cordiali saluti,<br>Team Parking Manager</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
@@ -122,16 +122,16 @@ def notify_parking_released(user: "User", assignment_date: date, spot_name: str)
|
|||||||
if not user.notify_parking_changes:
|
if not user.notify_parking_changes:
|
||||||
return
|
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"""
|
body_html = f"""
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h2>Parking Spot Released</h2>
|
<h2>Posto Auto Rilasciato</h2>
|
||||||
<p>Hi {user.name},</p>
|
<p>Ciao {user.name},</p>
|
||||||
<p>Your parking spot (Spot {spot_name}) for {day_name} has been released.</p>
|
<p>Il tuo posto auto (Posto {spot_name}) per il giorno {day_name} è stato rilasciato.</p>
|
||||||
<p>Best regards,<br>Parking Manager</p>
|
<p>Cordiali saluti,<br>Team Parking Manager</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
@@ -143,16 +143,16 @@ def notify_parking_reassigned(user: "User", assignment_date: date, spot_name: st
|
|||||||
if not user.notify_parking_changes:
|
if not user.notify_parking_changes:
|
||||||
return
|
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"""
|
body_html = f"""
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h2>Parking Spot Reassigned</h2>
|
<h2>Posto Auto Riassegnato</h2>
|
||||||
<p>Hi {user.name},</p>
|
<p>Ciao {user.name},</p>
|
||||||
<p>Your parking spot (Spot {spot_name}) for {day_name} has been reassigned to {new_user_name}.</p>
|
<p>Il tuo posto auto (Posto {spot_name}) per il giorno {day_name} è stato riassegnato a {new_user_name}.</p>
|
||||||
<p>Best regards,<br>Parking Manager</p>
|
<p>Cordiali saluti,<br>Team Parking Manager</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
@@ -188,19 +188,19 @@ def send_presence_reminder(user: "User", next_week_dates: List[date], db: "Sessi
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Send reminder
|
# Send reminder
|
||||||
start_date = next_week_dates[0].strftime("%B %d")
|
start_date = next_week_dates[0].strftime("%d/%m/%Y")
|
||||||
end_date = next_week_dates[-1].strftime("%B %d, %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"""
|
body_html = f"""
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h2>Presence Reminder</h2>
|
<h2>Promemoria Compilazione Presenze</h2>
|
||||||
<p>Hi {user.name},</p>
|
<p>Ciao {user.name},</p>
|
||||||
<p>This is a friendly reminder to fill your presence for the upcoming week
|
<p>Ti ricordiamo di compilare le tue presenze per la prossima settimana
|
||||||
({start_date} - {end_date}).</p>
|
({start_date} - {end_date}).</p>
|
||||||
<p>Please log in to the Parking Manager to mark your presence.</p>
|
<p>Accedi al Parking Manager per segnare le tue presenze.</p>
|
||||||
<p>Best regards,<br>Parking Manager</p>
|
<p>Cordiali saluti,<br>Team Parking Manager</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
@@ -254,17 +254,17 @@ def send_daily_parking_reminder(user: "User", date_obj: datetime, db: "Session")
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
spot_name = get_spot_display_name(assignment.spot_id, assignment.office_id, db)
|
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"""
|
body_html = f"""
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h2>Daily Parking Reminder</h2>
|
<h2>Promemoria Parcheggio Giornaliero</h2>
|
||||||
<p>Hi {user.name},</p>
|
<p>Ciao {user.name},</p>
|
||||||
<p>You have a parking spot assigned for today ({day_name}):</p>
|
<p>Hai un posto auto assegnato per oggi ({day_name}):</p>
|
||||||
<p style="font-size: 18px; font-weight: bold;">Spot {spot_name}</p>
|
<p style="font-size: 18px; font-weight: bold;">Posto {spot_name}</p>
|
||||||
<p>Best regards,<br>Parking Manager</p>
|
<p>Cordiali saluti,<br>Team Parking Manager</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ def get_current_user(
|
|||||||
remote_name = request.headers.get(config.AUTHELIA_HEADER_NAME, "")
|
remote_name = request.headers.get(config.AUTHELIA_HEADER_NAME, "")
|
||||||
remote_groups = request.headers.get(config.AUTHELIA_HEADER_GROUPS, "")
|
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:
|
if not remote_user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|||||||
Reference in New Issue
Block a user