feat: add multi-tenant system with properties, organizations, and public booking
Implement complete multi-property architecture: - Properties (groups of spaces) with public/private visibility - Property managers (many-to-many) with role-based permissions - Organizations with member management - Anonymous/guest booking support via public API (/api/public/*) - Property-scoped spaces, bookings, and settings - Frontend: property selector, organization management, public booking views - Migration script and updated seed data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
"""Booking schemas for request/response."""
|
||||
from datetime import datetime, date
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
|
||||
|
||||
class BookingCalendarPublic(BaseModel):
|
||||
@@ -21,7 +21,7 @@ class BookingCalendarAdmin(BaseModel):
|
||||
"""Full booking data for admins (calendar view)."""
|
||||
|
||||
id: int
|
||||
user_id: int
|
||||
user_id: int | None = None
|
||||
space_id: int
|
||||
start_datetime: datetime
|
||||
end_datetime: datetime
|
||||
@@ -50,7 +50,7 @@ class BookingResponse(BaseModel):
|
||||
"""Schema for booking response after creation."""
|
||||
|
||||
id: int
|
||||
user_id: int
|
||||
user_id: int | None = None
|
||||
space_id: int
|
||||
start_datetime: datetime
|
||||
end_datetime: datetime
|
||||
@@ -58,6 +58,10 @@ class BookingResponse(BaseModel):
|
||||
title: str
|
||||
description: str | None
|
||||
created_at: datetime
|
||||
guest_name: str | None = None
|
||||
guest_email: str | None = None
|
||||
guest_organization: str | None = None
|
||||
is_anonymous: bool = False
|
||||
# Timezone-aware formatted strings (optional, set by endpoint)
|
||||
start_datetime_tz: Optional[str] = None
|
||||
end_datetime_tz: Optional[str] = None
|
||||
@@ -79,6 +83,10 @@ class BookingResponse(BaseModel):
|
||||
title=booking.title,
|
||||
description=booking.description,
|
||||
created_at=booking.created_at,
|
||||
guest_name=booking.guest_name,
|
||||
guest_email=booking.guest_email,
|
||||
guest_organization=booking.guest_organization,
|
||||
is_anonymous=booking.is_anonymous,
|
||||
start_datetime_tz=format_datetime_tz(booking.start_datetime, user_timezone),
|
||||
end_datetime_tz=format_datetime_tz(booking.end_datetime, user_timezone)
|
||||
)
|
||||
@@ -90,9 +98,20 @@ class SpaceInBooking(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
type: str
|
||||
property_id: int | None = None
|
||||
property_name: str | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@model_validator(mode="wrap")
|
||||
@classmethod
|
||||
def extract_property_name(cls, data: Any, handler: Any) -> "SpaceInBooking":
|
||||
"""Extract property_name from ORM relationship."""
|
||||
instance = handler(data)
|
||||
if instance.property_name is None and hasattr(data, 'property') and data.property:
|
||||
instance.property_name = data.property.name
|
||||
return instance
|
||||
|
||||
|
||||
class BookingWithSpace(BaseModel):
|
||||
"""Booking with associated space details for user's booking list."""
|
||||
@@ -127,14 +146,18 @@ class BookingPendingDetail(BaseModel):
|
||||
id: int
|
||||
space_id: int
|
||||
space: SpaceInBooking
|
||||
user_id: int
|
||||
user: UserInBooking
|
||||
user_id: int | None = None
|
||||
user: UserInBooking | None = None
|
||||
start_datetime: datetime
|
||||
end_datetime: datetime
|
||||
status: str
|
||||
title: str
|
||||
description: str | None
|
||||
created_at: datetime
|
||||
guest_name: str | None = None
|
||||
guest_email: str | None = None
|
||||
guest_organization: str | None = None
|
||||
is_anonymous: bool = False
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@@ -242,3 +265,16 @@ class BookingReschedule(BaseModel):
|
||||
|
||||
start_datetime: datetime
|
||||
end_datetime: datetime
|
||||
|
||||
|
||||
class AnonymousBookingCreate(BaseModel):
|
||||
"""Schema for anonymous/guest booking creation."""
|
||||
|
||||
space_id: int
|
||||
start_datetime: datetime
|
||||
end_datetime: datetime
|
||||
title: str = Field(..., min_length=1, max_length=200)
|
||||
description: str | None = None
|
||||
guest_name: str = Field(..., min_length=1)
|
||||
guest_email: str = Field(..., min_length=1)
|
||||
guest_organization: str | None = None
|
||||
|
||||
Reference in New Issue
Block a user