diff --git a/.idea/ OOP-PythonAssignment-200L-NUN-2025.iml b/.idea/ OOP-PythonAssignment-200L-NUN-2025.iml
new file mode 100644
index 0000000..2c80e12
--- /dev/null
+++ b/.idea/ OOP-PythonAssignment-200L-NUN-2025.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..fc5a4f3
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..11b66a2
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..f550868
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..b0f621f
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1747358063617
+
+
+ 1747358063617
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.venv/.gitignore b/.venv/.gitignore
new file mode 100644
index 0000000..cd485e2
--- /dev/null
+++ b/.venv/.gitignore
@@ -0,0 +1,9 @@
+# Python virtualenv
+.venv/
+Lib/
+Scripts/
+pyvenv.cfg
+
+# Byte-compiled files
+__pycache__/
+*.py[cod]
diff --git a/GUI.py b/GUI.py
new file mode 100644
index 0000000..d86f50e
--- /dev/null
+++ b/GUI.py
@@ -0,0 +1,379 @@
+import tkinter as tk
+from tkinter import ttk, messagebox
+from hotel_management import HotelManagement
+from guest import Guest
+
+class HotelApp:
+ def __init__(self, root):
+ self.root = root
+ root.title("Hotel Management")
+ root.geometry("360x640")
+ root.resizable(False, False)
+ root.configure(bg="skyblue")
+
+ # ─── Styles ───────────────────────────────────────────────────
+ self.style = ttk.Style(root)
+ self.style.theme_use('default')
+
+ # Accent button style (bright blue, white text, padded)
+ self.style.configure(
+ 'Accent.TButton',
+ background='#4eb5f1',
+ foreground='white',
+ font=('Helvetica', 16),
+ padding=10,
+ borderwidth=0
+ )
+ self.style.map(
+ 'Accent.TButton',
+ background=[('active', '#66c3ff')]
+ )
+
+ # Treeview style (taller rows, bold headings, light header bg)
+ self.style.configure(
+ 'Custom.Treeview',
+ rowheight=28,
+ font=('Helvetica', 12),
+ fieldbackground='white'
+ )
+ self.style.configure(
+ 'Custom.Treeview.Heading',
+ font=('Helvetica', 13, 'bold'),
+ background='#e0e0e0'
+ )
+ self.style.layout('Custom.Treeview', self.style.layout('Treeview'))
+
+ # ─── Backend ──────────────────────────────────────────────────
+ self.hm = HotelManagement()
+ self.hm.load_data()
+ self.current_guest = None
+
+ # ─── Frames ───────────────────────────────────────────────────
+ self.login_frame = tk.Frame(root, bg="gray")
+ self.menu_frame = tk.Frame(root, bg="gray")
+ self.book_frame = tk.Frame(root, bg="gray")
+ self.view_frame = tk.Frame(root, bg="gray")
+ self.modify_frame = tk.Frame(root, bg="gray")
+ for f in (
+ self.login_frame,
+ self.menu_frame,
+ self.book_frame,
+ self.view_frame,
+ self.modify_frame
+ ):
+ f.place(relwidth=1, relheight=1)
+
+ # ─── Build UI ─────────────────────────────────────────────────
+ self._build_login()
+ self._build_menu()
+ self._build_book()
+ self._build_view()
+ self._build_modify()
+
+ # start on login
+ self._show(self.login_frame)
+
+ def _show(self, frame):
+ frame.tkraise()
+
+ # ─── Login Screen ───────────────────────────────────────────────
+
+ def _build_login(self):
+ frm = self.login_frame
+ frm.columnconfigure(0, weight=1)
+
+ ttk.Label(frm, text="Select Existing Guest:", background="gray",
+ font=('Helvetica',12)).grid(row=0, column=0, pady=(30,5))
+ self.cb_guest = ttk.Combobox(frm, state="readonly")
+ self.cb_guest.grid(row=1, column=0, sticky="we", padx=30)
+
+ ttk.Button(frm, text="Log In", style='Accent.TButton',
+ command=self._on_login).grid(row=2, column=0, pady=15, padx=50)
+
+ ttk.Separator(frm, orient="horizontal")\
+ .grid(row=3, column=0, sticky="ew", padx=30, pady=15)
+
+ ttk.Label(frm, text="Name:", background="gray").grid(
+ row=4, column=0, sticky="w", padx=30
+ )
+ self.e_name = ttk.Entry(frm)
+ self.e_name.grid(row=5, column=0, sticky="we", padx=30, pady=5)
+
+ ttk.Label(frm, text="Contact Info:", background="gray").grid(
+ row=6, column=0, sticky="w", padx=30, pady=(10,0)
+ )
+ self.e_contact = ttk.Entry(frm)
+ self.e_contact.grid(row=7, column=0, sticky="we", padx=30, pady=5)
+
+ ttk.Button(frm, text="Register & Log In", style='Accent.TButton',
+ command=self._on_register).grid(row=8, column=0, pady=25, padx=50)
+
+ self._reload_guest_list()
+
+ def _reload_guest_list(self):
+ self._guest_map = {
+ f"{g.name} — {g.contact_info}": g.guest_id
+ for g in self.hm.guests.values()
+ }
+ self.cb_guest['values'] = list(self._guest_map)
+ self.cb_guest.set("")
+
+ def _on_login(self):
+ sel = self.cb_guest.get()
+ gid = self._guest_map.get(sel)
+ if not gid:
+ messagebox.showwarning("Login Error", "Please select a guest.")
+ return
+ self.current_guest = self.hm.guests[gid]
+ self._enter_menu()
+
+ def _on_register(self):
+ name = self.e_name.get().strip()
+ contact = self.e_contact.get().strip()
+ if not name or not contact:
+ messagebox.showwarning("Input Error", "Name & Contact required.")
+ return
+ guest = Guest(name=name, contact_info=contact)
+ self.hm.add_guest(guest)
+ self.current_guest = guest
+ self._reload_guest_list()
+ self._enter_menu()
+
+
+ # ─── Main Menu ──────────────────────────────────────────────────
+
+ def _build_menu(self):
+ frm = self.menu_frame
+ frm.columnconfigure(0, weight=1)
+
+ self.lbl_welcome = ttk.Label(
+ frm, text="", background="gray",
+ font=('Helvetica', 16, 'bold')
+ )
+ self.lbl_welcome.grid(row=0, column=0, pady=40)
+
+ ttk.Button(frm, text="Make New Booking", style='Accent.TButton',
+ command=self._enter_book).grid(row=1, column=0, pady=8, padx=60)
+ ttk.Button(frm, text="View Bookings", style='Accent.TButton',
+ command=self._enter_view).grid(row=2, column=0, pady=8, padx=60)
+ ttk.Button(frm, text="Modify Bookings", style='Accent.TButton',
+ command=self._enter_modify).grid(row=3, column=0, pady=8, padx=60)
+ ttk.Button(frm, text="Logout", style='Accent.TButton',
+ command=self._logout).grid(row=4, column=0, pady=30, padx=60)
+
+ def _enter_menu(self):
+ self.lbl_welcome.config(
+ text=f"Hello, {self.current_guest.name} ({self.current_guest.guest_id})"
+ )
+ self._show(self.menu_frame)
+
+ def _logout(self):
+ self.current_guest = None
+ self.e_name.delete(0, 'end')
+ self.e_contact.delete(0, 'end')
+ self._reload_guest_list()
+ self._show(self.login_frame)
+
+
+ # ─── Search & Book Screen ───────────────────────────────────────
+
+ def _build_book(self):
+ frm = self.book_frame
+ frm.columnconfigure(0, weight=1)
+
+ ttk.Label(frm, text="Room Type:", background="gray").grid(
+ row=0, column=0, sticky="w", padx=30, pady=(30,5)
+ )
+ self.cb_type = ttk.Combobox(frm, state="readonly")
+ self.cb_type.grid(row=1, column=0, padx=30, sticky="we")
+ self.cb_type.bind("<>", lambda e: self._refresh_rooms())
+
+ ttk.Label(frm, text="Room #:", background="gray").grid(
+ row=2, column=0, sticky="w", padx=30, pady=(20,5)
+ )
+ self.cb_rooms = ttk.Combobox(frm, state="readonly")
+ self.cb_rooms.grid(row=3, column=0, padx=30, sticky="we")
+
+ ttk.Label(frm, text="Stay (days):", background="gray").grid(
+ row=4, column=0, sticky="w", padx=30, pady=(20,5)
+ )
+ self.e_days = ttk.Entry(frm)
+ self.e_days.grid(row=5, column=0, padx=30, sticky="we")
+
+ ttk.Button(frm, text="Book", style='Accent.TButton',
+ command=self._on_book).grid(
+ row=6, column=0, pady=20, padx=80
+ )
+ ttk.Button(frm, text="Logout", style='Accent.TButton',
+ command=self._logout).grid(
+ row=7, column=0, pady=10, padx=80
+ )
+
+ def _enter_book(self):
+ types = sorted({r.room_type for r in self.hm.rooms.values()})
+ self.cb_type['values'] = types
+ self.cb_type.set('')
+ self.cb_rooms.set('')
+ self.e_days.delete(0, 'end')
+ self._show(self.book_frame)
+
+ def _refresh_rooms(self):
+ sel = self.cb_type.get().lower().strip()
+ avail = [
+ r.room_number for r in self.hm.rooms.values()
+ if r.room_type.lower()==sel and r.availability
+ ]
+ self.cb_rooms['values'] = avail
+ self.cb_rooms.set('')
+
+ def _on_book(self):
+ try:
+ room_no = int(self.cb_rooms.get())
+ days = int(self.e_days.get())
+ if days<1: raise ValueError
+ except:
+ messagebox.showwarning(
+ "Input Error",
+ "Pick a room and enter a positive number of days."
+ )
+ return
+ try:
+ self.hm.make_reservation(self.current_guest, room_no, days)
+ messagebox.showinfo("Success", f"Booked room {room_no} for {days} days.")
+ except Exception as e:
+ messagebox.showerror("Booking Failed", str(e))
+ return
+ self._refresh_rooms()
+
+
+ # ─── View Bookings Screen ───────────────────────────────────────
+
+ def _build_view(self):
+ frm = self.view_frame
+ frm.columnconfigure(0, weight=1)
+
+ cols = ("Room","Check-in","Check-out")
+ self.tv_view = ttk.Treeview(
+ frm, columns=cols, show="headings",
+ style='Custom.Treeview', height=10
+ )
+ for c in cols:
+ self.tv_view.heading(c, text=c)
+ self.tv_view.column(c, width=100, anchor="center")
+ self.tv_view.grid(row=0, column=0, pady=(30,10), padx=10)
+
+ ttk.Button(frm, text="Back", style='Accent.TButton',
+ command=self._enter_menu).grid(row=1, column=0, pady=20, padx=100)
+
+ def _enter_view(self):
+ self.tv_view.delete(*self.tv_view.get_children())
+ for res in self.hm.reservations:
+ if res.guest.guest_id == self.current_guest.guest_id and res.is_active:
+ self.tv_view.insert("", "end", values=(
+ res.room.room_number,
+ res.check_in_date.strftime("%d/%m/%Y"),
+ res.check_out_date.strftime("%d/%m/%Y")
+ ))
+ self._show(self.view_frame)
+
+
+ # ─── Modify Bookings Screen ────────────────────────────────────
+
+ def _build_modify(self):
+ frm = self.modify_frame
+ frm.columnconfigure(0, weight=1)
+
+ cols = ("Room","Check-in","Check-out")
+ self.tv_mod = ttk.Treeview(
+ frm, columns=cols, show="headings",
+ style='Custom.Treeview', height=8
+ )
+ for c in cols:
+ self.tv_mod.heading(c, text=c)
+ self.tv_mod.column(c, width=100, anchor="center")
+ self.tv_mod.grid(row=0, column=0, pady=(30,10), padx=10)
+
+ btn_frame = tk.Frame(frm, bg="gray")
+ btn_frame.grid(row=1, column=0, pady=20)
+ ttk.Button(btn_frame, text="Back", style='Accent.TButton',
+ command=self._enter_menu).grid(row=0, column=0, padx=5)
+ ttk.Button(btn_frame, text="Modify", style='Accent.TButton',
+ command=self._on_modify_popup).grid(row=0, column=1, padx=5)
+
+ def _enter_modify(self):
+ self.tv_mod.delete(*self.tv_mod.get_children())
+ for idx, res in enumerate(self.hm.reservations):
+ if res.guest.guest_id == self.current_guest.guest_id and res.is_active:
+ self.tv_mod.insert("", "end", iid=str(idx), values=(
+ res.room.room_number,
+ res.check_in_date.strftime("%d/%m/%Y"),
+ res.check_out_date.strftime("%d/%m/%Y")
+ ))
+ self._show(self.modify_frame)
+
+ def _on_modify_popup(self):
+ sel = self.tv_mod.selection()
+ if not sel:
+ messagebox.showwarning("Select Reservation", "Please select one.")
+ return
+ idx = int(sel[0])
+ res = self.hm.reservations[idx]
+
+ win = tk.Toplevel(self.root)
+ win.title("Modify Reservation")
+ win.geometry("280x200")
+ win.resizable(False, False)
+ win.configure(bg="gray")
+
+ ttk.Label(win, text=f"Room {res.room.room_number}",
+ background="gray", font=('Helvetica',14))\
+ .grid(row=0, column=0, columnspan=2, pady=10)
+
+ ttk.Label(win, text="New Stay (days):",
+ background="gray")\
+ .grid(row=1, column=0, sticky="e", padx=10)
+ e_days = ttk.Entry(win)
+ e_days.grid(row=1, column=1, padx=10, pady=5)
+
+ ttk.Button(win, text="Update", style='Accent.TButton',
+ command=lambda: self._modify_confirm(res, e_days, win))\
+ .grid(row=2, column=0, pady=15, padx=5)
+ ttk.Button(win, text="Cancel Reservation", style='Accent.TButton',
+ command=lambda: self._cancel_confirm(res, win))\
+ .grid(row=2, column=1, pady=15, padx=5)
+
+ def _modify_confirm(self, res, e_days, win):
+ try:
+ newd = int(e_days.get())
+ if newd < 1:
+ raise ValueError
+ except:
+ messagebox.showwarning("Input Error", "Enter a positive number.")
+ return
+ try:
+ self.hm.modify_reservation(res, newd)
+ messagebox.showinfo("Updated", "Reservation updated.")
+ win.destroy()
+ self._enter_modify()
+ except Exception as e:
+ messagebox.showerror("Error", str(e))
+
+ def _cancel_confirm(self, res, win):
+ if messagebox.askyesno("Confirm Cancel",
+ "Are you sure you want to cancel?"):
+ try:
+ self.hm.cancel_reservation(res)
+ messagebox.showinfo("Canceled", "Reservation canceled.")
+ win.destroy()
+ self._enter_modify()
+ except Exception as e:
+ messagebox.showerror("Error", str(e))
+
+
+def main():
+ root = tk.Tk()
+ app = HotelApp(root)
+ root.mainloop()
+
+if __name__ == "__main__":
+ main()
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..de9a1e9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,99 @@
+=============================================================
+HOTEL MANAGEMENT SYSTEM – Python 3 + Tkinter
+=============================================================
+
+A phone-sized desktop app (360 x 640 window) for front-desk staff.
+
+ • Register or log-in guests (fixed 8-character IDs).
+ • Search available rooms and create new bookings.
+ • View current bookings for the logged-in guest.
+ • Modify a booking’s stay length or cancel it.
+ • Data is persisted in three CSV files (rooms / guests / reservations).
+ • No external libraries required – just Python 3 and the Tkinter that ships
+ with CPython.
+
+-------------------------------------------------------------
+1. PROJECT STRUCTURE
+-------------------------------------------------------------
+ hotel-management/
+ ├── GUI.py Tkinter front-end (5 stacked frames)
+ ├── hotel_management.py Data manager
+ ├── room.py Room entity
+ ├── guest.py Guest entity
+ ├── reservation.py Reservation entity
+ ├── rooms.csv Seeded list of 50 rooms
+ ├── guests.csv Starts with header only
+ └── reservations.csv Starts with header only
+
+-------------------------------------------------------------
+2. CSV LAYOUTS
+-------------------------------------------------------------
+ rooms.csv
+ Room Number , Room Type , Price , Availability (True/False)
+
+ guests.csv
+ Guest ID , Name , Contact Info
+
+ reservations.csv
+ Guest ID , Guest Name , Room Number ,
+ Check In Date , Check Out Date , Is Active (True/False)
+
+ All dates are stored in ISO format YYYY-MM-DD.
+
+-------------------------------------------------------------
+3. HOW TO RUN
+-------------------------------------------------------------
+ 1. Install Python 3.10+ (Tkinter is included on Windows/macOS.
+ On Debian/Ubuntu: sudo apt install python3-tk)
+
+ 2. From the project folder:
+ python GUI.py
+
+ 3. A 360 x 640 sky-blue window appears.
+
+ • Select an existing guest OR enter Name + Contact and click
+ “Register & Log In”.
+ • Main menu shows:
+ – Search and Book
+ – View Bookings
+ – Generate Receipt (modify / cancel booking)
+ – Logout
+ • All changes are saved immediately back to the CSV files.
+
+-------------------------------------------------------------
+4. SCREEN SUMMARY
+-------------------------------------------------------------
+ LOGIN
+ Choose existing guest or register a new one.
+
+ MAIN MENU
+ Search and Book / View Bookings / Generate Receipt / Logout
+
+ SEARCH & BOOK
+ Room Type -> Room # -> Stay days -> Book
+
+ VIEW BOOKINGS
+ Table shows Room, Check-in, Check-out for current guest.
+
+ MODIFY BOOKINGS
+ Same table + “Modify” button.
+ • Modify – change stay length.
+ • Cancel – delete booking and free room.
+
+-------------------------------------------------------------
+5. CODE OVERVIEW
+-------------------------------------------------------------
+ room.py Room(number, type, price, availability)
+ guest.py Guest(id, name, contact_info)
+ reservation.py Reservation(book, cancel, update)
+ hotel_management.py
+ – loads/saves CSV
+ – add_guest / make_reservation / modify_reservation / cancel_reservation
+ GUI.py
+ – five Tkinter frames (login, menu, book, view, modify)
+ – phone portrait size, grey theme, styled Treeview grids
+
+-------------------------------------------------------------
+Muhammad Muntazar Tasiu 20231725
+Muhammad Lele 20231940
+Almuktar Sani 20230101
\ No newline at end of file
diff --git a/__pycache__/guest.cpython-312.pyc b/__pycache__/guest.cpython-312.pyc
new file mode 100644
index 0000000..6ef6359
Binary files /dev/null and b/__pycache__/guest.cpython-312.pyc differ
diff --git a/__pycache__/hotel_management.cpython-312.pyc b/__pycache__/hotel_management.cpython-312.pyc
new file mode 100644
index 0000000..07f462e
Binary files /dev/null and b/__pycache__/hotel_management.cpython-312.pyc differ
diff --git a/__pycache__/reservation.cpython-312.pyc b/__pycache__/reservation.cpython-312.pyc
new file mode 100644
index 0000000..176f39f
Binary files /dev/null and b/__pycache__/reservation.cpython-312.pyc differ
diff --git a/__pycache__/room.cpython-312.pyc b/__pycache__/room.cpython-312.pyc
new file mode 100644
index 0000000..b969a71
Binary files /dev/null and b/__pycache__/room.cpython-312.pyc differ
diff --git a/guest.py b/guest.py
new file mode 100644
index 0000000..db20d24
--- /dev/null
+++ b/guest.py
@@ -0,0 +1,36 @@
+from uuid import uuid4
+from typing import Optional
+
+class Guest:
+
+ def __init__(
+ self,name: str,contact_info: str,guest_id: Optional[str] = None
+ ):
+ self._guest_id = guest_id or uuid4().hex[:8]
+ self._name = name
+ self._contact_info = contact_info
+
+ @property
+ def guest_id(self) -> str:
+ return self._guest_id
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ @property
+ def contact_info(self) -> str:
+ return self._contact_info
+
+ def to_dict(self) -> dict:
+ return {
+ "Guest ID": self._guest_id,
+ "Name": self._name,
+ "Contact Info": self._contact_info
+ }
+
+ def __str__(self) -> str:
+ return f"Guest[{self._guest_id}] {self._name}"
+
+ def __repr__(self) -> str:
+ return str(self)
diff --git a/guests.csv b/guests.csv
new file mode 100644
index 0000000..6c8446b
--- /dev/null
+++ b/guests.csv
@@ -0,0 +1,4 @@
+Guest ID,Name,Contact Info
+4888c255,Muhammad,20231725
+21fa4ece,Lele,20231940
+f43b215d,Almukhtar,20230101
diff --git a/hotel_management.py b/hotel_management.py
new file mode 100644
index 0000000..2d167e5
--- /dev/null
+++ b/hotel_management.py
@@ -0,0 +1,149 @@
+import csv
+from datetime import date
+from room import Room
+from guest import Guest
+from reservation import Reservation
+
+class HotelManagement:
+ ROOMS_FILE = "rooms.csv"
+ GUESTS_FILE = "guests.csv"
+ RESERVATIONS_FILE = "reservations.csv"
+
+ def __init__(self):
+ self.rooms : dict[int,Room] = {}
+ self.guests : dict[str,Guest] = {}
+ self.reservations : list[Reservation] = []
+
+ # ─── Loading ───────────────────────────────────────────────────────
+
+ def load_data(self) -> None:
+ self._load_rooms()
+ self._load_guests()
+ self._load_reservations()
+
+ def _load_rooms(self) -> None:
+ try:
+ with open(self.ROOMS_FILE, newline="") as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ num = int(row["Room Number"])
+ room = Room(
+ room_number = num,
+ room_type = row["Room Type"],
+ price = float(row["Price"]),
+ availability= row["Availability"].lower()=="true"
+ )
+ self.rooms[num] = room
+ except FileNotFoundError:
+ pass
+
+ def _load_guests(self) -> None:
+ try:
+ with open(self.GUESTS_FILE, newline="") as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ guest = Guest(
+ name = row["Name"],
+ contact_info = row["Contact Info"],
+ guest_id = row["Guest ID"]
+ )
+ self.guests[guest.guest_id] = guest
+ except FileNotFoundError:
+ pass
+
+ def _load_reservations(self) -> None:
+ try:
+ with open(self.RESERVATIONS_FILE, newline="") as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ gid = row["Guest ID"]
+ rid = int(row["Room Number"])
+ ci = date.fromisoformat(row["Check In Date"])
+ co = date.fromisoformat(row["Check Out Date"])
+ guest = self.guests.get(gid)
+ room = self.rooms.get(rid)
+ if not guest or not room:
+ continue
+ res = Reservation(
+ guest = guest,
+ room = room,
+ check_in_date = ci,
+ check_out_date = co
+ )
+ if row["Is Active"].lower() == "true":
+ res._is_active = True
+ res.room.availability = False
+ self.reservations.append(res)
+ except FileNotFoundError:
+ pass
+
+ # ─── Saving ────────────────────────────────────────────────────────
+
+ def save_data(self) -> None:
+ # Rooms
+ with open(self.ROOMS_FILE, 'w', newline='') as f:
+ writer = csv.DictWriter(
+ f,
+ fieldnames=["Room Number","Room Type","Price","Availability"]
+ )
+ writer.writeheader()
+ for r in self.rooms.values():
+ writer.writerow(r.to_dict())
+
+ # Guests
+ with open(self.GUESTS_FILE, 'w', newline='') as f:
+ writer = csv.DictWriter(
+ f,
+ fieldnames=["Guest ID","Name","Contact Info"]
+ )
+ writer.writeheader()
+ for g in self.guests.values():
+ writer.writerow(g.to_dict())
+
+ # Reservations
+ with open(self.RESERVATIONS_FILE, 'w', newline='') as f:
+ writer = csv.DictWriter(
+ f,
+ fieldnames=[
+ "Guest ID","Guest Name",
+ "Room Number","Check In Date","Check Out Date","Is Active"
+ ]
+ )
+ writer.writeheader()
+ for res in self.reservations:
+ # only active or we want to keep history?
+ writer.writerow(res.to_dict())
+
+ # ─── Guest Operations ─────────────────────────────────────────────
+
+ def add_guest(self, guest: Guest) -> None:
+ self.guests[guest.guest_id] = guest
+ self.save_data()
+
+ # ─── Reservation Operations ────────────────────────────────────────
+
+ def make_reservation(
+ self,guest: Guest,room_number: int,stay_duration_days: int
+ ) -> Reservation:
+ if room_number not in self.rooms:
+ raise KeyError(f"Room {room_number} does not exist.")
+ # prevent double-booking of same room:
+ room = self.rooms[room_number]
+ if not room.availability:
+ raise RuntimeError("Room is not available.")
+
+ res = Reservation(guest, room, stay_duration_days=stay_duration_days)
+ res.book()
+ self.reservations.append(res)
+ self.save_data()
+ return res
+
+ def modify_reservation(self, reservation: Reservation, new_days: int) -> None:
+ reservation.update_stay_duration(new_days)
+ self.save_data()
+
+ def cancel_reservation(self, reservation: Reservation) -> None:
+ reservation.cancel()
+ # remove it from our list
+ self.reservations = [r for r in self.reservations if r is not reservation]
+ self.save_data()
diff --git a/reservation.py b/reservation.py
new file mode 100644
index 0000000..158c505
--- /dev/null
+++ b/reservation.py
@@ -0,0 +1,85 @@
+from datetime import date, timedelta
+from typing import Any
+from room import Room
+from guest import Guest
+
+class Reservation:
+
+
+ def __init__(
+ self,
+ guest: Guest,room: Room,stay_duration_days: int | None = None,
+ check_in_date: date | None = None,check_out_date: date | None = None):
+ if check_in_date and check_out_date:
+ # loaded from CSV
+ self._check_in_date = check_in_date
+ self._check_out_date = check_out_date
+ elif stay_duration_days is not None:
+ today = date.today()
+ self._check_in_date = today
+ self._check_out_date = today + timedelta(days=stay_duration_days)
+ else:
+ raise ValueError("Must supply either stay_duration_days or explicit dates")
+
+ self._guest = guest
+ self._room = room
+ self._is_active = False
+
+ @property
+ def guest(self) -> Guest:
+ return self._guest
+
+ @property
+ def room(self) -> Room:
+ return self._room
+
+ @property
+ def check_in_date(self) -> date:
+ return self._check_in_date
+
+ @property
+ def check_out_date(self) -> date:
+ return self._check_out_date
+
+ @property
+ def is_active(self) -> bool:
+ return self._is_active
+
+ def book(self) -> None:
+ if not self._room.availability:
+ raise RuntimeError(f"Room {self._room.room_number} is not available.")
+ self._room.availability = False
+ self._is_active = True
+
+ def cancel(self) -> None:
+ if not self._is_active:
+ raise RuntimeError("Cannot cancel an inactive reservation.")
+ self._room.availability = True
+ self._is_active = False
+
+ def update_stay_duration(self, new_days: int) -> None:
+ if not self._is_active:
+ raise RuntimeError("Cannot modify an inactive reservation.")
+ self._check_out_date = self._check_in_date + timedelta(days=new_days)
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "Guest ID": self._guest.guest_id,
+ "Guest Name": self._guest.name,
+ "Room Number": self._room.room_number,
+ "Check In Date": self._check_in_date.isoformat(),
+ "Check Out Date": self._check_out_date.isoformat(),
+ "Is Active": self._is_active
+ }
+
+ def __str__(self) -> str:
+ ci = self._check_in_date.strftime("%d/%m/%Y")
+ co = self._check_out_date.strftime("%d/%m/%Y")
+ status = "Active" if self._is_active else "Inactive"
+ return (
+ f"Reservation[{self._guest.guest_id}→{self._room.room_number}] "
+ f"{ci} to {co} ({status})"
+ )
+
+ def __repr__(self) -> str:
+ return str(self)
diff --git a/reservations.csv b/reservations.csv
new file mode 100644
index 0000000..9b4a783
--- /dev/null
+++ b/reservations.csv
@@ -0,0 +1,5 @@
+Guest ID,Guest Name,Room Number,Check In Date,Check Out Date,Is Active
+4888c255,Muhammad,290,2025-05-15,2025-05-17,True
+f43b215d,Almukhtar,30,2025-05-15,2025-05-20,True
+21fa4ece,Lele,270,2025-05-15,2025-05-18,True
+21fa4ece,Lele,240,2025-05-16,2025-05-18,True
diff --git a/room.py b/room.py
new file mode 100644
index 0000000..c97d1e9
--- /dev/null
+++ b/room.py
@@ -0,0 +1,43 @@
+class Room:
+
+
+ def __init__(self, room_number: int, room_type: str, price: float, availability: bool = True):
+ self._room_number = room_number
+ self._room_type = room_type
+ self._price = price
+ self._availability = availability
+
+ @property
+ def room_number(self) -> int:
+ return self._room_number
+
+ @property
+ def room_type(self) -> str:
+ return self._room_type
+
+ @property
+ def price(self) -> float:
+ return self._price
+
+ @property
+ def availability(self) -> bool:
+ return self._availability
+
+ @availability.setter
+ def availability(self, val: bool):
+ self._availability = val
+
+ def to_dict(self) -> dict:
+ return {
+ "Room Number": self._room_number,
+ "Room Type": self._room_type,
+ "Price": self._price,
+ "Availability": self._availability
+ }
+
+ def __str__(self) -> str:
+ status = "Available" if self._availability else "Occupied"
+ return f"Room {self._room_number} ({self._room_type}) — {status} @ ${self._price:.2f}"
+
+ def __repr__(self) -> str:
+ return str(self)
diff --git a/rooms.csv b/rooms.csv
new file mode 100644
index 0000000..4fa5e21
--- /dev/null
+++ b/rooms.csv
@@ -0,0 +1,51 @@
+Room Number,Room Type,Price,Availability
+0,Standard,250.0,True
+5,Standard,250.0,True
+10,Standard,250.0,True
+15,Standard,250.0,True
+20,Standard,250.0,True
+25,Standard,250.0,True
+30,Standard,250.0,False
+35,Standard,250.0,True
+40,Standard,250.0,True
+45,Standard,250.0,True
+50,Standard,250.0,True
+55,Standard,250.0,True
+60,Standard,250.0,True
+65,Standard,250.0,True
+70,Standard,250.0,True
+75,Standard,250.0,True
+80,Standard,250.0,True
+85,Standard,250.0,True
+90,Standard,250.0,True
+95,Standard,250.0,True
+100,Family,450.0,True
+105,Family,450.0,True
+110,Family,450.0,True
+115,Family,450.0,True
+120,Family,450.0,True
+125,Family,450.0,True
+130,Family,450.0,True
+135,Family,450.0,True
+140,Family,450.0,True
+145,Family,450.0,True
+150,Deluxe,700.0,True
+155,Deluxe,700.0,True
+160,Deluxe,700.0,True
+165,Deluxe,700.0,True
+170,Deluxe,700.0,True
+175,Deluxe,700.0,True
+180,Deluxe,700.0,True
+185,Deluxe,700.0,True
+190,Deluxe,700.0,True
+195,Deluxe,700.0,True
+200,Penthouse,1500.0,True
+210,Penthouse,1500.0,True
+220,Penthouse,1500.0,True
+230,Penthouse,1500.0,True
+240,Penthouse,1500.0,False
+250,Penthouse,1500.0,True
+260,Penthouse,1500.0,True
+270,Penthouse,1500.0,False
+280,Penthouse,1500.0,True
+290,Penthouse,1500.0,False