Загрузка данных
import tkinter as tk
from tkinter import ttk, messagebox
import mysql.connector
from mysql.connector import Error
# Конфигурация подключения к MySQL (измени пароль и пользователя, если отличаются)
DB_CONFIG = {
'host': 'localhost',
'user': 'root',
'password': 'root', -- укажи свой пароль от MySQL Workbench
'database': 'health'
}
class MedicalCenterApp:
def __init__(self, root):
self.root = root
self.root.title("Информационная система: Медицинский центр Здоровье")
self.root.geometry("1000x650")
# Настройка стиля интерфейса
style = ttk.Style()
style.theme_use('clam')
# Создание главного контейнера вкладок
self.notebook = ttk.Notebook(root)
self.notebook.pack(fill='both', expand=True, padx=10, pady=10)
# Инициализация вкладок
self.init_reports_tab()
self.init_view_tables_tab()
self.init_add_data_tab()
def execute_query(self, query, params=None):
"""Универсальный метод для безопасного выполнения запросов чтения из БД"""
connection = None
try:
connection = mysql.connector.connect(**DB_CONFIG)
cursor = connection.cursor()
if params:
cursor.execute(query, params)
else:
cursor.execute(query)
headers = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
return headers, rows
except Error as e:
messagebox.showerror("Ошибка базы данных", f"Не удалось выполнить запрос:\n{e}")
return None, None
finally:
if connection and connection.is_connected():
cursor.close()
connection.close()
def execute_insert(self, query, params):
"""Метод для безопасного добавления данных"""
connection = None
try:
connection = mysql.connector.connect(**DB_CONFIG)
cursor = connection.cursor()
cursor.execute(query, params)
connection.commit()
return True
except Error as e:
messagebox.showerror("Ошибка добавления данных", f"Ошибка операции:\n{e}")
return False
finally:
if connection and connection.is_connected():
cursor.close()
connection.close()
def display_in_treeview(self, tree, headers, rows):
"""Очищает и наполняет переданную таблицу Treeview новыми структурами данных"""
tree.delete(*tree.get_children())
if headers is None or rows is None:
return
tree["columns"] = headers
tree["show"] = "headings"
for h in headers:
tree.heading(h, text=h)
tree.column(h, width=130, anchor="center")
for row in rows:
# Преобразование значений в читаемый строковый вид (для дат и логических полей)
formatted_row = [str(item) if item is not None else "" for item in row]
tree.insert("", "end", values=formatted_row)
# --- ВКЛАДКА 1: АНАЛИТИЧЕСКИЕ ОТЧЕТЫ ЛАБОРАТОРНОЙ РАБОТЫ ---
def init_reports_tab(self):
tab = ttk.Frame(self.notebook)
self.notebook.add(tab, text="Аналитические отчеты")
# Левая панель управления кнопками отчетов
btn_panel = ttk.LabelFrame(tab, text="Выберите отчет", padding=10)
btn_panel.pack(side='left', fill='y', padx=5, pady=5)
ttk.Button(btn_panel, text="1. Действующие договоры", command=self.report_active_contracts).pack(fill='x', pady=5)
ttk.Button(btn_panel, text="2. Начисления врачам", command=self.report_doctor_salaries).pack(fill='x', pady=5)
# Компонент для ввода параметров 3-го отчета
policy_frame = ttk.Frame(btn_panel)
policy_frame.pack(fill='x', pady=5)
ttk.Label(policy_frame, text="Полис для отчета №3:").pack(anchor='w')
self.policy_entry = ttk.Entry(policy_frame)
self.policy_entry.insert(0, "ОМС-1234567890")
self.policy_entry.pack(fill='x', pady=2)
ttk.Button(btn_panel, text="3. Поиск записей по полису", command=self.report_patient_appointments).pack(fill='x', pady=2)
ttk.Button(btn_panel, text="4. Контроль сроков договоров", command=self.report_contract_control).pack(fill='x', pady=5)
ttk.Button(btn_panel, text="5. Анализ прибыльности услуг", command=self.report_profitability).pack(fill='x', pady=5)
# Правая панель вывода результатов в таблицу
table_panel = ttk.LabelFrame(tab, text="Результаты выполнения", padding=5)
table_panel.pack(side='right', fill='both', expand=True, padx=5, pady=5)
# Скроллбары для таблицы отчетов
scroll_y = ttk.Scrollbar(table_panel, orient="vertical")
scroll_x = ttk.Scrollbar(table_panel, orient="horizontal")
self.report_tree = ttk.Treeview(table_panel, yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set)
scroll_y.config(command=self.report_tree.yview)
scroll_x.config(command=self.report_tree.xview)
scroll_y.pack(side='right', fill='y')
scroll_x.pack(side='bottom', fill='x')
self.report_tree.pack(fill='both', expand=True)
# Реализация SQL логики для каждого отчета лабораторной работы
def report_active_contracts(self):
query = """
SELECT в.фамилия, в.имя, в.отчество, в.телефон, тд.номер_договора, тд.дата_заключения, тд.срок_договора_лет
FROM врач в
JOIN трудовой_договор тд ON в.id_врач = тд.id_врач
WHERE тд.договор_расторгнут = FALSE;
"""
headers, rows = self.execute_query(query)
self.display_in_treeview(self.report_tree, headers, rows)
def report_doctor_salaries(self):
query = """
SELECT в.фамилия, в.имя, му.название AS услуга,
ROUND(му.зарплатный_фонд_руб / бригада_стат.кол_врачей, 2) AS начисление_врачу,
оу.фактическая_дата,
CASE WHEN оу.зарплата_начислена = FALSE THEN 'Ожидает' ELSE 'Начислено' END AS статус
FROM оказанная_услуга оу
JOIN медицинская_услуга му ON оу.id_услуги = му.id_услуги
JOIN запись_на_прием знп ON оу.id_записи = знп.id_записи
JOIN бригада ON бригада.id_услуги = му.id_услуги
JOIN врач в ON бригада.id_врач = в.id_врач
JOIN (
SELECT id_услуги, COUNT(*) AS кол_врачей
FROM бригада
GROUP BY id_услуги
) AS бригада_стат ON му.id_услуги = бригада_стат.id_услуги
ORDER BY в.фамилия, оу.фактическая_дата;
"""
headers, rows = self.execute_query(query)
self.display_in_treeview(self.report_tree, headers, rows)
def report_patient_appointments(self):
policy = self.policy_entry.get().strip()
query = """
SELECT знп.номер_записи, му.название AS услуга, знп.дата_и_время_приема, знп.дата_создания_записи,
CASE WHEN знп.статус_выполнения = TRUE THEN 'Выполнен' ELSE 'Ожидает' END AS статус
FROM запись_на_прием знп
JOIN пациент п ON знп.id_пациента = п.id_пациента
JOIN медицинская_услуга му ON знп.id_услуги = му.id_услуги
WHERE п.лис_омс_дмс = %s;
"""
headers, rows = self.execute_query(query, (policy,))
self.display_in_treeview(self.report_tree, headers, rows)
def report_contract_control(self):
query = """
SELECT в.фамилия, в.имя, тд.номер_договора, тд.дата_заключения, тд.срок_договора_лет,
DATE_ADD(тд.дата_заключения, INTERVAL тд.срок_договора_лет YEAR) AS дата_окончания,
CASE
WHEN CURDATE() > DATE_ADD(тд.дата_заключения, INTERVAL тд.срок_договора_лет YEAR) THEN 'ПРОСРОЧЕН'
WHEN CURDATE() + INTERVAL 30 DAY > DATE_ADD(тд.дата_заключения, INTERVAL тд.срок_договора_лет YEAR) THEN 'Истекает скоро'
ELSE 'Действует'
END AS контроль
FROM трудовой_договор тд
JOIN врач в ON тд.id_врач = в.id_врач
WHERE тд.договор_расторгнут = FALSE;
"""
headers, rows = self.execute_query(query)
self.display_in_treeview(self.report_tree, headers, rows)
def report_profitability(self):
query = """
SELECT му.название, COUNT(оу.id_оказания) AS количество_оказаний,
му.цена_для_пациента_руб AS выручка_за_ед, му.себестоимость_руб AS расходники_за_ед,
му.зарплатный_фонд_руб AS фот_за_ед,
(му.цена_для_пациента_руб - му.себестоимость_руб - му.зарплатный_фонд_руб) AS прибыль_за_ед,
SUM(му.цена_для_пациента_руб - му.себестоимость_руб - му.зарплатный_фонд_руб) AS общая_прибыль
FROM оказанная_услуга оу
JOIN медицинская_услуга му ON оу.id_услуги = му.id_услуги
GROUP BY му.id_услуги
ORDER BY общая_прибыль DESC;
"""
headers, rows = self.execute_query(query)
self.display_in_treeview(self.report_tree, headers, rows)
-- --- ВКЛАДКА 2: ИНТЕРАКТИВНЫЙ ПРОСМОТР ТАБЛИЦ ---
def init_view_tables_tab(self):
tab = ttk.Frame(self.notebook)
self.notebook.add(tab, text="Просмотр таблиц")
top_panel = ttk.Frame(tab, padding=5)
top_panel.pack(fill='x')
ttk.Label(top_panel, text="Выберите таблицу для просмотра: ").pack(side='left', padx=5)
self.table_selector = ttk.Combobox(top_panel, values=["врач", "трудовой_договор", "медицинская_услуга", "пациент", "запись_на_прием", "оказанная_услуга", "бригада"], state="readonly")
self.table_selector.pack(side='left', padx=5)
self.table_selector.current(0)
ttk.Button(top_panel, text="Загрузить данные", command=self.load_selected_table).pack(side='left', padx=5)
# Контейнер отображения структуры выбранной таблицы
view_panel = ttk.Frame(tab, padding=5)
view_panel.pack(fill='both', expand=True)
scroll_y = ttk.Scrollbar(view_panel, orient="vertical")
scroll_x = ttk.Scrollbar(view_panel, orient="horizontal")
self.view_tree = ttk.Treeview(view_panel, yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set)
scroll_y.config(command=self.view_tree.yview)
scroll_x.config(command=self.view_tree.xview)
scroll_y.pack(side='right', fill='y')
scroll_x.pack(side='bottom', fill='x')
self.view_tree.pack(fill='both', expand=True)
def load_selected_table(self):
table_name = self.table_selector.get()
if not table_name:
return
query = f"SELECT * FROM {table_name};"
headers, rows = self.execute_query(query)
self.display_in_treeview(self.view_tree, headers, rows)
-- --- ВКЛАДКА 3: УПРАВЛЕНИЕ И ДОБАВЛЕНИЕ ДАННЫХ ---
def init_add_data_tab(self):
tab = ttk.Frame(self.notebook)
self.notebook.add(tab, text="Добавление данных")
# Форма ввода параметров нового врача
doctor_frame = ttk.LabelFrame(tab, text="Регистрация нового врача", padding=10)
doctor_frame.grid(row=0, column=0, padx=15, pady=15, sticky='nsew')
fields = ["Паспорт:", "Фамилия:", "Имя:", "Отчество:", "Адрес:", "Телефон:"]
self.doc_entries = {}
for i, field in enumerate(fields):
ttk.Label(doctor_frame, text=field).grid(row=i, column=0, sticky='w', pady=3)
entry = ttk.Entry(doctor_frame, width=30)
entry.grid(row=i, column=1, pady=3, padx=5)
self.doc_entries[field] = entry
ttk.Button(doctor_frame, text="Добавить врача", command=self.add_doctor).grid(row=len(fields), column=0, columnspan=2, pady=10)
# Форма ввода параметров нового пациента
patient_frame = ttk.LabelFrame(tab, text="Регистрация нового пациента", padding=10)
patient_frame.grid(row=0, column=1, padx=15, pady=15, sticky='nsew')
p_fields = ["Фамилия:", "Имя:", "Отчество:", "Адрес:", "Телефон:", "Дата рождения (ГГГГ-ММ-ДД):", "Полис ОМС/ДМС:"]
self.pat_entries = {}
for i, field in enumerate(p_fields):
ttk.Label(patient_frame, text=field).grid(row=i, column=0, sticky='w', pady=3)
entry = ttk.Entry(patient_frame, width=30)
entry.grid(row=i, column=1, pady=3, padx=5)
self.pat_entries[field] = entry
ttk.Button(patient_frame, text="Добавить пациента", command=self.add_patient).grid(row=len(p_fields), column=0, columnspan=2, pady=10)
def add_doctor(self):
passport = self.doc_entries["Паспорт:"].get().strip()
ln = self.doc_entries["Фамилия:"].get().strip()
fn = self.doc_entries["Имя:"].get().strip()
mn = self.doc_entries["Отчество:"].get().strip()
adr = self.doc_entries["Адрес:"].get().strip()
ph = self.doc_entries["Телефон:"].get().strip()
if not passport or not ln or not fn:
messagebox.showwarning("Внимание", "Поля Паспорт, Фамилия и Имя обязательны!")
return
query = "INSERT INTO врач (номер_паспорта, фамилия, имя, отчество, адрес, телефон) VALUES (%s, %s, %s, %s, %s, %s)"
if self.execute_insert(query, (passport, ln, fn, mn, adr, ph)):
messagebox.showinfo("Успех", f"Врач {ln} {fn} успешно добавлен в систему.")
for entry in self.doc_entries.values():
entry.delete(0, tk.END)
def add_patient(self):
ln = self.pat_entries["Фамилия:"].get().strip()
fn = self.pat_entries["Имя:"].get().strip()
mn = self.pat_entries["Отчество:"].get().strip()
adr = self.pat_entries["Адрес:"].get().strip()
ph = self.pat_entries["Телефон:"].get().strip()
dob = self.pat_entries["Дата рождения (ГГГГ-ММ-ДД):"].get().strip()
policy = self.pat_entries["Полис ОМС/ДМС:"].get().strip()
if not ln or not fn or not policy:
messagebox.showwarning("Внимание", "Поля Фамилия, Имя и Номер полиса обязательны!")
return
query = "INSERT INTO пациент (фамилия, имя, отчество, адрес, телефон, дата_рождения, лис_омс_дмс) VALUES (%s, %s, %s, %s, %s, %s, %s)"
if self.execute_insert(query, (ln, fn, mn, adr, ph, dob if dob else None, policy)):
messagebox.showinfo("Успех", f"Пациент {ln} {fn} успешно зарегистрирован.")
for entry in self.pat_entries.values():
entry.delete(0, tk.END)
if __name__ == "__main__":
root = tk.Tk()
app = MedicalCenterApp(root)
root.mainloop()