| [95cf703] | 1 | from db import get_connection, pick_from_list, print_table
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 | def approve_reservations(admin_user_id):
|
|---|
| 5 | """UC0003: Approve or Reject Reservations."""
|
|---|
| 6 | while True:
|
|---|
| 7 | pending = _fetch_pending()
|
|---|
| 8 | if not pending:
|
|---|
| 9 | print("\n No pending reservations.")
|
|---|
| 10 | return
|
|---|
| 11 |
|
|---|
| 12 | print(f"\n=== Pending Reservations ({len(pending)}) ===\n")
|
|---|
| 13 | items = []
|
|---|
| 14 | for p in pending:
|
|---|
| 15 | loc = f"{p['building']} / {p['room']}" if p["building"] else "Digital"
|
|---|
| 16 | items.append(
|
|---|
| 17 | f"[#{p['reservation_id']}] {p['purpose'][:40]}\n"
|
|---|
| 18 | f" by {p['requested_by']} ({p['requester_role']})\n"
|
|---|
| 19 | f" {p['resource_name']} ({loc})\n"
|
|---|
| 20 | f" {p['start_time'].strftime('%Y-%m-%d %H:%M')} - {p['end_time'].strftime('%Y-%m-%d %H:%M')}"
|
|---|
| 21 | )
|
|---|
| 22 | idx = pick_from_list(" Select a reservation to review: ", items)
|
|---|
| 23 | if idx is None:
|
|---|
| 24 | return
|
|---|
| 25 |
|
|---|
| 26 | reservation = pending[idx]
|
|---|
| 27 | _review_reservation(reservation, admin_user_id)
|
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 | def _fetch_pending():
|
|---|
| 31 | with get_connection() as conn:
|
|---|
| 32 | with conn.cursor() as cur:
|
|---|
| 33 | cur.execute("""
|
|---|
| 34 | SELECT res.reservation_id, res.start_time, res.end_time,
|
|---|
| 35 | res.purpose, res.created_at, res.recurrence_group_id, res.resource_id,
|
|---|
| 36 | u.first_name || ' ' || u.last_name AS requested_by,
|
|---|
| 37 | u.email AS requester_email,
|
|---|
| 38 | ut.type_name AS requester_role,
|
|---|
| 39 | r.name AS resource_name,
|
|---|
| 40 | rt.type_name AS resource_type,
|
|---|
| 41 | l.building, l.room
|
|---|
| 42 | FROM reservations res
|
|---|
| 43 | JOIN users u ON res.user_id = u.user_id
|
|---|
| 44 | JOIN user_types ut ON u.type_id = ut.type_id
|
|---|
| 45 | JOIN resources r ON res.resource_id = r.resource_id
|
|---|
| 46 | JOIN resource_types rt ON r.type_id = rt.type_id
|
|---|
| 47 | LEFT JOIN locations l ON r.location_id = l.location_id
|
|---|
| 48 | WHERE res.status = 'pending'
|
|---|
| 49 | ORDER BY res.created_at ASC
|
|---|
| 50 | """)
|
|---|
| 51 | cols = [desc[0] for desc in cur.description]
|
|---|
| 52 | return [dict(zip(cols, row)) for row in cur.fetchall()]
|
|---|
| 53 |
|
|---|
| 54 |
|
|---|
| 55 | def _review_reservation(res, admin_user_id):
|
|---|
| 56 | print(f"\n === Reservation #{res['reservation_id']} ===")
|
|---|
| 57 | print(f" Purpose: {res['purpose']}")
|
|---|
| 58 | print(f" Requested by: {res['requested_by']} ({res['requester_role']})")
|
|---|
| 59 | print(f" Email: {res['requester_email']}")
|
|---|
| 60 | print(f" Resource: {res['resource_name']} ({res['resource_type']})")
|
|---|
| 61 | if res["building"]:
|
|---|
| 62 | print(f" Location: {res['building']} / {res['room']}")
|
|---|
| 63 | print(f" Time: {res['start_time'].strftime('%Y-%m-%d %H:%M')} - {res['end_time'].strftime('%Y-%m-%d %H:%M')}")
|
|---|
| 64 | print(f" Created: {res['created_at'].strftime('%Y-%m-%d %H:%M')}")
|
|---|
| 65 |
|
|---|
| 66 | # Show recurrence series if applicable
|
|---|
| 67 | if res["recurrence_group_id"]:
|
|---|
| 68 | print(f"\n Part of recurring series:")
|
|---|
| 69 | with get_connection() as conn:
|
|---|
| 70 | with conn.cursor() as cur:
|
|---|
| 71 | cur.execute(
|
|---|
| 72 | """
|
|---|
| 73 | SELECT reservation_id, start_time, end_time, status
|
|---|
| 74 | FROM reservations
|
|---|
| 75 | WHERE recurrence_group_id = %s
|
|---|
| 76 | ORDER BY start_time
|
|---|
| 77 | """,
|
|---|
| 78 | (str(res["recurrence_group_id"]),),
|
|---|
| 79 | )
|
|---|
| 80 | series = cur.fetchall()
|
|---|
| 81 | print_table(
|
|---|
| 82 | ["ID", "Start", "End", "Status"],
|
|---|
| 83 | [(r[0], r[1].strftime("%Y-%m-%d %H:%M"), r[2].strftime("%Y-%m-%d %H:%M"), r[3]) for r in series],
|
|---|
| 84 | )
|
|---|
| 85 |
|
|---|
| 86 | # Check conflicts
|
|---|
| 87 | with get_connection() as conn:
|
|---|
| 88 | with conn.cursor() as cur:
|
|---|
| 89 | cur.execute(
|
|---|
| 90 | """
|
|---|
| 91 | SELECT res2.reservation_id, res2.start_time, res2.end_time,
|
|---|
| 92 | res2.purpose,
|
|---|
| 93 | u.first_name || ' ' || u.last_name AS reserved_by
|
|---|
| 94 | FROM reservations res2
|
|---|
| 95 | JOIN users u ON res2.user_id = u.user_id
|
|---|
| 96 | WHERE res2.resource_id = %s
|
|---|
| 97 | AND res2.reservation_id != %s
|
|---|
| 98 | AND res2.status = 'approved'
|
|---|
| 99 | AND res2.start_time < %s
|
|---|
| 100 | AND res2.end_time > %s
|
|---|
| 101 | """,
|
|---|
| 102 | (res["resource_id"], res["reservation_id"], res["end_time"], res["start_time"]),
|
|---|
| 103 | )
|
|---|
| 104 | conflicts = cur.fetchall()
|
|---|
| 105 |
|
|---|
| 106 | if conflicts:
|
|---|
| 107 | print(f"\n WARNING: {len(conflicts)} conflict(s) with approved reservations:")
|
|---|
| 108 | print_table(
|
|---|
| 109 | ["ID", "Start", "End", "Purpose", "Reserved by"],
|
|---|
| 110 | [(c[0], c[1].strftime("%Y-%m-%d %H:%M"), c[2].strftime("%Y-%m-%d %H:%M"), c[3][:30], c[4]) for c in conflicts],
|
|---|
| 111 | )
|
|---|
| 112 | else:
|
|---|
| 113 | print("\n No conflicts with approved reservations.")
|
|---|
| 114 |
|
|---|
| 115 | # Action
|
|---|
| 116 | print()
|
|---|
| 117 | action = pick_from_list(" Choose action: ", ["Approve", "Reject", "Skip"])
|
|---|
| 118 | if action is None or action == 2:
|
|---|
| 119 | return
|
|---|
| 120 |
|
|---|
| 121 | new_status = "approved" if action == 0 else "rejected"
|
|---|
| 122 | conn = get_connection()
|
|---|
| 123 | try:
|
|---|
| 124 | with conn.cursor() as cur:
|
|---|
| 125 | cur.execute(
|
|---|
| 126 | """
|
|---|
| 127 | UPDATE reservations
|
|---|
| 128 | SET status = %s, approved_by = %s
|
|---|
| 129 | WHERE reservation_id = %s AND status = 'pending'
|
|---|
| 130 | RETURNING reservation_id, status
|
|---|
| 131 | """,
|
|---|
| 132 | (new_status, admin_user_id, res["reservation_id"]),
|
|---|
| 133 | )
|
|---|
| 134 | result = cur.fetchone()
|
|---|
| 135 | conn.commit()
|
|---|
| 136 | if result:
|
|---|
| 137 | print(f"\n Reservation #{result[0]} has been {result[1]}.")
|
|---|
| 138 | else:
|
|---|
| 139 | print("\n Reservation was already processed by another administrator.")
|
|---|
| 140 | except Exception as e:
|
|---|
| 141 | conn.rollback()
|
|---|
| 142 | print(f"\n Error: {e}")
|
|---|
| 143 | finally:
|
|---|
| 144 | conn.close()
|
|---|