booking_manegment.py

PHOTO EMBED

Tue May 13 2025 11:12:24 GMT+0000 (Coordinated Universal Time)

Saved by @dev_shubham14

import tkinter as tk
from tkinter import ttk, messagebox
from datetime import datetime, timedelta
from tkcalendar import DateEntry

class BookingManagement(ttk.Frame):
    def __init__(self, parent, db):
        super().__init__(parent)
        self.db = db
        self.pack(fill=tk.BOTH, expand=True)
        
        # Create UI elements
        self._create_widgets()
        
        # Populate booking list
        self._populate_booking_list()
        
    def _create_widgets(self):
        """Create UI widgets for booking management"""
        # Title
        title_label = ttk.Label(self, text="Booking Management", font=("Arial", 14, "bold"))
        title_label.grid(row=0, column=0, columnspan=2, pady=10, sticky=tk.W)
        
        # Main content split into two frames
        left_frame = ttk.Frame(self)
        left_frame.grid(row=1, column=0, sticky=tk.NSEW, padx=(0, 10))
        
        right_frame = ttk.Frame(self)
        right_frame.grid(row=1, column=1, sticky=tk.NSEW)
        
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)
        self.rowconfigure(1, weight=1)
        
        # === Left Frame: Booking List ===
        list_label = ttk.Label(left_frame, text="Current Bookings", font=("Arial", 12, "bold"))
        list_label.pack(anchor=tk.W, pady=(0, 10))
        
        # Booking list with scrollbar
        list_frame = ttk.Frame(left_frame)
        list_frame.pack(fill=tk.BOTH, expand=True)
        
        self.booking_tree = ttk.Treeview(list_frame, 
                                      columns=("Guest", "Room", "Check In", "Check Out", "Status"), 
                                      selectmode="browse", show="headings")
        self.booking_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # Configure columns
        self.booking_tree.heading("Guest", text="Guest")
        self.booking_tree.heading("Room", text="Room")
        self.booking_tree.heading("Check In", text="Check In")
        self.booking_tree.heading("Check Out", text="Check Out")
        self.booking_tree.heading("Status", text="Status")
        
        self.booking_tree.column("Guest", width=150)
        self.booking_tree.column("Room", width=70)
        self.booking_tree.column("Check In", width=100)
        self.booking_tree.column("Check Out", width=100)
        self.booking_tree.column("Status", width=100)
        
        # Add scrollbar
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.booking_tree.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.booking_tree.configure(yscrollcommand=scrollbar.set)
        
        # Bind selection event
        self.booking_tree.bind("<<TreeviewSelect>>", self._on_booking_select)
        
        # Filter controls
        filter_frame = ttk.Frame(left_frame)
        filter_frame.pack(fill=tk.X, pady=10)
        
        ttk.Label(filter_frame, text="Status Filter:").pack(side=tk.LEFT, padx=5)
        self.filter_var = tk.StringVar()
        status_cb = ttk.Combobox(filter_frame, textvariable=self.filter_var, width=15)
        status_cb['values'] = ("All", "Confirmed", "Checked In", "Checked Out", "Cancelled")
        status_cb.current(0)
        status_cb.pack(side=tk.LEFT, padx=5)
        status_cb.bind("<<ComboboxSelected>>", self._apply_filter)
        
        refresh_btn = ttk.Button(filter_frame, text="Refresh", command=self._populate_booking_list)
        refresh_btn.pack(side=tk.RIGHT, padx=5)
        
        # === Right Frame: Booking Details & Actions ===
        # Right frame has two sections: Details and New Booking
        notebook = ttk.Notebook(right_frame)
        notebook.pack(fill=tk.BOTH, expand=True)
        
        # First tab: Booking Details
        details_frame = ttk.Frame(notebook)
        notebook.add(details_frame, text="Booking Details")
        
        # Second tab: New Booking
        new_booking_frame = ttk.Frame(notebook)
        notebook.add(new_booking_frame, text="New Booking")
        
        # === Details Frame ===
        # Selected booking details
        info_frame = ttk.LabelFrame(details_frame, text="Booking Information")
        info_frame.pack(fill=tk.X, padx=5, pady=10)
        
        # Guest info
        ttk.Label(info_frame, text="Guest:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=2)
        self.guest_label = ttk.Label(info_frame, text="-")
        self.guest_label.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Room info
        ttk.Label(info_frame, text="Room:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)
        self.room_label = ttk.Label(info_frame, text="-")
        self.room_label.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Check-in date
        ttk.Label(info_frame, text="Check-in:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=2)
        self.check_in_label = ttk.Label(info_frame, text="-")
        self.check_in_label.grid(row=2, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Check-out date
        ttk.Label(info_frame, text="Check-out:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=2)
        self.check_out_label = ttk.Label(info_frame, text="-")
        self.check_out_label.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Total amount
        ttk.Label(info_frame, text="Total Amount:").grid(row=4, column=0, sticky=tk.W, padx=5, pady=2)
        self.amount_label = ttk.Label(info_frame, text="-")
        self.amount_label.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Status
        ttk.Label(info_frame, text="Status:").grid(row=5, column=0, sticky=tk.W, padx=5, pady=2)
        self.status_label = ttk.Label(info_frame, text="-")
        self.status_label.grid(row=5, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Booking date
        ttk.Label(info_frame, text="Booking Date:").grid(row=6, column=0, sticky=tk.W, padx=5, pady=2)
        self.booking_date_label = ttk.Label(info_frame, text="-")
        self.booking_date_label.grid(row=6, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Configure columns
        info_frame.columnconfigure(1, weight=1)
        
        # Action buttons
        action_frame = ttk.Frame(details_frame)
        action_frame.pack(fill=tk.X, padx=5, pady=10)
        
        self.checkin_btn = ttk.Button(action_frame, text="Check In", command=self._check_in, state=tk.DISABLED)
        self.checkin_btn.pack(side=tk.LEFT, padx=5)
        
        self.checkout_btn = ttk.Button(action_frame, text="Check Out", command=self._check_out, state=tk.DISABLED)
        self.checkout_btn.pack(side=tk.LEFT, padx=5)
        
        self.cancel_btn = ttk.Button(action_frame, text="Cancel Booking", command=self._cancel_booking, state=tk.DISABLED)
        self.cancel_btn.pack(side=tk.LEFT, padx=5)
        
        # Payment entry
        payment_frame = ttk.LabelFrame(details_frame, text="Add Payment")
        payment_frame.pack(fill=tk.X, padx=5, pady=10)
        
        ttk.Label(payment_frame, text="Amount:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.payment_amount_var = tk.StringVar()
        self.payment_amount_entry = ttk.Entry(payment_frame, textvariable=self.payment_amount_var, width=15)
        self.payment_amount_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
        
        ttk.Label(payment_frame, text="Method:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=5)
        self.payment_method_var = tk.StringVar()
        method_cb = ttk.Combobox(payment_frame, textvariable=self.payment_method_var, width=15)
        method_cb['values'] = ("Cash", "Credit Card", "Debit Card", "Bank Transfer")
        method_cb.current(0)
        method_cb.grid(row=0, column=3, sticky=tk.W, padx=5, pady=5)
        
        self.add_payment_btn = ttk.Button(payment_frame, text="Add Payment", command=self._add_payment, state=tk.DISABLED)
        self.add_payment_btn.grid(row=0, column=4, sticky=tk.W, padx=5, pady=5)
        
        # === New Booking Frame ===
        form_frame = ttk.Frame(new_booking_frame)
        form_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=10)
        
        # Guest selection
        ttk.Label(form_frame, text="Select Guest:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.guest_id_var = tk.StringVar()
        self.guest_cb = ttk.Combobox(form_frame, textvariable=self.guest_id_var, width=30)
        self.guest_cb.grid(row=0, column=1, sticky=tk.EW, pady=5, padx=5)
        
        # Room selection
        ttk.Label(form_frame, text="Select Room:").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.room_id_var = tk.StringVar()
        self.room_cb = ttk.Combobox(form_frame, textvariable=self.room_id_var, width=30)
        self.room_cb.grid(row=1, column=1, sticky=tk.EW, pady=5, padx=5)
        
        # Check-in date
        ttk.Label(form_frame, text="Check-in Date:").grid(row=2, column=0, sticky=tk.W, pady=5)
        self.check_in_date = DateEntry(form_frame, width=12, background='darkblue',
                                    foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd')
        self.check_in_date.grid(row=2, column=1, sticky=tk.W, pady=5, padx=5)
        
        # Check-out date
        ttk.Label(form_frame, text="Check-out Date:").grid(row=3, column=0, sticky=tk.W, pady=5)
        self.check_out_date = DateEntry(form_frame, width=12, background='darkblue',
                                     foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd')
        # Set default to check-in + 1 day
        tomorrow = datetime.now() + timedelta(days=1)
        self.check_out_date.set_date(tomorrow)
        self.check_out_date.grid(row=3, column=1, sticky=tk.W, pady=5, padx=5)
        
        # Total amount
        ttk.Label(form_frame, text="Total Amount:").grid(row=4, column=0, sticky=tk.W, pady=5)
        self.total_amount_var = tk.StringVar()
        self.total_amount_entry = ttk.Entry(form_frame, textvariable=self.total_amount_var)
        self.total_amount_entry.grid(row=4, column=1, sticky=tk.EW, pady=5, padx=5)
        
        # Calculate button
        calculate_btn = ttk.Button(form_frame, text="Calculate Total", command=self._calculate_total)
        calculate_btn.grid(row=4, column=2, sticky=tk.W, pady=5)
        
        # Create booking button
        create_btn = ttk.Button(form_frame, text="Create Booking", command=self._create_booking)
        create_btn.grid(row=5, column=1, sticky=tk.E, pady=10, padx=5)
        
        # Configure grid
        form_frame.columnconfigure(1, weight=1)
        
        # Populate dropdowns
        self._populate_dropdowns()
        
        # Selected booking data (hidden)
        self.selected_booking_id = None
    
    def _populate_booking_list(self):
        """Fetch bookings from database and populate the treeview"""
        # Clear the treeview
        for item in self.booking_tree.get_children():
            self.booking_tree.delete(item)
        
        # Get filtered bookings
        status_filter = self.filter_var.get()
        if status_filter and status_filter != "All":
            bookings = self.db.get_bookings(status=status_filter)
        else:
            bookings = self.db.get_bookings()
        
        # Insert bookings into treeview
        for booking in bookings:
            booking_id, first_name, last_name, room_number, check_in, check_out, amount, status = booking
            guest_name = f"{first_name} {last_name}"
            self.booking_tree.insert("", tk.END, values=(guest_name, room_number, check_in, check_out, status), tags=(booking_id,))
        
        # Clear selection and details
        self._clear_selection()
    
    def _apply_filter(self, event=None):
        """Apply filter to booking list"""
        self._populate_booking_list()
    
    def _on_booking_select(self, event=None):
        """Handle booking selection from treeview"""
        selection = self.booking_tree.selection()
        if selection:
            item = selection[0]
            
            # Get booking ID from tags
            booking_id = self.booking_tree.item(item, "tags")[0]
            self.selected_booking_id = booking_id
            
            # Get full booking details from database
            booking = self.db.get_booking(booking_id)
            if booking:
                _, guest_id, first_name, last_name, room_id, room_number, check_in, check_out, booking_date, amount, status = booking
                
                # Update detail labels
                self.guest_label.config(text=f"{first_name} {last_name}")
                self.room_label.config(text=room_number)
                self.check_in_label.config(text=check_in)
                self.check_out_label.config(text=check_out)
                self.amount_label.config(text=f"₹{amount:.2f}")
                self.status_label.config(text=status)
                self.booking_date_label.config(text=booking_date)
                
                # Enable/disable action buttons based on status
                if status == "Confirmed":
                    self.checkin_btn.config(state=tk.NORMAL)
                    self.checkout_btn.config(state=tk.DISABLED)
                    self.cancel_btn.config(state=tk.NORMAL)
                elif status == "Checked In":
                    self.checkin_btn.config(state=tk.DISABLED)
                    self.checkout_btn.config(state=tk.NORMAL)
                    self.cancel_btn.config(state=tk.NORMAL)
                else:
                    self.checkin_btn.config(state=tk.DISABLED)
                    self.checkout_btn.config(state=tk.DISABLED)
                    self.cancel_btn.config(state=tk.DISABLED)
                
                # Enable payment button
                self.add_payment_btn.config(state=tk.NORMAL)
    
    def _clear_selection(self):
        """Clear booking selection and details"""
        # Clear tree selection
        if self.booking_tree.selection():
            self.booking_tree.selection_remove(self.booking_tree.selection()[0])
        
        # Clear detail labels
        self.guest_label.config(text="-")
        self.room_label.config(text="-")
        self.check_in_label.config(text="-")
        self.check_out_label.config(text="-")
        self.amount_label.config(text="-")
        self.status_label.config(text="-")
        self.booking_date_label.config(text="-")
        
        # Disable action buttons
        self.checkin_btn.config(state=tk.DISABLED)
        self.checkout_btn.config(state=tk.DISABLED)
        self.cancel_btn.config(state=tk.DISABLED)
        self.add_payment_btn.config(state=tk.DISABLED)
        
        # Clear payment fields
        self.payment_amount_var.set("")
        self.payment_method_var.set("Cash")
        
        self.selected_booking_id = None
    
    def _populate_dropdowns(self):
        """Populate guest and room dropdown lists"""
        # Populate guest dropdown
        guests = self.db.get_guests()
        guest_list = []
        self.guest_map = {}  # Map display names to IDs
        
        for guest in guests:
            guest_id, first_name, last_name = guest[0:3]
            display_name = f"{first_name} {last_name}"
            guest_list.append(display_name)
            self.guest_map[display_name] = guest_id
        
        self.guest_cb['values'] = guest_list
        
        # Auto-select first guest if available
        if guest_list:
            self.guest_cb.current(0)
        
        # Populate room dropdown (only available rooms)
        rooms = self.db.get_rooms(status="Available")
        room_list = []
        self.room_map = {}  # Map display names to IDs
        
        for room in rooms:
            room_id, room_number, room_type, rate = room[0:4]
            display_name = f"{room_number} - {room_type} (₹{rate:.2f}/night)"
            room_list.append(display_name)
            self.room_map[display_name] = (room_id, rate)
        
        self.room_cb['values'] = room_list
        
        # Auto-select first room if available
        if room_list:
            self.room_cb.current(0)
            
        # If we have both a room and a guest selected, calculate the total automatically
        if guest_list and room_list:
            self._calculate_total()
    
    def _calculate_total(self):
        """Calculate total amount based on selected room and dates"""
        try:
            # Get selected room
            room_display = self.room_id_var.get()
            if not room_display or room_display not in self.room_map:
                messagebox.showerror("Error", "Please select a room")
                return
            
            room_id, rate = self.room_map[room_display]
            
            # Get dates
            check_in = self.check_in_date.get_date()
            check_out = self.check_out_date.get_date()
            
            # Calculate number of nights
            nights = (check_out - check_in).days
            if nights <= 0:
                messagebox.showerror("Error", "Check-out date must be after check-in date")
                return
            
            # Calculate total
            total = nights * rate
            
            # Update total field
            self.total_amount_var.set(f"{total:.2f}")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to calculate total: {str(e)}")
    
    def _create_booking(self):
        """Create a new booking"""
        try:
            # Get selected guest
            guest_display = self.guest_id_var.get()
            if not guest_display:
                messagebox.showerror("Error", "Please select a guest")
                return
                
            # Check if guest is in map (only if we have a selection)
            if guest_display and guest_display not in self.guest_map:
                # If no guests exist yet, show a helpful message
                if not self.guest_map:
                    messagebox.showerror("Error", "No guests available. Please add a guest first in Guest Management.")
                    return
                messagebox.showerror("Error", "Please select a valid guest from the dropdown")
                return
            
            guest_id = self.guest_map[guest_display]
            
            # Get selected room
            room_display = self.room_id_var.get()
            if not room_display:
                messagebox.showerror("Error", "Please select a room")
                return
                
            # Check if room is in map (only if we have a selection)
            if room_display and room_display not in self.room_map:
                # If no rooms are available
                if not self.room_map:
                    messagebox.showerror("Error", "No rooms available for booking.")
                    return
                messagebox.showerror("Error", "Please select a valid room from the dropdown")
                return
            
            room_id, _ = self.room_map[room_display]
            
            # Get dates
            check_in = self.check_in_date.get_date().strftime("%Y-%m-%d")
            check_out = self.check_out_date.get_date().strftime("%Y-%m-%d")
            
            # Get total amount
            total_str = self.total_amount_var.get().strip()
            if not total_str:
                messagebox.showerror("Error", "Please calculate the total amount")
                return
            
            try:
                total = float(total_str)
            except ValueError:
                messagebox.showerror("Error", "Invalid total amount")
                return
            
            # Create booking
            booking_id = self.db.add_booking(guest_id, room_id, check_in, check_out, total)
            
            if booking_id:
                messagebox.showinfo("Success", "Booking created successfully")
                self._populate_booking_list()
                self._populate_dropdowns()  # Refresh room list
                
                # Clear form
                self.guest_id_var.set("")
                self.room_id_var.set("")
                self.total_amount_var.set("")
            else:
                messagebox.showerror("Error", "Failed to create booking")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to create booking: {str(e)}")
    
    def _check_in(self):
        """Check in a guest for selected booking"""
        if not self.selected_booking_id:
            return
        
        result = self.db.update_booking_status(self.selected_booking_id, "Checked In")
        
        if result:
            messagebox.showinfo("Success", "Guest checked in successfully")
            self._populate_booking_list()
        else:
            messagebox.showerror("Error", "Failed to check in guest")
    
    def _check_out(self):
        """Check out a guest for selected booking"""
        if not self.selected_booking_id:
            return
        
        result = self.db.update_booking_status(self.selected_booking_id, "Checked Out")
        
        if result:
            messagebox.showinfo("Success", "Guest checked out successfully")
            self._populate_booking_list()
            self._populate_dropdowns()  # Refresh room list
        else:
            messagebox.showerror("Error", "Failed to check out guest")
    
    def _cancel_booking(self):
        """Cancel selected booking"""
        if not self.selected_booking_id:
            return
        
        # Confirm cancellation
        if not messagebox.askyesno("Confirm", "Are you sure you want to cancel this booking?"):
            return
        
        result = self.db.update_booking_status(self.selected_booking_id, "Cancelled")
        
        if result:
            messagebox.showinfo("Success", "Booking cancelled successfully")
            self._populate_booking_list()
            self._populate_dropdowns()  # Refresh room list
        else:
            messagebox.showerror("Error", "Failed to cancel booking")
    
    def _add_payment(self):
        """Add payment for selected booking"""
        if not self.selected_booking_id:
            return
        
        # Validate payment amount
        amount_str = self.payment_amount_var.get().strip()
        method = self.payment_method_var.get()
        
        if not amount_str:
            messagebox.showerror("Error", "Please enter payment amount")
            return
        
        try:
            amount = float(amount_str)
            if amount <= 0:
                raise ValueError("Amount must be positive")
        except ValueError:
            messagebox.showerror("Error", "Invalid payment amount")
            return
        
        # Add payment
        result = self.db.add_payment(self.selected_booking_id, amount, method)
        
        if result:
            messagebox.showinfo("Success", f"Payment of ₹{amount:.2f} added successfully")
            self.payment_amount_var.set("")  # Clear payment field
        else:
            messagebox.showerror("Error", "Failed to add payment") 
content_copyCOPY