feat: add per-space timezone settings and improve booking management
- Add timezone configuration per space with fallback to system default - Implement timezone-aware datetime display and editing across frontend - Add migration for per_space_settings table - Update booking service to handle timezone conversions properly - Improve .gitignore to exclude build artifacts - Add comprehensive testing documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -248,6 +248,7 @@ def create_booking(
|
||||
user_id=user_id,
|
||||
start_datetime=start_datetime_utc,
|
||||
end_datetime=end_datetime_utc,
|
||||
user_timezone=user_timezone,
|
||||
)
|
||||
|
||||
if errors:
|
||||
@@ -344,6 +345,9 @@ def create_recurring_booking(
|
||||
|
||||
duration = timedelta(minutes=data.duration_minutes)
|
||||
|
||||
# Get user timezone
|
||||
user_timezone = current_user.timezone or "UTC"
|
||||
|
||||
# Generate occurrence dates
|
||||
occurrences = []
|
||||
current_date = data.start_date
|
||||
@@ -360,18 +364,23 @@ def create_recurring_booking(
|
||||
|
||||
# Create bookings for each occurrence
|
||||
for occurrence_date in occurrences:
|
||||
# Build datetime
|
||||
# Build datetime in user timezone
|
||||
start_datetime = datetime.combine(occurrence_date, time(hour, minute))
|
||||
end_datetime = start_datetime + duration
|
||||
|
||||
# Convert to UTC for validation and storage
|
||||
start_datetime_utc = convert_to_utc(start_datetime, user_timezone)
|
||||
end_datetime_utc = convert_to_utc(end_datetime, user_timezone)
|
||||
|
||||
# Validate
|
||||
user_id = int(current_user.id) # type: ignore[arg-type]
|
||||
errors = validate_booking_rules(
|
||||
db=db,
|
||||
space_id=data.space_id,
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
start_datetime=start_datetime_utc,
|
||||
end_datetime=end_datetime_utc,
|
||||
user_id=user_id,
|
||||
user_timezone=user_timezone,
|
||||
)
|
||||
|
||||
if errors:
|
||||
@@ -387,14 +396,14 @@ def create_recurring_booking(
|
||||
# Skip and continue
|
||||
continue
|
||||
|
||||
# Create booking
|
||||
# Create booking (store UTC times)
|
||||
booking = Booking(
|
||||
user_id=user_id,
|
||||
space_id=data.space_id,
|
||||
title=data.title,
|
||||
description=data.description,
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
start_datetime=start_datetime_utc,
|
||||
end_datetime=end_datetime_utc,
|
||||
status="pending",
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
@@ -491,6 +500,7 @@ def update_booking(
|
||||
end_datetime=updated_end, # type: ignore[arg-type]
|
||||
user_id=user_id,
|
||||
exclude_booking_id=booking.id, # Exclude self from overlap check
|
||||
user_timezone=user_timezone,
|
||||
)
|
||||
|
||||
if errors:
|
||||
@@ -662,6 +672,8 @@ def approve_booking(
|
||||
)
|
||||
|
||||
# Re-validate booking rules to prevent race conditions
|
||||
# Use booking owner's timezone for validation
|
||||
user_timezone = booking.user.timezone or "UTC" if booking.user else "UTC"
|
||||
errors = validate_booking_rules(
|
||||
db=db,
|
||||
space_id=int(booking.space_id), # type: ignore[arg-type]
|
||||
@@ -669,6 +681,7 @@ def approve_booking(
|
||||
start_datetime=booking.start_datetime, # type: ignore[arg-type]
|
||||
end_datetime=booking.end_datetime, # type: ignore[arg-type]
|
||||
exclude_booking_id=int(booking.id), # type: ignore[arg-type]
|
||||
user_timezone=user_timezone,
|
||||
)
|
||||
|
||||
if errors:
|
||||
@@ -842,6 +855,8 @@ def admin_update_booking(
|
||||
booking.end_datetime = data.end_datetime # type: ignore[assignment]
|
||||
|
||||
# Re-validate booking rules
|
||||
# Use booking owner's timezone for validation
|
||||
user_timezone = booking.user.timezone or "UTC" if booking.user else "UTC"
|
||||
errors = validate_booking_rules(
|
||||
db=db,
|
||||
space_id=int(booking.space_id), # type: ignore[arg-type]
|
||||
@@ -849,6 +864,7 @@ def admin_update_booking(
|
||||
end_datetime=booking.end_datetime, # type: ignore[arg-type]
|
||||
user_id=int(booking.user_id), # type: ignore[arg-type]
|
||||
exclude_booking_id=booking.id, # Exclude self from overlap check
|
||||
user_timezone=user_timezone,
|
||||
)
|
||||
|
||||
if errors:
|
||||
@@ -987,6 +1003,8 @@ def reschedule_booking(
|
||||
old_end = booking.end_datetime
|
||||
|
||||
# Validate new time slot
|
||||
# Use booking owner's timezone for validation
|
||||
user_timezone = booking.user.timezone or "UTC" if booking.user else "UTC"
|
||||
errors = validate_booking_rules(
|
||||
db=db,
|
||||
space_id=int(booking.space_id), # type: ignore[arg-type]
|
||||
@@ -994,6 +1012,7 @@ def reschedule_booking(
|
||||
end_datetime=data.end_datetime,
|
||||
user_id=int(booking.user_id), # type: ignore[arg-type]
|
||||
exclude_booking_id=booking.id, # Exclude self from overlap check
|
||||
user_timezone=user_timezone,
|
||||
)
|
||||
|
||||
if errors:
|
||||
|
||||
Reference in New Issue
Block a user