Skip to content

Commit

Permalink
Room Reservations (#250)
Browse files Browse the repository at this point in the history
This very significant feature landing adds the ability to reserve group rooms in the XL into the future. Full-stack implications from reservation UI to resource limitations on the backend. Feature implemented by group B8 in the Fall of 2023 and Spring of 2024. For a full run-down of this feature, see `docs/specs/room-reservation.md`.
  • Loading branch information
yuvrajjain2003 authored Mar 8, 2024
1 parent 3e805a3 commit b13e6dd
Show file tree
Hide file tree
Showing 81 changed files with 4,108 additions and 773 deletions.
19 changes: 15 additions & 4 deletions backend/api/coworking/ambassador.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,26 @@
api = APIRouter(prefix="/api/coworking/ambassador")


@api.get("", tags=["Coworking"])
def active_and_upcoming_reservations(
@api.get("/xl", tags=["Coworking"])
def active_and_upcoming_reservations_for_xl(
subject: User = Depends(registered_user),
reservation_svc: ReservationService = Depends(),
) -> Sequence[Reservation]:
"""List active and upcoming reservations.
"""List active and upcoming reservations for the XL.
This list drives the ambassador's checkin UI."""
return reservation_svc.list_all_active_and_upcoming(subject)
return reservation_svc.list_all_active_and_upcoming_for_xl(subject)


@api.get("/rooms", tags=["Coworking"])
def active_and_upcoming_reservations_for_rooms(
subject: User = Depends(registered_user),
reservation_svc: ReservationService = Depends(),
) -> Sequence[Reservation]:
"""List active and upcoming reservations for the rooms.
This list drives the ambassador's checkin UI."""
return reservation_svc.list_all_active_and_upcoming_for_rooms(subject)


@api.put("/checkin", tags=["Coworking"])
Expand Down
45 changes: 43 additions & 2 deletions backend/api/coworking/reservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
This API is used to make and manage reservations."""

from fastapi import APIRouter, Depends, HTTPException
from typing import Sequence
from datetime import datetime

from backend.models.room import Room
from ..authentication import registered_user
from ...services.coworking.reservation import ReservationService
from ...services.coworking.reservation import ReservationException, ReservationService
from ...models import User
from ...models.coworking import (
Reservation,
ReservationRequest,
ReservationPartial,
ReservationState,
ReservationMapDetails
)

__authors__ = ["Kris Jordan"]
__authors__ = ["Kris Jordan, Yuvraj Jain"]
__copyright__ = "Copyright 2023"
__license__ = "MIT"

Expand Down Expand Up @@ -44,6 +49,20 @@ def get_reservation(
return reservation_svc.get_reservation(subject, id)


@api.get("/room-reservations/", tags=["Coworking"])
def get_all_reservations_by_state(
state: ReservationState,
subject: User = Depends(registered_user),
reservation_svc: ReservationService = Depends(),
) -> Sequence[Reservation]:
try:
return reservation_svc.get_current_reservations_for_user(
subject=subject, focus=subject, state=state
)
except Exception as e:
raise HTTPException(status_code=404, detail=str(e))


@api.put("/reservation/{id}", tags=["Coworking"])
def update_reservation(
reservation: ReservationPartial,
Expand All @@ -64,3 +83,25 @@ def cancel_reservation(
return reservation_svc.change_reservation(
subject, ReservationPartial(id=id, state=ReservationState.CANCELLED)
)


@api.get("/room-reservation/", tags=["Coworking"])
def get_reservations_for_rooms_by_date(
date: datetime,
subject: User = Depends(registered_user),
reservation_svc: ReservationService = Depends(),
) -> ReservationMapDetails:
"""See available rooms for any given day."""
try:
return reservation_svc.get_map_reserved_times_by_date(date, subject)
except Exception as e:
raise HTTPException(status_code=404, detail=str(e))


@api.get("/user-reservations/", tags=["Coworking"])
def get_total_hours_study_room_reservations(
subject: User = Depends(registered_user),
reservation_svc: ReservationService = Depends(),
) -> str:
"""Allows a user to know how many hours they have reserved in all study rooms (Excludes CSXL)."""
return reservation_svc._get_total_time_user_reservations(subject)
3 changes: 2 additions & 1 deletion backend/models/coworking/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
ReservationRequest,
ReservationState,
ReservationPartial,
ReservationMapDetails,
ReservationIdentity,
)

from .availability_list import AvailabilityList
from .availability import SeatAvailability, RoomAvailability
from .availability import RoomState, SeatAvailability, RoomAvailability

from .status import Status

Expand Down
13 changes: 13 additions & 0 deletions backend/models/coworking/availability.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
"""Models for the availability of rooms and seats over a time range."""

from enum import Enum
from pydantic import BaseModel, validator

from ..room import Room
from .seat import Seat
from .time_range import TimeRange
from .availability_list import AvailabilityList

__authors__ = ["Kris Jordan, Yuvraj Jain"]
__copyright__ = "Copyright 2024"
__license__ = "MIT"


class RoomState(int, Enum):
AVAILABLE = 0
RESERVED = 1
SELECTED = 2
UNAVAILABLE = 3
SUBJECT_RESERVED = 4


class RoomAvailability(Room, AvailabilityList):
"""A room that is available for a given time range."""
Expand Down
4 changes: 4 additions & 0 deletions backend/models/coworking/operating_hours.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from .time_range import TimeRange


__authors__ = ["Kris Jordan, Yuvraj Jain"]
__copyright__ = "Copyright 2024"
__license__ = "MIT"

class OperatingHours(TimeRange, BaseModel):
"""The operating hours of the XL."""

Expand Down
14 changes: 13 additions & 1 deletion backend/models/coworking/reservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
from pydantic import BaseModel
from datetime import datetime
from ...models.user import User, UserIdentity
from ..room import Room
from ..room import Room, RoomPartial
from .seat import Seat, SeatIdentity
from .time_range import TimeRange

__authors__ = ["Kris Jordan, Yuvraj Jain"]
__copyright__ = "Copyright 2024"
__license__ = "MIT"


class ReservationState(str, Enum):
DRAFT = "DRAFT"
Expand All @@ -22,6 +26,7 @@ class ReservationIdentity(BaseModel):
class ReservationRequest(TimeRange):
users: list[UserIdentity] = []
seats: list[SeatIdentity] = []
room: RoomPartial | None = None


class Reservation(ReservationIdentity, TimeRange):
Expand All @@ -34,6 +39,13 @@ class Reservation(ReservationIdentity, TimeRange):
updated_at: datetime


class ReservationMapDetails(BaseModel):
reserved_date_map: dict[str, list[int]] = {}
operating_hours_start: datetime | None = None
operating_hours_end: datetime | None = None
number_of_time_slots: int | None = None


class ReservationPartial(Reservation, BaseModel):
start: datetime | None = None
end: datetime | None = None
Expand Down
2 changes: 1 addition & 1 deletion backend/models/coworking/time_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pydantic import BaseModel, field_validator, ValidationInfo, validator
from typing import Self

__authors__ = ["Kris Jordan"]
__authors__ = ["Kris Jordan, Yuvraj Jain"]
__copyright__ = "Copyright 2023"
__license__ = "MIT"

Expand Down
6 changes: 4 additions & 2 deletions backend/models/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
__copyright__ = "Copyright 2023"
__license__ = "MIT"


class Room(BaseModel):
class RoomPartial(BaseModel):
id: str

class Room(RoomPartial):
nickname: str = ""

3 changes: 1 addition & 2 deletions backend/script/deployments/coworking_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
from ...entities.coworking import SeatEntity, OperatingHoursEntity, RoomEntity
from ...test.services.reset_table_id_seq import reset_table_id_seq

from ...test.services import role_data, user_data, permission_data
from ...test.services import role_data, user_data, permission_data, room_data
from ...test.services.coworking import (
room_data,
seat_data,
operating_hours_data,
time,
Expand Down
1 change: 0 additions & 1 deletion backend/script/reset_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from ..test.services import role_data, user_data, permission_data, room_data
from ..test.services.organization import organization_demo_data
from ..test.services.event import event_demo_data

from ..test.services.coworking import seat_data, operating_hours_data, time
from ..test.services.coworking.reservation import reservation_data
from ..test.services.academics import course_data, term_data, section_data
Expand Down
109 changes: 106 additions & 3 deletions backend/services/coworking/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@

from fastapi import Depends
from sqlalchemy.orm import Session
from datetime import timedelta
from datetime import timedelta, datetime, time
from ...database import db_session
from ...models import User

__authors__ = ["Kris Jordan"]
__copyright__ = "Copyright 2023"
__license__ = "MIT"

MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6

class PolicyService:
"""RoleService is the access layer to the role data model, its members, and permissions.
Expand All @@ -18,8 +25,7 @@ class PolicyService:
for different groups of users (e.g. majors, ambassadors, LAs, etc).
"""

def __init__(self):
...
def __init__(self): ...

def walkin_window(self, _subject: User) -> timedelta:
"""How far into the future can walkins be reserved?"""
Expand Down Expand Up @@ -51,6 +57,103 @@ def maximum_initial_reservation_duration(self, _subject: User) -> timedelta:

def reservation_draft_timeout(self) -> timedelta:
return timedelta(minutes=5)


def reservation_checkin_timeout(self) -> timedelta:
return timedelta(minutes=10)


def room_reservation_weekly_limit(self) -> timedelta:
"""The maximum amount of hours a student can reserve the study rooms outside of the csxl."""
return timedelta(hours=6)

def non_reservable_rooms(self) -> list[str]:
return ['404']

def office_hours(self, date: datetime):
day = date.weekday()
if day == MONDAY:
return {
'SN135' : [
(time(hour=16, minute=30), time(hour=17, minute=00)),
],
'SN137' : [
(time(hour=15, minute=00), time(hour=16, minute=30))
],
'SN139' : [],
'SN141' : [
(time(hour=16, minute=00), time(hour=17, minute=30))
]
}
elif day == TUESDAY:
return {
'SN135' : [
(time(hour=14, minute=30), time(hour=16, minute=00)),
(time(hour=11, minute=00), time(hour=12, minute=00))
],
'SN137' : [],
'SN139' : [
(time(hour=10, minute=30), time(hour=11, minute=00)),
(time(hour=11, minute=30), time(hour=13, minute=00)),
],
'SN141' : [
(time(hour=10, minute=00), time(hour=11, minute=00))
]
}
elif day == WEDNESDAY:
return {
'SN135' : [
(time(hour=11, minute=00), time(hour=12, minute=00))
],
'SN137' : [],
'SN139' : [
(time(hour=10, minute=30), time(hour=11, minute=00)),
(time(hour=11, minute=30), time(hour=13, minute=00)),
(time(hour=14, minute=30), time(hour=15, minute=00))
],
'SN141' : [
(time(hour=10, minute=00), time(hour=11, minute=00))
]
}
elif day == THURSDAY:
return {
'SN135' : [
(time(hour=14, minute=30), time(hour=16, minute=00)),
(time(hour=11, minute=00), time(hour=12, minute=00))
],
'SN137' : [],
'SN139' : [
(time(hour=10, minute=30), time(hour=11, minute=00)),
(time(hour=11, minute=30), time(hour=13, minute=00)),
(time(hour=14, minute=30), time(hour=15, minute=00))
],
'SN141' : []
}
elif day == FRIDAY:
return {
'SN135' : [
(time(hour=11, minute=00), time(hour=12, minute=00))
],
'SN137' : [],
'SN139' : [
(time(hour=10, minute=30), time(hour=11, minute=00)),
(time(hour=14, minute=30), time(hour=15, minute=00))
],
'SN141' : [
(time(hour=10, minute=00), time(hour=11, minute=00))
]
}
elif day == SATURDAY:
return {
'SN135' : [],
'SN137' : [],
'SN139' : [],
'SN141' : []
}
elif day == SUNDAY:
return {
'SN135' : [],
'SN137' : [],
'SN139' : [],
'SN141' : []
}
Loading

0 comments on commit b13e6dd

Please sign in to comment.