| 1 | import datetime
|
|---|
| 2 |
|
|---|
| 3 | from db import get_connection, pick_from_list, print_table, input_date, input_time
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 | def browse_resources():
|
|---|
| 7 | """UC0001: Browse Available Resources."""
|
|---|
| 8 | type_filter = None
|
|---|
| 9 | building_filter = None
|
|---|
| 10 |
|
|---|
| 11 | while True:
|
|---|
| 12 | resources = _fetch_resources(type_filter, building_filter)
|
|---|
| 13 |
|
|---|
| 14 | filters_active = []
|
|---|
| 15 | if type_filter:
|
|---|
| 16 | filters_active.append(f"Type: {type_filter}")
|
|---|
| 17 | if building_filter:
|
|---|
| 18 | filters_active.append(f"Building: {building_filter}")
|
|---|
| 19 |
|
|---|
| 20 | print(f"\n=== Browse Resources ({len(resources)} found) ===")
|
|---|
| 21 | if filters_active:
|
|---|
| 22 | print(f" Active filters: {', '.join(filters_active)}")
|
|---|
| 23 | print()
|
|---|
| 24 |
|
|---|
| 25 | for i, r in enumerate(resources, start=1):
|
|---|
| 26 | loc = f"{r['building']} / {r['room']}" if r["building"] else "Digital"
|
|---|
| 27 | print(f" {i:>3}. {r['name']} [{r['type_name']}] - {loc}")
|
|---|
| 28 |
|
|---|
| 29 | print()
|
|---|
| 30 | options = [
|
|---|
| 31 | "Filter by Resource Type",
|
|---|
| 32 | "Filter by Building",
|
|---|
| 33 | "View Resource Details",
|
|---|
| 34 | "Check Availability for a Time Slot",
|
|---|
| 35 | "Clear Filters",
|
|---|
| 36 | ]
|
|---|
| 37 | idx = pick_from_list(" Choose an option: ", options)
|
|---|
| 38 | if idx is None:
|
|---|
| 39 | return
|
|---|
| 40 |
|
|---|
| 41 | if idx == 0:
|
|---|
| 42 | type_filter = _select_type_filter()
|
|---|
| 43 | elif idx == 1:
|
|---|
| 44 | building_filter = _select_building_filter()
|
|---|
| 45 | elif idx == 2:
|
|---|
| 46 | _view_details(resources)
|
|---|
| 47 | elif idx == 3:
|
|---|
| 48 | _check_availability(resources)
|
|---|
| 49 | elif idx == 4:
|
|---|
| 50 | type_filter = None
|
|---|
| 51 | building_filter = None
|
|---|
| 52 | print(" Filters cleared.")
|
|---|
| 53 |
|
|---|
| 54 |
|
|---|
| 55 | def _fetch_resources(type_filter=None, building_filter=None):
|
|---|
| 56 | query = """
|
|---|
| 57 | SELECT r.resource_id, r.name, r.description,
|
|---|
| 58 | r.available_from, r.available_to, r.available_weekends,
|
|---|
| 59 | rt.type_name, rt.is_physical,
|
|---|
| 60 | l.building, l.room
|
|---|
| 61 | FROM resources r
|
|---|
| 62 | JOIN resource_types rt ON r.type_id = rt.type_id
|
|---|
| 63 | LEFT JOIN locations l ON r.location_id = l.location_id
|
|---|
| 64 | WHERE TRUE
|
|---|
| 65 | """
|
|---|
| 66 | params = []
|
|---|
| 67 | if type_filter:
|
|---|
| 68 | query += " AND rt.type_name = %s"
|
|---|
| 69 | params.append(type_filter)
|
|---|
| 70 | if building_filter:
|
|---|
| 71 | query += " AND l.building = %s"
|
|---|
| 72 | params.append(building_filter)
|
|---|
| 73 | query += " ORDER BY rt.type_name, r.name"
|
|---|
| 74 |
|
|---|
| 75 | with get_connection() as conn:
|
|---|
| 76 | with conn.cursor() as cur:
|
|---|
| 77 | cur.execute(query, params)
|
|---|
| 78 | cols = [desc[0] for desc in cur.description]
|
|---|
| 79 | return [dict(zip(cols, row)) for row in cur.fetchall()]
|
|---|
| 80 |
|
|---|
| 81 |
|
|---|
| 82 | def _select_type_filter():
|
|---|
| 83 | with get_connection() as conn:
|
|---|
| 84 | with conn.cursor() as cur:
|
|---|
| 85 | cur.execute("SELECT type_name FROM resource_types ORDER BY type_name")
|
|---|
| 86 | types = [row[0] for row in cur.fetchall()]
|
|---|
| 87 | print("\n Select resource type:")
|
|---|
| 88 | idx = pick_from_list(" Choose: ", types)
|
|---|
| 89 | return types[idx] if idx is not None else None
|
|---|
| 90 |
|
|---|
| 91 |
|
|---|
| 92 | def _select_building_filter():
|
|---|
| 93 | with get_connection() as conn:
|
|---|
| 94 | with conn.cursor() as cur:
|
|---|
| 95 | cur.execute("SELECT DISTINCT building FROM locations ORDER BY building")
|
|---|
| 96 | buildings = [row[0] for row in cur.fetchall()]
|
|---|
| 97 | print("\n Select building:")
|
|---|
| 98 | idx = pick_from_list(" Choose: ", buildings)
|
|---|
| 99 | return buildings[idx] if idx is not None else None
|
|---|
| 100 |
|
|---|
| 101 |
|
|---|
| 102 | def _view_details(resources):
|
|---|
| 103 | if not resources:
|
|---|
| 104 | print(" No resources to show.")
|
|---|
| 105 | return
|
|---|
| 106 | print("\n Select a resource to view details:")
|
|---|
| 107 | items = [r["name"] for r in resources]
|
|---|
| 108 | idx = pick_from_list(" Choose: ", items)
|
|---|
| 109 | if idx is None:
|
|---|
| 110 | return
|
|---|
| 111 |
|
|---|
| 112 | r = resources[idx]
|
|---|
| 113 | print(f"\n === {r['name']} ===")
|
|---|
| 114 | print(f" Type: {r['type_name']}")
|
|---|
| 115 | print(f" Description: {r['description']}")
|
|---|
| 116 | if r["building"]:
|
|---|
| 117 | print(f" Location: {r['building']} / {r['room']}")
|
|---|
| 118 | else:
|
|---|
| 119 | print(f" Location: Digital (no physical location)")
|
|---|
| 120 | print(f" Available: {r['available_from'].strftime('%H:%M')} - {r['available_to'].strftime('%H:%M')}")
|
|---|
| 121 | print(f" Weekends: {'Yes' if r['available_weekends'] else 'No'}")
|
|---|
| 122 |
|
|---|
| 123 | with get_connection() as conn:
|
|---|
| 124 | with conn.cursor() as cur:
|
|---|
| 125 | cur.execute(
|
|---|
| 126 | """
|
|---|
| 127 | SELECT res.start_time, res.end_time, res.status, res.purpose
|
|---|
| 128 | FROM reservations res
|
|---|
| 129 | WHERE res.resource_id = %s
|
|---|
| 130 | AND res.status IN ('approved', 'pending')
|
|---|
| 131 | AND res.end_time > CURRENT_TIMESTAMP
|
|---|
| 132 | ORDER BY res.start_time
|
|---|
| 133 | """,
|
|---|
| 134 | (r["resource_id"],),
|
|---|
| 135 | )
|
|---|
| 136 | reservations = cur.fetchall()
|
|---|
| 137 |
|
|---|
| 138 | if reservations:
|
|---|
| 139 | print(f"\n Upcoming reservations:")
|
|---|
| 140 | print_table(
|
|---|
| 141 | ["Start", "End", "Status", "Purpose"],
|
|---|
| 142 | [
|
|---|
| 143 | (
|
|---|
| 144 | row[0].strftime("%Y-%m-%d %H:%M"),
|
|---|
| 145 | row[1].strftime("%Y-%m-%d %H:%M"),
|
|---|
| 146 | row[2],
|
|---|
| 147 | row[3][:40],
|
|---|
| 148 | )
|
|---|
| 149 | for row in reservations
|
|---|
| 150 | ],
|
|---|
| 151 | )
|
|---|
| 152 | else:
|
|---|
| 153 | print("\n No upcoming reservations.")
|
|---|
| 154 |
|
|---|
| 155 |
|
|---|
| 156 | def _check_availability(resources):
|
|---|
| 157 | if not resources:
|
|---|
| 158 | print(" No resources to check.")
|
|---|
| 159 | return
|
|---|
| 160 | print("\n Select a resource to check availability:")
|
|---|
| 161 | items = [r["name"] for r in resources]
|
|---|
| 162 | idx = pick_from_list(" Choose: ", items)
|
|---|
| 163 | if idx is None:
|
|---|
| 164 | return
|
|---|
| 165 |
|
|---|
| 166 | r = resources[idx]
|
|---|
| 167 | date = input_date(" Date (YYYY-MM-DD): ")
|
|---|
| 168 | start = input_time(" Start time (HH:MM): ")
|
|---|
| 169 | end = input_time(" End time (HH:MM): ")
|
|---|
| 170 |
|
|---|
| 171 | if end <= start:
|
|---|
| 172 | print(" End time must be after start time.")
|
|---|
| 173 | return
|
|---|
| 174 |
|
|---|
| 175 | # Check availability window
|
|---|
| 176 | day_of_week = date.isoweekday() # 1=Mon, 7=Sun
|
|---|
| 177 | if day_of_week > 5 and not r["available_weekends"]:
|
|---|
| 178 | print(f"\n NOT AVAILABLE: {r['name']} is not available on weekends.")
|
|---|
| 179 | return
|
|---|
| 180 | if start < r["available_from"] or end > r["available_to"]:
|
|---|
| 181 | print(
|
|---|
| 182 | f"\n NOT AVAILABLE: {r['name']} is only available "
|
|---|
| 183 | f"{r['available_from'].strftime('%H:%M')} - {r['available_to'].strftime('%H:%M')}."
|
|---|
| 184 | )
|
|---|
| 185 | return
|
|---|
| 186 |
|
|---|
| 187 | # Check conflicts
|
|---|
| 188 | start_dt = datetime.datetime.combine(date, start)
|
|---|
| 189 | end_dt = datetime.datetime.combine(date, end)
|
|---|
| 190 |
|
|---|
| 191 | with get_connection() as conn:
|
|---|
| 192 | with conn.cursor() as cur:
|
|---|
| 193 | cur.execute(
|
|---|
| 194 | """
|
|---|
| 195 | SELECT COUNT(*) FROM reservations
|
|---|
| 196 | WHERE resource_id = %s
|
|---|
| 197 | AND status IN ('approved', 'pending')
|
|---|
| 198 | AND start_time < %s
|
|---|
| 199 | AND end_time > %s
|
|---|
| 200 | """,
|
|---|
| 201 | (r["resource_id"], end_dt, start_dt),
|
|---|
| 202 | )
|
|---|
| 203 | conflicts = cur.fetchone()[0]
|
|---|
| 204 |
|
|---|
| 205 | if conflicts == 0:
|
|---|
| 206 | print(f"\n AVAILABLE: {r['name']} is free on {date} from {start.strftime('%H:%M')} to {end.strftime('%H:%M')}.")
|
|---|
| 207 | else:
|
|---|
| 208 | print(f"\n NOT AVAILABLE: {conflicts} conflicting reservation(s) found.")
|
|---|