feat: aggiunti: loggica random, tema scuro, correzioni mail, miglioramenti generali, cache;

This commit is contained in:
StefanoSalemi
2026-04-17 18:27:37 +02:00
parent a7ef46640d
commit 104ad53a9a
26 changed files with 861 additions and 216 deletions

View File

@@ -216,15 +216,29 @@ def get_users_wanting_parking(office_id: str, pool_date: date, db: Session) -> l
def assign_parking_fairly(office_id: str, pool_date: date, db: Session) -> dict:
"""
Assign parking spots fairly based on parking ratio.
Assign parking spots based on the office's assignment_mode (fairness or random).
Creates new DailyParkingAssignment rows only for assigned users.
"""
if is_closing_day(office_id, pool_date, db):
return {"assigned": [], "waitlist": [], "closed": True}
# Get candidates sorted by fairness
# Retrieve office to check assignment mode
office = db.query(Office).filter(Office.id == office_id).first()
mode = office.assignment_mode if office and getattr(office, 'assignment_mode', None) else "fairness"
# Get candidates sorted by fairness (guaranteed first, then by ratio)
candidates = get_users_wanting_parking(office_id, pool_date, db)
if mode == "random":
import random
guaranteed = [c for c in candidates if c["has_guarantee"]]
non_guaranteed = [c for c in candidates if not c["has_guarantee"]]
# Shuffle non-guaranteed users to pick randomly
random.shuffle(non_guaranteed)
candidates = guaranteed + non_guaranteed
# Get available spots (OfficeSpots not yet in assignments table)
free_spots = get_available_spots(office_id, pool_date, db)
@@ -281,6 +295,10 @@ def release_user_spot(office_id: str, user_id: str, pool_date: date, db: Session
if not assignment:
return False
# Get old user name for notification
old_user = db.query(User).filter(User.id == user_id).first()
old_user_name = old_user.name if old_user else "un collega"
# Capture spot ID before deletion
spot_id = assignment.spot_id
@@ -305,6 +323,13 @@ def release_user_spot(office_id: str, user_id: str, pool_date: date, db: Session
db.add(new_assignment)
db.commit()
# Notify the lucky user
from services.notifications import notify_parking_released_to_user
top_user = db.query(User).filter(User.id == top_candidate["user_id"]).first()
if top_user:
spot_name = get_spot_display_name(spot_id, office_id, db)
notify_parking_released_to_user(top_user, pool_date, spot_name, old_user_name)
return True
@@ -328,31 +353,6 @@ def handle_presence_change(user_id: str, change_date: date, old_status: Presence
# User no longer coming - release their spot (will auto-reassign)
release_user_spot(office.id, user_id, change_date, db)
elif new_status == PresenceStatus.PRESENT:
# Check booking window
should_assign = True
if office.booking_window_enabled:
from zoneinfo import ZoneInfo
tz = ZoneInfo(config.TIMEZONE)
now = datetime.now(tz)
# Allocation time is Day-1 at cutoff hour
cutoff_dt = datetime.combine(change_date - timedelta(days=1), datetime.min.time())
cutoff_dt = cutoff_dt.replace(
hour=office.booking_window_end_hour,
minute=office.booking_window_end_minute,
tzinfo=tz
)
# If now is before cutoff, do not assign yet (wait for batch job)
if now < cutoff_dt:
should_assign = False
config.logger.debug(f"Queuing parking request for user {user_id} on {change_date} (Window open until {cutoff_dt})")
if should_assign:
# User coming in - run fair assignment for this date
assign_parking_fairly(office.id, change_date, db)
def clear_assignments_for_office_date(office_id: str, pool_date: date, db: Session) -> int:
"""
@@ -405,7 +405,24 @@ def process_daily_allocations(db: Session):
# Cutoff is defined as "Previous Day" (today) at Booking End Hour
# If NOW matches the cutoff time, we run allocation for TOMORROW
if now.hour == office.booking_window_end_hour and now.minute == office.booking_window_end_minute:
# Non eseguiamo l'assegnazione se oggi è un giorno di chiusura
# (è già stata fatta l'assegnazione per i giorni futuri nell'ultimo giorno lavorativo)
if is_closing_day(office.id, now.date(), db):
config.logger.info(f"[SCHEDULER] Skipping batch allocation for {office.name} because today ({now.date()}) is a closing day.")
continue
# Troviamo il prossimo giorno lavorativo a partire da "domani"
target_date = now.date() + timedelta(days=1)
days_ahead = 1
while is_closing_day(office.id, target_date, db) and days_ahead <= 30:
target_date += timedelta(days=1)
days_ahead += 1
if days_ahead > 30:
config.logger.warning(f"[SCHEDULER] Could not find a working day within 30 days for {office.name}.")
continue
config.logger.info(f"[SCHEDULER] CUTOFF REACHED for {office.name} (Cutoff: {office.booking_window_end_hour}:{office.booking_window_end_minute}). Starting Assegnazione Giornaliera parcheggi for {target_date}")
try: