source: uc_reserve.py

main
Last change on this file was 527c097, checked in by teo <teodorbogoeski@…>, 3 months ago

Add connection pooling and transaction management

  • Property mode set to 100644
File size: 8.0 KB
Line 
1import datetime
2
3from db import get_connection, get_transaction, pick_from_list, print_table, input_date, input_time
4
5
6def make_reservation(user_id):
7 """UC0002: Make a Resource Reservation."""
8 print("\n=== Make a Resource Reservation ===\n")
9
10 # Step 1: Select resource
11 resources = _fetch_all_resources()
12 if not resources:
13 print(" No resources available.")
14 return
15
16 items = []
17 for r in resources:
18 loc = f"{r['building']} / {r['room']}" if r["building"] else "Digital"
19 items.append(f"{r['name']} [{r['type_name']}] - {loc}")
20 idx = pick_from_list(" Select a resource to reserve: ", items)
21 if idx is None:
22 return
23
24 resource = resources[idx]
25 rid = resource["resource_id"]
26
27 # Step 2: Show resource details and upcoming reservations
28 print(f"\n === {resource['name']} ===")
29 print(f" Available: {resource['available_from'].strftime('%H:%M')} - {resource['available_to'].strftime('%H:%M')}")
30 print(f" Weekends: {'Yes' if resource['available_weekends'] else 'No'}")
31
32 _show_upcoming(rid)
33
34 # Step 3: Enter reservation details
35 while True:
36 print()
37 date = input_date(" Reservation date (YYYY-MM-DD): ")
38 start = input_time(" Start time (HH:MM): ")
39 end = input_time(" End time (HH:MM): ")
40
41 if end <= start:
42 print(" End time must be after start time.")
43 continue
44
45 purpose = input(" Purpose: ").strip()
46 if not purpose:
47 print(" Purpose is required.")
48 continue
49 if len(purpose) > 512:
50 purpose = purpose[:512]
51
52 # Step 4: Validate availability window
53 day_of_week = date.isoweekday()
54 if day_of_week > 5 and not resource["available_weekends"]:
55 print(f"\n ERROR: {resource['name']} is not available on weekends.")
56 if not _retry():
57 return
58 continue
59
60 if start < resource["available_from"] or end > resource["available_to"]:
61 print(
62 f"\n ERROR: {resource['name']} is only available "
63 f"{resource['available_from'].strftime('%H:%M')} - {resource['available_to'].strftime('%H:%M')}."
64 )
65 if not _retry():
66 return
67 continue
68
69 # Step 5: Check conflicts
70 start_dt = datetime.datetime.combine(date, start)
71 end_dt = datetime.datetime.combine(date, end)
72 conflicts = _check_conflicts(rid, start_dt, end_dt)
73
74 if conflicts:
75 print(f"\n CONFLICT: {len(conflicts)} overlapping reservation(s) found:")
76 print_table(
77 ["ID", "Start", "End", "Purpose", "Reserved by"],
78 [
79 (c[0], c[1].strftime("%Y-%m-%d %H:%M"), c[2].strftime("%Y-%m-%d %H:%M"), c[3][:30], c[4])
80 for c in conflicts
81 ],
82 )
83 if not _retry():
84 return
85 continue
86
87 # Step 6: Confirm
88 print(f"\n --- Reservation Summary ---")
89 print(f" Resource: {resource['name']}")
90 print(f" Date: {date}")
91 print(f" Time: {start.strftime('%H:%M')} - {end.strftime('%H:%M')}")
92 print(f" Purpose: {purpose}")
93 confirm = input("\n Confirm reservation? (y/n): ").strip().lower()
94 if confirm not in ("y", "yes"):
95 print(" Reservation cancelled.")
96 return
97
98 # Step 7: INSERT (transaction: re-check conflicts + insert atomically)
99 try:
100 with get_transaction() as conn:
101 with conn.cursor() as cur:
102 # Re-check conflicts inside the transaction to prevent
103 # race conditions (another user inserting between our
104 # check and insert)
105 cur.execute(
106 """
107 SELECT COUNT(*) FROM reservations
108 WHERE resource_id = %s
109 AND status IN ('approved', 'pending')
110 AND start_time < %s AND end_time > %s
111 """,
112 (rid, end_dt, start_dt),
113 )
114 if cur.fetchone()[0] > 0:
115 raise RuntimeError("Conflict: another reservation was just created for this time slot")
116
117 cur.execute(
118 """
119 INSERT INTO reservations
120 (start_time, end_time, status, purpose, created_at, user_id, resource_id)
121 VALUES (%s, %s, 'pending', %s, CURRENT_TIMESTAMP, %s, %s)
122 RETURNING reservation_id, status, created_at
123 """,
124 (start_dt, end_dt, purpose, user_id, rid),
125 )
126 result = cur.fetchone()
127 # Transaction committed automatically
128 print(f"\n Reservation created successfully!")
129 print(f" Reservation ID: {result[0]}")
130 print(f" Status: {result[1]} (awaiting administrator approval)")
131 print(f" Created at: {result[2].strftime('%Y-%m-%d %H:%M')}")
132 return
133 except Exception as e:
134 # Transaction rolled back automatically
135 print(f"\n Error creating reservation: {e}")
136 return
137
138
139def _fetch_all_resources():
140 with get_connection() as conn:
141 with conn.cursor() as cur:
142 cur.execute("""
143 SELECT r.resource_id, r.name, r.description,
144 r.available_from, r.available_to, r.available_weekends,
145 rt.type_name, l.building, l.room
146 FROM resources r
147 JOIN resource_types rt ON r.type_id = rt.type_id
148 LEFT JOIN locations l ON r.location_id = l.location_id
149 ORDER BY rt.type_name, r.name
150 """)
151 cols = [desc[0] for desc in cur.description]
152 return [dict(zip(cols, row)) for row in cur.fetchall()]
153
154
155def _show_upcoming(resource_id):
156 today = datetime.date.today()
157 week_end = today + datetime.timedelta(days=7)
158 with get_connection() as conn:
159 with conn.cursor() as cur:
160 cur.execute(
161 """
162 SELECT res.start_time, res.end_time, res.status, res.purpose,
163 u.first_name || ' ' || u.last_name AS reserved_by
164 FROM reservations res
165 JOIN users u ON res.user_id = u.user_id
166 WHERE res.resource_id = %s
167 AND res.status IN ('approved', 'pending')
168 AND res.start_time >= %s AND res.start_time < %s
169 ORDER BY res.start_time
170 """,
171 (resource_id, today, week_end),
172 )
173 rows = cur.fetchall()
174 if rows:
175 print(f"\n Reservations in the next 7 days:")
176 print_table(
177 ["Start", "End", "Status", "Purpose", "By"],
178 [
179 (r[0].strftime("%Y-%m-%d %H:%M"), r[1].strftime("%Y-%m-%d %H:%M"), r[2], r[3][:30], r[4])
180 for r in rows
181 ],
182 )
183 else:
184 print("\n No reservations in the next 7 days.")
185
186
187def _check_conflicts(resource_id, start_dt, end_dt):
188 with get_connection() as conn:
189 with conn.cursor() as cur:
190 cur.execute(
191 """
192 SELECT res.reservation_id, res.start_time, res.end_time,
193 res.purpose,
194 u.first_name || ' ' || u.last_name AS reserved_by
195 FROM reservations res
196 JOIN users u ON res.user_id = u.user_id
197 WHERE res.resource_id = %s
198 AND res.status IN ('approved', 'pending')
199 AND res.start_time < %s
200 AND res.end_time > %s
201 """,
202 (resource_id, end_dt, start_dt),
203 )
204 return cur.fetchall()
205
206
207def _retry():
208 choice = input(" Try a different time? (y/n): ").strip().lower()
209 return choice in ("y", "yes")
Note: See TracBrowser for help on using the repository browser.