| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- import tkinter as tk
- from tkinter import ttk, messagebox, filedialog
- from datetime import datetime
- import csv
- from db import Database
- from tkcalendar import DateEntry
- class TheatreApp:
- def __init__(self, db):
- self.db = db
- self.root = tk.Tk()
- self.root.title("Театральная система")
- self.root.geometry("1100x650")
- self.create_widgets()
- self.refresh_all()
- def create_widgets(self):
- notebook = ttk.Notebook(self.root)
- notebook.pack(fill=tk.BOTH, expand=True)
- # Отделы
- self.depts_frame = ttk.Frame(notebook)
- notebook.add(self.depts_frame, text="Отделы")
- self.depts_tree = self._make_table(self.depts_frame,
- ["ID","Фамилия","Имя","Отчество","Email","Реквизиты"])
- tk.Button(self.depts_frame, text="Обновить", command=self.refresh_departments).pack(pady=5)
- # Пользователи
- self.users_frame = ttk.Frame(notebook)
- notebook.add(self.users_frame, text="Пользователи")
- self.users_tree = self._make_table(self.users_frame,
- ["ID","Фамилия","Имя","Отчество","Дата рождения","Телефон"])
- btn_frame = tk.Frame(self.users_frame)
- btn_frame.pack(pady=5)
- tk.Button(btn_frame, text="Добавить", command=self.add_user).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame, text="Редактировать", command=self.edit_user).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame, text="Удалить", command=self.delete_user).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame, text="Обновить", command=self.refresh_users).pack(side=tk.LEFT, padx=5)
- # Билеты
- self.tickets_frame = ttk.Frame(notebook)
- notebook.add(self.tickets_frame, text="Билеты")
- self.tickets_tree = self._make_table(self.tickets_frame,
- ["ID","Отдел","Пользователь","Спектакль","Цена","Место","Дата/время","Полная стоимость"])
- btn_frame2 = tk.Frame(self.tickets_frame)
- btn_frame2.pack(pady=5)
- tk.Button(btn_frame2, text="Добавить", command=self.add_ticket).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame2, text="Редактировать", command=self.edit_ticket).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame2, text="Удалить", command=self.delete_ticket).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame2, text="Поиск", command=self.search_tickets).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame2, text="Отчёт (выручка)", command=self.revenue_report).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame2, text="Обновить", command=self.refresh_tickets).pack(side=tk.LEFT, padx=5)
- def _make_table(self, parent, columns):
- tree = ttk.Treeview(parent, columns=columns, show="headings", height=20)
- for col in columns:
- tree.heading(col, text=col)
- tree.column(col, width=120, anchor="center")
- tree.pack(fill=tk.BOTH, expand=True)
- return tree
- def _fill_tree(self, tree, rows):
- for item in tree.get_children():
- tree.delete(item)
- for row in rows:
- tree.insert("", "end", values=row)
- def refresh_departments(self):
- rows = self.db.get_departments()
- self._fill_tree(self.depts_tree, rows)
- def refresh_users(self):
- rows = self.db.get_users()
- self._fill_tree(self.users_tree, rows)
- def refresh_tickets(self):
- rows = self.db.get_tickets()
- self._fill_tree(self.tickets_tree, rows)
- def add_user(self):
- dialog = UserDialog(self.root, self.db, title="Добавить пользователя")
- self.wait_for_dialog(dialog, self.refresh_users)
- def edit_user(self):
- selected = self.users_tree.selection()
- if not selected:
- messagebox.showerror("Ошибка", "Выберите пользователя")
- return
- values = self.users_tree.item(selected[0])["values"]
- user_id = values[0]
- defaults = {
- "last_name": values[1],
- "first_name": values[2],
- "middle_name": values[3] if values[3] else "",
- "birth_date": values[4],
- "phone": values[5]
- }
- dialog = UserDialog(self.root, self.db, user_id=user_id, defaults=defaults, title="Редактировать пользователя")
- self.wait_for_dialog(dialog, self.refresh_users)
- def delete_user(self):
- selected = self.users_tree.selection()
- if not selected:
- messagebox.showerror("Ошибка", "Выберите пользователя")
- return
- user_id = self.users_tree.item(selected[0])["values"][0]
- if messagebox.askyesno("Удаление", f"Удалить пользователя ID={user_id}?"):
- self.db.delete_user(user_id)
- self.refresh_users()
- def add_ticket(self):
- dialog = TicketDialog(self.root, self.db, title="Добавить билет")
- self.wait_for_dialog(dialog, self.refresh_tickets)
- def edit_ticket(self):
- selected = self.tickets_tree.selection()
- if not selected:
- messagebox.showerror("Ошибка", "Выберите билет")
- return
- values = self.tickets_tree.item(selected[0])["values"]
- ticket_id = values[0]
- defaults = {
- "department_id": values[1],
- "user_id": values[2],
- "performance_name": values[3],
- "ticket_price": values[4],
- "hall_seat": values[5],
- "event_datetime": values[6]
- }
- dialog = TicketDialog(self.root, self.db, ticket_id=ticket_id, defaults=defaults, title="Редактировать билет")
- self.wait_for_dialog(dialog, self.refresh_tickets)
- def delete_ticket(self):
- selected = self.tickets_tree.selection()
- if not selected:
- messagebox.showerror("Ошибка", "Выберите билет")
- return
- ticket_id = self.tickets_tree.item(selected[0])["values"][0]
- if messagebox.askyesno("Удаление", f"Удалить билет ID={ticket_id}?"):
- self.db.delete_ticket(ticket_id)
- self.refresh_tickets()
- def search_tickets(self):
- SearchDialog(self.root, self.db)
- def revenue_report(self):
- perf = tk.simpledialog.askstring("Отчёт", "Введите название спектакля:")
- if perf:
- total = self.db.revenue_by_performance(perf)
- messagebox.showinfo("Выручка", f"Выручка по спектаклю '{perf}': {total} руб.")
- def refresh_all(self):
- self.refresh_departments()
- self.refresh_users()
- self.refresh_tickets()
- def wait_for_dialog(self, dialog, refresh_callback):
- self.root.wait_window(dialog.top)
- refresh_callback()
- def run(self):
- self.root.mainloop()
- # ДИАЛОГ ПОЛЬЗОВАТЕЛЯ (календарь для даты рождения)
- class UserDialog:
- def __init__(self, parent, db, user_id=None, defaults=None, title="Пользователь"):
- self.db = db
- self.user_id = user_id
- self.top = tk.Toplevel(parent)
- self.top.title(title)
- self.top.grab_set()
- self.top.resizable(False, False)
- row = 0
- tk.Label(self.top, text="Фамилия:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- self.last_name_entry = tk.Entry(self.top, width=30)
- self.last_name_entry.grid(row=row, column=1, padx=5, pady=5)
- row += 1
- tk.Label(self.top, text="Имя:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- self.first_name_entry = tk.Entry(self.top, width=30)
- self.first_name_entry.grid(row=row, column=1, padx=5, pady=5)
- row += 1
- tk.Label(self.top, text="Отчество:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- self.middle_name_entry = tk.Entry(self.top, width=30)
- self.middle_name_entry.grid(row=row, column=1, padx=5, pady=5)
- row += 1
- tk.Label(self.top, text="Дата рождения:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- frame_birth = tk.Frame(self.top)
- frame_birth.grid(row=row, column=1, padx=5, pady=5, sticky="w")
- self.birth_date = DateEntry(frame_birth, width=12, background='darkblue',
- foreground='white', borderwidth=2,
- date_pattern='yyyy-mm-dd')
- self.birth_date.pack(side=tk.LEFT)
- tk.Button(frame_birth, text="Сегодня", command=self.set_today_birth).pack(side=tk.LEFT, padx=5)
- row += 1
- tk.Label(self.top, text="Телефон:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- self.phone_entry = tk.Entry(self.top, width=30)
- self.phone_entry.grid(row=row, column=1, padx=5, pady=5)
- row += 1
- btn_frame = tk.Frame(self.top)
- btn_frame.grid(row=row, column=0, columnspan=2, pady=10)
- tk.Button(btn_frame, text="Сохранить", command=self.save).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame, text="Отмена", command=self.top.destroy).pack(side=tk.LEFT, padx=5)
- if defaults:
- self.last_name_entry.insert(0, defaults.get("last_name", ""))
- self.first_name_entry.insert(0, defaults.get("first_name", ""))
- self.middle_name_entry.insert(0, defaults.get("middle_name", ""))
- self.phone_entry.insert(0, defaults.get("phone", ""))
- birth = defaults.get("birth_date")
- if birth:
- try:
- if isinstance(birth, datetime):
- self.birth_date.set_date(birth)
- else:
- self.birth_date.set_date(datetime.strptime(str(birth), "%Y-%m-%d"))
- except:
- pass
- else:
- self.birth_date.set_date(datetime.now())
- def set_today_birth(self):
- self.birth_date.set_date(datetime.now())
- def save(self):
- last = self.last_name_entry.get().strip()
- first = self.first_name_entry.get().strip()
- middle = self.middle_name_entry.get().strip() or None
- phone = self.phone_entry.get().strip()
- birth_date = self.birth_date.get_date().strftime("%Y-%m-%d")
- if not last or not first or not phone:
- messagebox.showerror("Ошибка", "Заполните фамилию, имя и телефон")
- return
- if self.user_id:
- self.db.update_user(self.user_id, last, first, middle, birth_date, phone)
- else:
- self.db.add_user(last, first, middle, birth_date, phone)
- self.top.destroy()
- # ДИАЛОГ БИЛЕТА
- class TicketDialog:
- def __init__(self, parent, db, ticket_id=None, defaults=None, title="Билет"):
- self.db = db
- self.ticket_id = ticket_id
- self.top = tk.Toplevel(parent)
- self.top.title(title)
- self.top.grab_set()
- self.top.resizable(False, False)
- self.dept_list = db.get_departments_list()
- self.user_list = db.get_users_list()
- self.dept_var = tk.StringVar()
- self.user_var = tk.StringVar()
- self.perf_var = tk.StringVar()
- self.price_var = tk.StringVar()
- self.seat_var = tk.StringVar()
- self.date_var = tk.StringVar()
- self.hour_var = tk.StringVar()
- self.minute_var = tk.StringVar()
- row = 0
- tk.Label(self.top, text="Отдел:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- dept_combo = ttk.Combobox(self.top, textvariable=self.dept_var,
- values=[t for _, t in self.dept_list], width=40)
- dept_combo.grid(row=row, column=1, padx=5, pady=5)
- row += 1
- tk.Label(self.top, text="Пользователь:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- user_combo = ttk.Combobox(self.top, textvariable=self.user_var,
- values=[t for _, t in self.user_list], width=40)
- user_combo.grid(row=row, column=1, padx=5, pady=5)
- row += 1
- tk.Label(self.top, text="Спектакль:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- tk.Entry(self.top, textvariable=self.perf_var, width=42).grid(row=row, column=1, padx=5, pady=5)
- row += 1
- tk.Label(self.top, text="Цена:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- tk.Entry(self.top, textvariable=self.price_var, width=42).grid(row=row, column=1, padx=5, pady=5)
- row += 1
- tk.Label(self.top, text="Место:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- tk.Entry(self.top, textvariable=self.seat_var, width=42).grid(row=row, column=1, padx=5, pady=5)
- row += 1
- # Дата и время
- tk.Label(self.top, text="Дата:").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- self.date_picker = DateEntry(self.top, width=12, background='darkblue',
- foreground='white', borderwidth=2,
- date_pattern='yyyy-mm-dd', textvariable=self.date_var)
- self.date_picker.grid(row=row, column=1, padx=5, pady=5, sticky="w")
- row += 1
- tk.Label(self.top, text="Час (0-23):").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- self.hour_spin = tk.Spinbox(self.top, from_=0, to=23, width=5, format="%02.0f", textvariable=self.hour_var)
- self.hour_spin.grid(row=row, column=1, padx=5, pady=5, sticky="w")
- row += 1
- tk.Label(self.top, text="Минуты (0-59):").grid(row=row, column=0, padx=5, pady=5, sticky="e")
- self.minute_spin = tk.Spinbox(self.top, from_=0, to=59, width=5, format="%02.0f", textvariable=self.minute_var)
- self.minute_spin.grid(row=row, column=1, padx=5, pady=5, sticky="w")
- row += 1
- def set_now():
- now = datetime.now()
- self.date_picker.set_date(now)
- self.hour_var.set(f"{now.hour:02d}")
- self.minute_var.set(f"{now.minute:02d}")
- tk.Button(self.top, text="Сейчас", command=set_now).grid(row=row, column=1, padx=5, pady=5, sticky="w")
- row += 1
- if defaults:
- self.set_defaults(defaults)
- else:
- self.date_picker.set_date(datetime.now())
- self.hour_var.set("00")
- self.minute_var.set("00")
- btn_frame = tk.Frame(self.top)
- btn_frame.grid(row=row, column=0, columnspan=2, pady=10)
- tk.Button(btn_frame, text="Сохранить", command=self.save).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame, text="Отмена", command=self.top.destroy).pack(side=tk.LEFT, padx=5)
- def set_defaults(self, defaults):
- dept_id = defaults.get("department_id")
- if dept_id:
- for id_, text in self.dept_list:
- if id_ == dept_id:
- self.dept_var.set(text)
- break
- user_id = defaults.get("user_id")
- if user_id:
- for id_, text in self.user_list:
- if id_ == user_id:
- self.user_var.set(text)
- break
- self.perf_var.set(defaults.get("performance_name", ""))
- self.price_var.set(str(defaults.get("ticket_price", "")))
- self.seat_var.set(defaults.get("hall_seat", ""))
- dt = defaults.get("event_datetime")
- if dt:
- if isinstance(dt, datetime):
- self.date_picker.set_date(dt)
- self.hour_var.set(f"{dt.hour:02d}")
- self.minute_var.set(f"{dt.minute:02d}")
- else:
- try:
- dt_obj = datetime.strptime(str(dt), "%Y-%m-%d %H:%M:%S")
- self.date_picker.set_date(dt_obj)
- self.hour_var.set(f"{dt_obj.hour:02d}")
- self.minute_var.set(f"{dt_obj.minute:02d}")
- except:
- pass
- def save(self):
- dept_text = self.dept_var.get()
- dept_id = None
- for id_, text in self.dept_list:
- if text == dept_text:
- dept_id = id_
- break
- if dept_id is None:
- messagebox.showerror("Ошибка", "Выберите отдел из списка")
- return
- user_text = self.user_var.get()
- user_id = None
- for id_, text in self.user_list:
- if text == user_text:
- user_id = id_
- break
- if user_id is None:
- messagebox.showerror("Ошибка", "Выберите пользователя из списка")
- return
- perf = self.perf_var.get().strip()
- if not perf:
- messagebox.showerror("Ошибка", "Введите название спектакля")
- return
- try:
- price = float(self.price_var.get().strip())
- except ValueError:
- messagebox.showerror("Ошибка", "Цена должна быть числом")
- return
- seat = self.seat_var.get().strip()
- if not seat:
- messagebox.showerror("Ошибка", "Введите место")
- return
- date_obj = self.date_picker.get_date()
- try:
- hour = int(self.hour_var.get().strip())
- minute = int(self.minute_var.get().strip())
- except ValueError:
- messagebox.showerror("Ошибка", "Время должно быть числом")
- return
- if hour < 0 or hour > 23 or minute < 0 or minute > 59:
- messagebox.showerror("Ошибка", "Некорректное время")
- return
- event_dt = datetime(date_obj.year, date_obj.month, date_obj.day, hour, minute)
- if self.ticket_id:
- self.db.update_ticket(self.ticket_id, dept_id, user_id, perf, price, seat, event_dt)
- else:
- self.db.add_ticket(dept_id, user_id, perf, price, seat, event_dt)
- self.top.destroy()
- # ДИАЛОГ ПОИСКА
- class SearchDialog:
- def __init__(self, parent, db):
- self.db = db
- self.top = tk.Toplevel(parent)
- self.top.title("Поиск билетов")
- self.top.grab_set()
- self.top.geometry("900x500")
- frame = tk.Frame(self.top)
- frame.pack(pady=10)
- tk.Label(frame, text="Фамилия (часть):").grid(row=0, column=0, padx=5, pady=5)
- self.last_entry = tk.Entry(frame, width=20)
- self.last_entry.grid(row=0, column=1, padx=5, pady=5)
- tk.Label(frame, text="Спектакль (часть):").grid(row=0, column=2, padx=5, pady=5)
- self.perf_entry = tk.Entry(frame, width=20)
- self.perf_entry.grid(row=0, column=3, padx=5, pady=5)
- tk.Button(frame, text="Искать", command=self.search).grid(row=0, column=4, padx=10)
- self.tree = ttk.Treeview(self.top, columns=("ID","Фамилия","Имя","Спектакль","Цена","Место","Дата","Полная"), show="headings")
- for col in self.tree["columns"]:
- self.tree.heading(col, text=col)
- self.tree.column(col, width=100, anchor="center")
- self.tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
- tk.Button(self.top, text="Экспорт в CSV", command=self.export_csv).pack(pady=5)
- def search(self):
- last = self.last_entry.get().strip()
- perf = self.perf_entry.get().strip()
- rows = self.db.search_tickets(last, perf)
- for item in self.tree.get_children():
- self.tree.delete(item)
- if rows:
- for row in rows:
- self.tree.insert("", "end", values=row)
- else:
- messagebox.showinfo("Результат", "Ничего не найдено")
- def export_csv(self):
- if not self.tree.get_children():
- messagebox.showwarning("Нет данных", "Сначала выполните поиск")
- return
- filename = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV", "*.csv")])
- if filename:
- data = []
- for child in self.tree.get_children():
- data.append(self.tree.item(child)["values"])
- with open(filename, 'w', encoding='utf-8', newline='') as f:
- writer = csv.writer(f)
- writer.writerow(["ID","Фамилия","Имя","Спектакль","Цена","Место","Дата","Полная"])
- writer.writerows(data)
- messagebox.showinfo("Успех", f"Экспортировано в {filename}")
|