From 703adc3326e02015e1cef6eff16975e9c954bacf Mon Sep 17 00:00:00 2001 From: helldh Date: Wed, 26 Nov 2025 07:34:44 +0300 Subject: [PATCH] Full demo: complete --- control1.py | 807 ++++++++++++++++++++++++++++++++++++++++++++ control2.py | 693 +++++++++++++++++++++++++++++++++++++ fitness.db | Bin 0 -> 40960 bytes service_requests.db | Bin 0 -> 36864 bytes 4 files changed, 1500 insertions(+) create mode 100644 control1.py create mode 100644 control2.py create mode 100644 fitness.db create mode 100644 service_requests.db diff --git a/control1.py b/control1.py new file mode 100644 index 0000000..17a5a2f --- /dev/null +++ b/control1.py @@ -0,0 +1,807 @@ +import sys +import sqlite3 +from datetime import datetime +from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, + QLabel, QLineEdit, QTextEdit, QComboBox, QPushButton, QTableWidget, + QTableWidgetItem, QTabWidget, QGroupBox, QMessageBox, QFileDialog, + QSplitter, QHeaderView, QFormLayout, QCheckBox, QDialog) +from PyQt6.QtCore import Qt, pyqtSignal +from PyQt6.QtGui import QIcon, QAction +import os + +class DatabaseManager: + def __init__(self): + self.conn = sqlite3.connect('service_requests.db') + self.create_tables() + + def create_tables(self): + cursor = self.conn.cursor() + + # Таблица пользователей + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + role TEXT NOT NULL, + full_name TEXT NOT NULL, + phone TEXT + ) + ''') + + # Таблица заявок + cursor.execute(''' + CREATE TABLE IF NOT EXISTS service_requests ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + client_name TEXT NOT NULL, + client_phone TEXT NOT NULL, + equipment_type TEXT NOT NULL, + equipment_model TEXT NOT NULL, + problem_description TEXT NOT NULL, + status TEXT DEFAULT 'Новая', + priority TEXT DEFAULT 'Средний', + request_type TEXT DEFAULT 'Ремонт', + assigned_operator TEXT, + assigned_master TEXT, + created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + completed_date TIMESTAMP, + observer_group TEXT + ) + ''') + + # Таблица истории заявок + cursor.execute(''' + CREATE TABLE IF NOT EXISTS request_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + request_id INTEGER, + user_id INTEGER, + action TEXT, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (request_id) REFERENCES service_requests (id) + ) + ''') + + # Таблица вложений + cursor.execute(''' + CREATE TABLE IF NOT EXISTS attachments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + request_id INTEGER, + filename TEXT, + filepath TEXT, + uploaded_by INTEGER, + upload_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (request_id) REFERENCES service_requests (id) + ) + ''') + + # Таблица отчетов + cursor.execute(''' + CREATE TABLE IF NOT EXISTS reports ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + request_id INTEGER, + master_id INTEGER, + work_description TEXT, + parts_used TEXT, + time_spent REAL, + created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (request_id) REFERENCES service_requests (id) + ) + ''') + + # Таблица заказов запчастей + cursor.execute(''' + CREATE TABLE IF NOT EXISTS part_orders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + request_id INTEGER, + part_name TEXT, + quantity INTEGER, + status TEXT DEFAULT 'Заказан', + ordered_by INTEGER, + order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (request_id) REFERENCES service_requests (id) + ) + ''') + + # Создаем тестовых пользователей + self.create_test_users() + + self.conn.commit() + + def create_test_users(self): + cursor = self.conn.cursor() + + test_users = [ + ('client1', '123', 'client', 'Иван Иванов', '+79161234567'), + ('operator1', '123', 'operator', 'Петр Петров', '+79167654321'), + ('master1', '123', 'master', 'Сергей Сергеев', '+79169998877'), + ('admin1', '123', 'admin', 'Администратор Системы', '+79160001122') + ] + + for user in test_users: + try: + cursor.execute( + 'INSERT INTO users (username, password, role, full_name, phone) VALUES (?, ?, ?, ?, ?)', + user + ) + except sqlite3.IntegrityError: + pass # Пользователь уже существует + + self.conn.commit() + +class ServiceRequestApp(QMainWindow): + def __init__(self): + super().__init__() + self.db = DatabaseManager() + self.current_user = None + self.init_ui() + + def init_ui(self): + self.setWindowTitle('Система учета заявок на ремонт оргтехники') + self.setGeometry(100, 100, 1200, 700) + + # Центральный виджет + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # Основной layout + layout = QVBoxLayout(central_widget) + + # Панель входа + self.login_widget = self.create_login_widget() + layout.addWidget(self.login_widget) + + # Основной интерфейс (скрыт до входа) + self.main_tabs = QTabWidget() + self.main_tabs.setVisible(False) + layout.addWidget(self.main_tabs) + + def create_login_widget(self): + widget = QWidget() + layout = QVBoxLayout(widget) + + layout.addWidget(QLabel('Вход в систему')) + + form_layout = QFormLayout() + + self.username_input = QLineEdit() + self.password_input = QLineEdit() + self.password_input.setEchoMode(QLineEdit.EchoMode.Password) + + form_layout.addRow('Логин:', self.username_input) + form_layout.addRow('Пароль:', self.password_input) + + layout.addLayout(form_layout) + + login_btn = QPushButton('Войти') + login_btn.clicked.connect(self.login) + layout.addWidget(login_btn) + + return widget + + def login(self): + username = self.username_input.text() + password = self.password_input.text() + + cursor = self.db.conn.cursor() + cursor.execute( + 'SELECT * FROM users WHERE username = ? AND password = ?', + (username, password) + ) + + user = cursor.fetchone() + + if user: + self.current_user = { + 'id': user[0], + 'username': user[1], + 'role': user[3], + 'full_name': user[4] + } + self.show_main_interface() + else: + QMessageBox.warning(self, 'Ошибка', 'Неверный логин или пароль') + + def show_main_interface(self): + self.login_widget.setVisible(False) + self.main_tabs.setVisible(True) + + # Очищаем предыдущие вкладки + self.main_tabs.clear() + + role = self.current_user['role'] + + if role == 'client': + self.setup_client_interface() + elif role == 'operator': + self.setup_operator_interface() + elif role == 'master': + self.setup_master_interface() + elif role == 'admin': + self.setup_admin_interface() + + def setup_client_interface(self): + # Вкладка создания заявки + create_tab = QWidget() + layout = QVBoxLayout(create_tab) + + form_layout = QFormLayout() + + self.client_name = QLineEdit(self.current_user['full_name']) + self.client_phone = QLineEdit() + self.equipment_type = QComboBox() + self.equipment_type.addItems(['Принтер', 'Копир', 'Сканер', 'МФУ', 'Компьютер']) + self.equipment_model = QLineEdit() + self.problem_description = QTextEdit() + + form_layout.addRow('ФИО:', self.client_name) + form_layout.addRow('Телефон:', self.client_phone) + form_layout.addRow('Тип оборудования:', self.equipment_type) + form_layout.addRow('Модель:', self.equipment_model) + form_layout.addRow('Описание проблемы:', self.problem_description) + + layout.addLayout(form_layout) + + submit_btn = QPushButton('Создать заявку') + submit_btn.clicked.connect(self.create_service_request) + layout.addWidget(submit_btn) + + self.main_tabs.addTab(create_tab, 'Новая заявка') + + # Вкладка моих заявок + requests_tab = QWidget() + layout = QVBoxLayout(requests_tab) + + self.client_requests_table = QTableWidget() + self.client_requests_table.setColumnCount(6) + self.client_requests_table.setHorizontalHeaderLabels([ + 'ID', 'Оборудование', 'Модель', 'Статус', 'Дата создания', 'Приоритет' + ]) + layout.addWidget(self.client_requests_table) + + details_btn = QPushButton('Просмотреть детали') + details_btn.clicked.connect(self.show_request_details) + layout.addWidget(details_btn) + + self.main_tabs.addTab(requests_tab, 'Мои заявки') + + self.load_client_requests() + + def setup_operator_interface(self): + # Вкладка всех заявок + requests_tab = QWidget() + layout = QVBoxLayout(requests_tab) + + self.operator_requests_table = QTableWidget() + self.operator_requests_table.setColumnCount(8) + self.operator_requests_table.setHorizontalHeaderLabels([ + 'ID', 'Клиент', 'Оборудование', 'Модель', 'Статус', 'Приоритет', 'Дата', 'Оператор' + ]) + layout.addWidget(self.operator_requests_table) + + # Панель управления заявкой + control_group = QGroupBox('Управление заявкой') + control_layout = QHBoxLayout(control_group) + + self.status_combo = QComboBox() + self.status_combo.addItems(['Новая', 'В работе', 'Ожидает запчасти', 'Выполнена', 'Отменена']) + + self.priority_combo = QComboBox() + self.priority_combo.addItems(['Низкий', 'Средний', 'Высокий', 'Критичный']) + + self.type_combo = QComboBox() + self.type_combo.addItems(['Ремонт', 'Обслуживание', 'Консультация']) + + self.observer_group = QLineEdit() + + update_btn = QPushButton('Обновить заявку') + update_btn.clicked.connect(self.update_request_operator) + + control_layout.addWidget(QLabel('Статус:')) + control_layout.addWidget(self.status_combo) + control_layout.addWidget(QLabel('Приоритет:')) + control_layout.addWidget(self.priority_combo) + control_layout.addWidget(QLabel('Тип:')) + control_layout.addWidget(self.type_combo) + control_layout.addWidget(QLabel('Группа наблюдателей:')) + control_layout.addWidget(self.observer_group) + control_layout.addWidget(update_btn) + + layout.addWidget(control_group) + + self.main_tabs.addTab(requests_tab, 'Все заявки') + + # Вкладка архива + archive_tab = QWidget() + layout = QVBoxLayout(archive_tab) + + search_layout = QHBoxLayout() + self.archive_search = QLineEdit() + self.archive_search.setPlaceholderText('Поиск в архиве...') + search_btn = QPushButton('Найти') + search_btn.clicked.connect(self.search_archive) + + search_layout.addWidget(self.archive_search) + search_layout.addWidget(search_btn) + layout.addLayout(search_layout) + + self.archive_table = QTableWidget() + self.archive_table.setColumnCount(8) + self.archive_table.setHorizontalHeaderLabels([ + 'ID', 'Клиент', 'Оборудование', 'Модель', 'Статус', 'Дата создания', 'Дата завершения', 'Мастер' + ]) + layout.addWidget(self.archive_table) + + self.main_tabs.addTab(archive_tab, 'Архив') + + self.load_operator_requests() + self.load_archive() + + def setup_master_interface(self): + # Вкладка назначенных заявок + requests_tab = QWidget() + layout = QVBoxLayout(requests_tab) + + self.master_requests_table = QTableWidget() + self.master_requests_table.setColumnCount(7) + self.master_requests_table.setHorizontalHeaderLabels([ + 'ID', 'Клиент', 'Оборудование', 'Модель', 'Статус', 'Приоритет', 'Дата' + ]) + layout.addWidget(self.master_requests_table) + + # Панель управления для мастера + control_group = QGroupBox('Управление ремонтом') + control_layout = QVBoxLayout(control_group) + + # Смена статуса + status_layout = QHBoxLayout() + status_layout.addWidget(QLabel('Статус:')) + self.master_status_combo = QComboBox() + self.master_status_combo.addItems(['В работе', 'Ожидает запчасти', 'Выполнена']) + status_layout.addWidget(self.master_status_combo) + + update_status_btn = QPushButton('Обновить статус') + update_status_btn.clicked.connect(self.update_request_master) + status_layout.addWidget(update_status_btn) + + control_layout.addLayout(status_layout) + + # Заказ запчастей + parts_layout = QHBoxLayout() + parts_layout.addWidget(QLabel('Запчасть:')) + self.part_name = QLineEdit() + self.part_quantity = QLineEdit() + self.part_quantity.setText('1') + + parts_layout.addWidget(self.part_name) + parts_layout.addWidget(QLabel('Количество:')) + parts_layout.addWidget(self.part_quantity) + + order_parts_btn = QPushButton('Заказать запчасти') + order_parts_btn.clicked.connect(self.order_parts) + parts_layout.addWidget(order_parts_btn) + + control_layout.addLayout(parts_layout) + + # Прикрепление файлов + file_layout = QHBoxLayout() + attach_file_btn = QPushButton('Прикрепить файл') + attach_file_btn.clicked.connect(self.attach_file) + file_layout.addWidget(attach_file_btn) + + control_layout.addLayout(file_layout) + + # Создание отчета + report_layout = QHBoxLayout() + create_report_btn = QPushButton('Создать отчет') + create_report_btn.clicked.connect(self.create_report) + report_layout.addWidget(create_report_btn) + + control_layout.addLayout(report_layout) + + layout.addWidget(control_group) + + self.main_tabs.addTab(requests_tab, 'Мои заявки') + + self.load_master_requests() + + def setup_admin_interface(self): + # Админский интерфейс будет похож на операторский с дополнительными функциями + self.setup_operator_interface() + + # Добавляем вкладку управления пользователями + users_tab = QWidget() + layout = QVBoxLayout(users_tab) + + self.users_table = QTableWidget() + self.users_table.setColumnCount(4) + self.users_table.setHorizontalHeaderLabels(['ID', 'Логин', 'ФИО', 'Роль']) + layout.addWidget(self.users_table) + + add_user_btn = QPushButton('Добавить пользователя') + add_user_btn.clicked.connect(self.show_add_user_dialog) + layout.addWidget(add_user_btn) + + self.main_tabs.addTab(users_tab, 'Пользователи') + + self.load_users() + + def create_service_request(self): + cursor = self.db.conn.cursor() + cursor.execute(''' + INSERT INTO service_requests + (client_name, client_phone, equipment_type, equipment_model, problem_description) + VALUES (?, ?, ?, ?, ?) + ''', ( + self.client_name.text(), + self.client_phone.text(), + self.equipment_type.currentText(), + self.equipment_model.text(), + self.problem_description.toPlainText() + )) + + self.db.conn.commit() + + QMessageBox.information(self, 'Успех', 'Заявка успешно создана!') + + # Очищаем форму + self.client_phone.clear() + self.equipment_model.clear() + self.problem_description.clear() + + def load_client_requests(self): + cursor = self.db.conn.cursor() + cursor.execute(''' + SELECT id, equipment_type, equipment_model, status, created_date, priority + FROM service_requests + WHERE client_name = ? + ORDER BY created_date DESC + ''', (self.current_user['full_name'],)) + + requests = cursor.fetchall() + + self.client_requests_table.setRowCount(len(requests)) + for row, request in enumerate(requests): + for col, value in enumerate(request): + self.client_requests_table.setItem(row, col, QTableWidgetItem(str(value))) + + def load_operator_requests(self): + cursor = self.db.conn.cursor() + cursor.execute(''' + SELECT id, client_name, equipment_type, equipment_model, status, priority, created_date, assigned_operator + FROM service_requests + ORDER BY created_date DESC + ''') + + requests = cursor.fetchall() + + self.operator_requests_table.setRowCount(len(requests)) + for row, request in enumerate(requests): + for col, value in enumerate(request): + self.operator_requests_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else '')) + + def load_master_requests(self): + cursor = self.db.conn.cursor() + cursor.execute(''' + SELECT id, client_name, equipment_type, equipment_model, status, priority, created_date + FROM service_requests + WHERE status != 'Выполнена' AND status != 'Отменена' + ORDER BY + CASE priority + WHEN 'Критичный' THEN 1 + WHEN 'Высокий' THEN 2 + WHEN 'Средний' THEN 3 + WHEN 'Низкий' THEN 4 + END, + created_date + ''') + + requests = cursor.fetchall() + + self.master_requests_table.setRowCount(len(requests)) + for row, request in enumerate(requests): + for col, value in enumerate(request): + self.master_requests_table.setItem(row, col, QTableWidgetItem(str(value))) + + def load_archive(self): + cursor = self.db.conn.cursor() + cursor.execute(''' + SELECT id, client_name, equipment_type, equipment_model, status, created_date, completed_date, assigned_master + FROM service_requests + WHERE status = 'Выполнена' + ORDER BY completed_date DESC + ''') + + requests = cursor.fetchall() + + self.archive_table.setRowCount(len(requests)) + for row, request in enumerate(requests): + for col, value in enumerate(request): + self.archive_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else '')) + + def load_users(self): + cursor = self.db.conn.cursor() + cursor.execute('SELECT id, username, full_name, role FROM users') + + users = cursor.fetchall() + + self.users_table.setRowCount(len(users)) + for row, user in enumerate(users): + for col, value in enumerate(user): + self.users_table.setItem(row, col, QTableWidgetItem(str(value))) + + def show_request_details(self): + current_row = self.client_requests_table.currentRow() + if current_row >= 0: + request_id = self.client_requests_table.item(current_row, 0).text() + + cursor = self.db.conn.cursor() + cursor.execute('SELECT * FROM service_requests WHERE id = ?', (request_id,)) + request = cursor.fetchone() + + if request: + details = f""" + ID: {request[0]} + Клиент: {request[1]} + Телефон: {request[2]} + Оборудование: {request[3]} + Модель: {request[4]} + Описание проблемы: {request[5]} + Статус: {request[6]} + Приоритет: {request[7]} + Тип: {request[8]} + Дата создания: {request[11]} + """ + + QMessageBox.information(self, 'Детали заявки', details) + + def update_request_operator(self): + current_row = self.operator_requests_table.currentRow() + if current_row >= 0: + request_id = self.operator_requests_table.item(current_row, 0).text() + + cursor = self.db.conn.cursor() + cursor.execute(''' + UPDATE service_requests + SET status = ?, priority = ?, request_type = ?, observer_group = ?, assigned_operator = ? + WHERE id = ? + ''', ( + self.status_combo.currentText(), + self.priority_combo.currentText(), + self.type_combo.currentText(), + self.observer_group.text(), + self.current_user['full_name'], + request_id + )) + + self.db.conn.commit() + self.load_operator_requests() + QMessageBox.information(self, 'Успех', 'Заявка обновлена!') + + def update_request_master(self): + current_row = self.master_requests_table.currentRow() + if current_row >= 0: + request_id = self.master_requests_table.item(current_row, 0).text() + + cursor = self.db.conn.cursor() + cursor.execute(''' + UPDATE service_requests + SET status = ?, assigned_master = ? + WHERE id = ? + ''', ( + self.master_status_combo.currentText(), + self.current_user['full_name'], + request_id + )) + + self.db.conn.commit() + self.load_master_requests() + QMessageBox.information(self, 'Успех', 'Статус заявки обновлен!') + + def order_parts(self): + current_row = self.master_requests_table.currentRow() + if current_row >= 0: + request_id = self.master_requests_table.item(current_row, 0).text() + + cursor = self.db.conn.cursor() + cursor.execute(''' + INSERT INTO part_orders (request_id, part_name, quantity, ordered_by) + VALUES (?, ?, ?, ?) + ''', ( + request_id, + self.part_name.text(), + int(self.part_quantity.text()), + self.current_user['id'] + )) + + self.db.conn.commit() + + # Обновляем статус заявки + cursor.execute(''' + UPDATE service_requests + SET status = 'Ожидает запчасти' + WHERE id = ? + ''', (request_id,)) + + self.db.conn.commit() + + self.part_name.clear() + self.part_quantity.setText('1') + self.load_master_requests() + + QMessageBox.information(self, 'Успех', 'Запчасти заказаны!') + + def attach_file(self): + current_row = self.master_requests_table.currentRow() + if current_row >= 0: + request_id = self.master_requests_table.item(current_row, 0).text() + + file_path, _ = QFileDialog.getOpenFileName(self, 'Выберите файл') + if file_path: + filename = os.path.basename(file_path) + + cursor = self.db.conn.cursor() + cursor.execute(''' + INSERT INTO attachments (request_id, filename, filepath, uploaded_by) + VALUES (?, ?, ?, ?) + ''', ( + request_id, + filename, + file_path, + self.current_user['id'] + )) + + self.db.conn.commit() + QMessageBox.information(self, 'Успех', 'Файл прикреплен!') + + def create_report(self): + current_row = self.master_requests_table.currentRow() + if current_row >= 0: + request_id = self.master_requests_table.item(current_row, 0).text() + + # Диалог для ввода отчета + report_dialog = QDialog(self) + report_dialog.setWindowTitle('Создание отчета') + report_dialog.setModal(True) + layout = QVBoxLayout(report_dialog) + + form_layout = QFormLayout() + work_description = QTextEdit() + parts_used = QLineEdit() + time_spent = QLineEdit() + + form_layout.addRow('Описание работы:', work_description) + form_layout.addRow('Использованные запчасти:', parts_used) + form_layout.addRow('Затраченное время (часы):', time_spent) + + layout.addLayout(form_layout) + + buttons_layout = QHBoxLayout() + save_btn = QPushButton('Сохранить отчет') + cancel_btn = QPushButton('Отмена') + + buttons_layout.addWidget(save_btn) + buttons_layout.addWidget(cancel_btn) + layout.addLayout(buttons_layout) + + def save_report(): + cursor = self.db.conn.cursor() + cursor.execute(''' + INSERT INTO reports (request_id, master_id, work_description, parts_used, time_spent) + VALUES (?, ?, ?, ?, ?) + ''', ( + request_id, + self.current_user['id'], + work_description.toPlainText(), + parts_used.text(), + float(time_spent.text()) + )) + + # Обновляем статус заявки на выполненную + cursor.execute(''' + UPDATE service_requests + SET status = 'Выполнена', completed_date = CURRENT_TIMESTAMP + WHERE id = ? + ''', (request_id,)) + + self.db.conn.commit() + report_dialog.accept() + self.load_master_requests() + QMessageBox.information(self, 'Успех', 'Отчет создан!') + + save_btn.clicked.connect(save_report) + cancel_btn.clicked.connect(report_dialog.reject) + + report_dialog.exec() + + def search_archive(self): + search_text = self.archive_search.text() + cursor = self.db.conn.cursor() + + if search_text: + cursor.execute(''' + SELECT id, client_name, equipment_type, equipment_model, status, created_date, completed_date, assigned_master + FROM service_requests + WHERE status = 'Выполнена' AND + (client_name LIKE ? OR equipment_type LIKE ? OR equipment_model LIKE ?) + ORDER BY completed_date DESC + ''', (f'%{search_text}%', f'%{search_text}%', f'%{search_text}%')) + else: + cursor.execute(''' + SELECT id, client_name, equipment_type, equipment_model, status, created_date, completed_date, assigned_master + FROM service_requests + WHERE status = 'Выполнена' + ORDER BY completed_date DESC + ''') + + requests = cursor.fetchall() + + self.archive_table.setRowCount(len(requests)) + for row, request in enumerate(requests): + for col, value in enumerate(request): + self.archive_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else '')) + + def show_add_user_dialog(self): + dialog = QDialog(self) + dialog.setWindowTitle('Добавить пользователя') + dialog.setModal(True) + layout = QVBoxLayout(dialog) + + form_layout = QFormLayout() + username = QLineEdit() + password = QLineEdit() + password.setEchoMode(QLineEdit.EchoMode.Password) + full_name = QLineEdit() + phone = QLineEdit() + role = QComboBox() + role.addItems(['client', 'operator', 'master', 'admin']) + + form_layout.addRow('Логин:', username) + form_layout.addRow('Пароль:', password) + form_layout.addRow('ФИО:', full_name) + form_layout.addRow('Телефон:', phone) + form_layout.addRow('Роль:', role) + + layout.addLayout(form_layout) + + buttons_layout = QHBoxLayout() + save_btn = QPushButton('Сохранить') + cancel_btn = QPushButton('Отмена') + + buttons_layout.addWidget(save_btn) + buttons_layout.addWidget(cancel_btn) + layout.addLayout(buttons_layout) + + def save_user(): + cursor = self.db.conn.cursor() + try: + cursor.execute(''' + INSERT INTO users (username, password, role, full_name, phone) + VALUES (?, ?, ?, ?, ?) + ''', ( + username.text(), + password.text(), + role.currentText(), + full_name.text(), + phone.text() + )) + + self.db.conn.commit() + dialog.accept() + self.load_users() + QMessageBox.information(self, 'Успех', 'Пользователь добавлен!') + except sqlite3.IntegrityError: + QMessageBox.warning(self, 'Ошибка', 'Пользователь с таким логином уже существует!') + + save_btn.clicked.connect(save_user) + cancel_btn.clicked.connect(dialog.reject) + + dialog.exec() + +class Dialog(QDialog): + pass # Вспомогательный класс для диалогов + +if __name__ == '__main__': + app = QApplication(sys.argv) + window = ServiceRequestApp() + window.show() + sys.exit(app.exec()) diff --git a/control2.py b/control2.py new file mode 100644 index 0000000..d55c171 --- /dev/null +++ b/control2.py @@ -0,0 +1,693 @@ +import sys +import sqlite3 +from datetime import datetime, date +from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, + QHBoxLayout, QTabWidget, QTableWidget, QTableWidgetItem, + QPushButton, QLabel, QLineEdit, QComboBox, QDateEdit, + QTextEdit, QMessageBox, QHeaderView, QGroupBox, + QFormLayout, QSpinBox, QCheckBox, QTimeEdit) +from PyQt6.QtCore import Qt, QDate +from PyQt6.QtGui import QFont, QPalette, QColor +from PyQt6.QtCharts import QChart, QChartView, QPieSeries, QBarSeries, QBarSet, QBarCategoryAxis, QValueAxis + +class FitnessApp(QMainWindow): + def __init__(self): + super().__init__() + self.initDB() + self.initUI() + + def initDB(self): + """Инициализация базы данных""" + self.conn = sqlite3.connect('fitness.db') + self.cursor = self.conn.cursor() + + # Создание таблиц + self.create_tables() + # Заполнение тестовыми данными + self.insert_sample_data() + + def create_tables(self): + """Создание таблиц базы данных""" + tables = [ + """ + CREATE TABLE IF NOT EXISTS Users ( + userID INTEGER PRIMARY KEY, + fio TEXT NOT NULL, + phone TEXT, + email TEXT, + login TEXT UNIQUE, + password TEXT, + userType TEXT, + specialization TEXT, + birthDate DATE + ) + """, + """ + CREATE TABLE IF NOT EXISTS Memberships ( + membershipID INTEGER PRIMARY KEY, + clientID INTEGER, + membershipType TEXT, + startDate DATE, + endDate DATE, + visitsTotal INTEGER, + visitsUsed INTEGER, + zones TEXT, + membershipStatus TEXT, + cost REAL, + adminID INTEGER, + FOREIGN KEY (clientID) REFERENCES Users(userID), + FOREIGN KEY (adminID) REFERENCES Users(userID) + ) + """, + """ + CREATE TABLE IF NOT EXISTS Visits ( + visitID INTEGER PRIMARY KEY, + clientID INTEGER, + visitDate DATE, + checkInTime TIME, + checkOutTime TIME, + zone TEXT, + membershipID INTEGER, + FOREIGN KEY (clientID) REFERENCES Users(userID), + FOREIGN KEY (membershipID) REFERENCES Memberships(membershipID) + ) + """, + """ + CREATE TABLE IF NOT EXISTS GroupClasses ( + classID INTEGER PRIMARY KEY, + className TEXT, + trainerID INTEGER, + classDate DATE, + startTime TIME, + endTime TIME, + hall TEXT, + maxParticipants INTEGER, + enrolledParticipants INTEGER, + classStatus TEXT, + FOREIGN KEY (trainerID) REFERENCES Users(userID) + ) + """, + """ + CREATE TABLE IF NOT EXISTS PersonalTraining ( + trainingID INTEGER PRIMARY KEY, + clientID INTEGER, + trainerID INTEGER, + trainingDate DATE, + startTime TIME, + endTime TIME, + exercises TEXT, + notes TEXT, + progressMetrics TEXT, + FOREIGN KEY (clientID) REFERENCES Users(userID), + FOREIGN KEY (trainerID) REFERENCES Users(userID) + ) + """, + """ + CREATE TABLE IF NOT EXISTS ClassRegistrations ( + registrationID INTEGER PRIMARY KEY, + classID INTEGER, + clientID INTEGER, + registrationDate DATE, + status TEXT, + FOREIGN KEY (classID) REFERENCES GroupClasses(classID), + FOREIGN KEY (clientID) REFERENCES Users(userID) + ) + """, + """ + CREATE TABLE IF NOT EXISTS EquipmentRequests ( + requestID INTEGER PRIMARY KEY, + trainerID INTEGER, + equipment TEXT, + quantity INTEGER, + requestDate DATE, + status TEXT, + FOREIGN KEY (trainerID) REFERENCES Users(userID) + ) + """, + """ + CREATE TABLE IF NOT EXISTS ShiftSwaps ( + swapID INTEGER PRIMARY KEY, + trainerID1 INTEGER, + trainerID2 INTEGER, + shiftDate DATE, + status TEXT, + FOREIGN KEY (trainerID1) REFERENCES Users(userID), + FOREIGN KEY (trainerID2) REFERENCES Users(userID) + ) + """ + ] + + for table in tables: + self.cursor.execute(table) + self.conn.commit() + + def insert_sample_data(self): + """Вставка тестовых данных""" + # Проверяем, есть ли уже данные + self.cursor.execute("SELECT COUNT(*) FROM Users") + if self.cursor.fetchone()[0] > 0: + return + + users = [ + (1, 'Сидорова Марина Петровна', '89219014567', 'director@fitness.ru', 'director1', 'pass1', 'Директор', '', '1980-05-15'), + (2, 'Романова Анна Сергеевна', '89210125678', 'admin1@fitness.ru', 'admin1', 'pass2', 'Администратор', '', '1992-08-22'), + (4, 'Яковлева Елена Викторовна', '89211236789', 'admin2@fitness.ru', 'admin2', 'pass3', 'Администратор', '', '1988-11-10'), + (7, 'Петров Дмитрий Александрович', '89212347890', 'petrov@fitness.ru', 'trainer1', 'pass4', 'Тренер', 'Силовые тренировки', '1985-03-18'), + (9, 'Смирнова Ольга Игоревна', '89213458901', 'smirnova@fitness.ru', 'trainer2', 'pass5', 'Тренер', 'Йога, Пилатес', '1990-07-25'), + (11, 'Козлов Сергей Николаевич', '89214569012', 'kozlov@fitness.ru', 'trainer3', 'pass6', 'Тренер', 'Плавание', '1987-12-05'), + (16, 'Федорова Екатерина Дмитриевна', '89161112236', 'fedorova@mail.ru', 'client1', 'pass7', 'Клиент', '', '1995-04-12'), + (21, 'Михайлов Алексей Владимирович', '89162223347', 'mikhailov@gmail.com', 'client2', 'pass8', 'Клиент', '', '1988-09-30'), + (26, 'Новикова Ирина Сергеевна', '89163334458', 'novikova@yandex.ru', 'client3', 'pass9', 'Клиент', '', '1992-06-18'), + (30, 'Соколов Игорь Петрович', '89164445569', 'sokolov@mail.ru', 'client4', 'pass10', 'Клиент', '', '1983-02-28'), + (34, 'Павлова Мария Александровна', '89165556670', 'pavlova@gmail.com', 'client5', 'pass11', 'Клиент', '', '1997-11-07') + ] + + memberships = [ + (1, 16, 'Месячный безлимит', '2024-06-01', '2024-06-30', 999, 42, 'Зал, Бассейн, Групповые', 'Активен', 5000.00, 2), + (2, 21, '12 посещений', '2024-06-05', '2024-09-05', 12, 8, 'Зал', 'Активен', 4000.00, 2), + (3, 26, 'Годовой VIP', '2024-01-10', '2025-01-10', 999, 156, 'Все зоны', 'Активен', 45000.00, 4), + (4, 30, 'Разовое посещение', '2024-06-15', '2024-06-15', 1, 1, 'Зал', 'Завершен', 500.00, 2), + (5, 34, 'Квартальный', '2024-06-01', '2024-08-31', 999, 15, 'Зал, Групповые', 'Активен', 12000.00, 4) + ] + + visits = [ + (1, 16, '2024-06-15', '08:30', '10:15', 'Тренажерный зал', 1), + (2, 21, '2024-06-15', '09:00', '10:30', 'Тренажерный зал', 2), + (3, 26, '2024-06-15', '07:00', '08:30', 'Бассейн', 3), + (4, 16, '2024-06-15', '18:00', '19:45', 'Групповое занятие', 1), + (5, 34, '2024-06-15', '19:00', '20:30', 'Групповое занятие', 5) + ] + + group_classes = [ + (1, 'Йога для начинающих', 9, '2024-06-16', '10:00', '11:00', 'Зал 2', 15, 12, 'Запланировано'), + (2, 'Силовая аэробика', 7, '2024-06-16', '18:00', '19:00', 'Зал 1', 20, 18, 'Запланировано'), + (3, 'Пилатес', 9, '2024-06-17', '11:00', '12:00', 'Зал 2', 12, 12, 'Группа заполнена'), + (4, 'Аквааэробика', 11, '2024-06-17', '15:00', '16:00', 'Бассейн', 10, 7, 'Запланировано'), + (5, 'Бокс', 7, '2024-06-18', '19:00', '20:30', 'Зал 3', 8, 5, 'Запланировано') + ] + + personal_training = [ + (1, 26, 7, '2024-06-14', '16:00', '17:00', 'Жим лежа, Приседания, Тяга блока', 'Хорошая техника', 'Жим 80кг x 8'), + (2, 16, 9, '2024-06-13', '10:00', '11:00', 'Асаны йоги, Растяжка', 'Улучшилась гибкость', ''), + (3, 21, 7, '2024-06-12', '14:00', '15:00', 'Становая тяга, Жим гантелей', 'Нужно работать над техникой', 'Становая 60кг x 6') + ] + + # Вставка данных + self.cursor.executemany("INSERT INTO Users VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", users) + self.cursor.executemany("INSERT INTO Memberships VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", memberships) + self.cursor.executemany("INSERT INTO Visits VALUES (?, ?, ?, ?, ?, ?, ?)", visits) + self.cursor.executemany("INSERT INTO GroupClasses VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", group_classes) + self.cursor.executemany("INSERT INTO PersonalTraining VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", personal_training) + self.conn.commit() + + def initUI(self): + """Инициализация пользовательского интерфейса""" + self.setWindowTitle('Фитнес-клуб - Система управления') + self.setGeometry(100, 100, 1200, 800) + + # Центральный виджет с вкладками для разных ролей + self.tabs = QTabWidget() + + # Вкладка для администратора + self.admin_tab = QWidget() + self.init_admin_tab() + self.tabs.addTab(self.admin_tab, "Администратор") + + # Вкладка для тренера + self.trainer_tab = QWidget() + self.init_trainer_tab() + self.tabs.addTab(self.trainer_tab, "Тренер") + + # Вкладка для директора + self.director_tab = QWidget() + self.init_director_tab() + self.tabs.addTab(self.director_tab, "Директор") + + self.setCentralWidget(self.tabs) + + def init_admin_tab(self): + """Инициализация вкладки администратора""" + layout = QVBoxLayout() + + # Группа управления расписанием + schedule_group = QGroupBox("Управление расписанием групповых занятий") + schedule_layout = QVBoxLayout() + + # Таблица групповых занятий + self.classes_table = QTableWidget() + self.classes_table.setColumnCount(10) + self.classes_table.setHorizontalHeaderLabels([ + 'ID', 'Название', 'Тренер', 'Дата', 'Время начала', + 'Время окончания', 'Зал', 'Макс. участников', 'Записано', 'Статус' + ]) + schedule_layout.addWidget(self.classes_table) + + # Кнопки управления + btn_layout = QHBoxLayout() + self.add_class_btn = QPushButton("Добавить занятие") + self.edit_class_btn = QPushButton("Редактировать") + self.delete_class_btn = QPushButton("Удалить") + + btn_layout.addWidget(self.add_class_btn) + btn_layout.addWidget(self.edit_class_btn) + btn_layout.addWidget(self.delete_class_btn) + + schedule_layout.addLayout(btn_layout) + schedule_group.setLayout(schedule_layout) + layout.addWidget(schedule_group) + + # Группа управления абонементами + membership_group = QGroupBox("Управление абонементами") + membership_layout = QVBoxLayout() + + self.memberships_table = QTableWidget() + self.memberships_table.setColumnCount(8) + self.memberships_table.setHorizontalHeaderLabels([ + 'ID', 'Клиент', 'Тип', 'Начало', 'Окончание', + 'Использовано/Всего', 'Статус', 'Стоимость' + ]) + membership_layout.addWidget(self.memberships_table) + + # Кнопки для управления абонементами + membership_btn_layout = QHBoxLayout() + self.change_price_btn = QPushButton("Изменить стоимость") + self.freeze_membership_btn = QPushButton("Заморозить абонемент") + + membership_btn_layout.addWidget(self.change_price_btn) + membership_btn_layout.addWidget(self.freeze_membership_btn) + membership_layout.addLayout(membership_btn_layout) + + membership_group.setLayout(membership_layout) + layout.addWidget(membership_group) + + # Группа статистики + stats_group = QGroupBox("Статистика и отчетность") + stats_layout = QHBoxLayout() + + # Левая часть - форма для фильтров + stats_form = QFormLayout() + self.stats_start_date = QDateEdit() + self.stats_end_date = QDateEdit() + self.stats_start_date.setDate(QDate.currentDate().addMonths(-1)) + self.stats_end_date.setDate(QDate.currentDate()) + + stats_form.addRow("С:", self.stats_start_date) + stats_form.addRow("По:", self.stats_end_date) + + self.generate_stats_btn = QPushButton("Сформировать отчет") + stats_form.addRow(self.generate_stats_btn) + + stats_layout.addLayout(stats_form) + + # Правая часть - диаграмма + self.stats_chart = QChartView() + stats_layout.addWidget(self.stats_chart) + + stats_group.setLayout(stats_layout) + layout.addWidget(stats_group) + + # Загрузка данных + self.load_classes_data() + self.load_memberships_data() + + # Подключение сигналов + self.add_class_btn.clicked.connect(self.add_class) + self.generate_stats_btn.clicked.connect(self.generate_stats) + + self.admin_tab.setLayout(layout) + + def init_trainer_tab(self): + """Инициализация вкладки тренера""" + layout = QVBoxLayout() + + # Группа программ тренировок + programs_group = QGroupBox("Индивидуальные программы тренировок") + programs_layout = QVBoxLayout() + + self.programs_table = QTableWidget() + self.programs_table.setColumnCount(6) + self.programs_table.setHorizontalHeaderLabels([ + 'ID', 'Клиент', 'Дата', 'Упражнения', 'Заметки', 'Прогресс' + ]) + programs_layout.addWidget(self.programs_table) + + programs_btn_layout = QHBoxLayout() + self.add_program_btn = QPushButton("Добавить программу") + self.edit_program_btn = QPushButton("Редактировать") + + programs_btn_layout.addWidget(self.add_program_btn) + programs_btn_layout.addWidget(self.edit_program_btn) + programs_layout.addLayout(programs_btn_layout) + + programs_group.setLayout(programs_layout) + layout.addWidget(programs_group) + + # Группа базы упражнений + exercises_group = QGroupBox("База упражнений") + exercises_layout = QVBoxLayout() + + self.exercises_table = QTableWidget() + self.exercises_table.setColumnCount(3) + self.exercises_table.setHorizontalHeaderLabels(['ID', 'Название', 'Группа мышц']) + exercises_layout.addWidget(self.exercises_table) + + exercises_btn_layout = QHBoxLayout() + self.add_exercise_btn = QPushButton("Добавить упражнение") + exercises_btn_layout.addWidget(self.add_exercise_btn) + exercises_layout.addLayout(exercises_btn_layout) + + exercises_group.setLayout(exercises_layout) + layout.addWidget(exercises_group) + + # Группа запросов оборудования + equipment_group = QGroupBox("Запросы оборудования") + equipment_layout = QVBoxLayout() + + self.equipment_table = QTableWidget() + self.equipment_table.setColumnCount(5) + self.equipment_table.setHorizontalHeaderLabels([ + 'ID', 'Оборудование', 'Количество', 'Дата запроса', 'Статус' + ]) + equipment_layout.addWidget(self.equipment_table) + + equipment_btn_layout = QHBoxLayout() + self.request_equipment_btn = QPushButton("Запросить оборудование") + equipment_btn_layout.addWidget(self.request_equipment_btn) + equipment_layout.addLayout(equipment_btn_layout) + + equipment_group.setLayout(equipment_layout) + layout.addWidget(equipment_group) + + # Загрузка данных + self.load_programs_data() + self.load_equipment_data() + + self.trainer_tab.setLayout(layout) + + def init_director_tab(self): + """Инициализация вкладки директора""" + layout = QVBoxLayout() + + # Общая статистика + overall_stats_group = QGroupBox("Общая статистика клуба") + overall_layout = QHBoxLayout() + + # Левая часть - ключевые показатели + metrics_layout = QFormLayout() + self.total_members_label = QLabel("0") + self.active_members_label = QLabel("0") + self.monthly_revenue_label = QLabel("0 руб.") + self.attendance_rate_label = QLabel("0%") + + metrics_layout.addRow("Всего клиентов:", self.total_members_label) + metrics_layout.addRow("Активных абонементов:", self.active_members_label) + metrics_layout.addRow("Доход за месяц:", self.monthly_revenue_label) + metrics_layout.addRow("Посещаемость:", self.attendance_rate_label) + + overall_layout.addLayout(metrics_layout) + + # Правая часть - диаграмма доходов + self.revenue_chart = QChartView() + overall_layout.addWidget(self.revenue_chart) + + overall_stats_group.setLayout(overall_layout) + layout.addWidget(overall_stats_group) + + # Эффективность тренеров + trainers_group = QGroupBox("Эффективность тренеров") + trainers_layout = QVBoxLayout() + + self.trainers_table = QTableWidget() + self.trainers_table.setColumnCount(6) + self.trainers_table.setHorizontalHeaderLabels([ + 'Тренер', 'Групповые занятия', 'Персональные тренировки', + 'Средняя оценка', 'Доход', 'Бонусы' + ]) + trainers_layout.addWidget(self.trainers_table) + + trainers_group.setLayout(trainers_layout) + layout.addWidget(trainers_group) + + # Финансовые показатели + finance_group = QGroupBox("Финансовые показатели") + finance_layout = QVBoxLayout() + + self.finance_table = QTableWidget() + self.finance_table.setColumnCount(4) + self.finance_table.setHorizontalHeaderLabels([ + 'Период', 'Доход', 'Расходы', 'Прибыль' + ]) + finance_layout.addWidget(self.finance_table) + + finance_group.setLayout(finance_layout) + layout.addWidget(finance_group) + + # Загрузка данных + self.load_director_data() + + self.director_tab.setLayout(layout) + + def load_classes_data(self): + """Загрузка данных о групповых занятиях""" + self.cursor.execute(""" + SELECT gc.classID, gc.className, u.fio, gc.classDate, gc.startTime, + gc.endTime, gc.hall, gc.maxParticipants, gc.enrolledParticipants, gc.classStatus + FROM GroupClasses gc + LEFT JOIN Users u ON gc.trainerID = u.userID + ORDER BY gc.classDate, gc.startTime + """) + classes = self.cursor.fetchall() + + self.classes_table.setRowCount(len(classes)) + for row, class_data in enumerate(classes): + for col, data in enumerate(class_data): + self.classes_table.setItem(row, col, QTableWidgetItem(str(data))) + + def load_memberships_data(self): + """Загрузка данных об абонементах""" + self.cursor.execute(""" + SELECT m.membershipID, u.fio, m.membershipType, m.startDate, m.endDate, + m.visitsUsed || '/' || m.visitsTotal, m.membershipStatus, m.cost + FROM Memberships m + JOIN Users u ON m.clientID = u.userID + ORDER BY m.endDate + """) + memberships = self.cursor.fetchall() + + self.memberships_table.setRowCount(len(memberships)) + for row, membership_data in enumerate(memberships): + for col, data in enumerate(membership_data): + self.memberships_table.setItem(row, col, QTableWidgetItem(str(data))) + + def load_programs_data(self): + """Загрузка данных о программах тренировок""" + self.cursor.execute(""" + SELECT pt.trainingID, u.fio, pt.trainingDate, pt.exercises, pt.notes, pt.progressMetrics + FROM PersonalTraining pt + JOIN Users u ON pt.clientID = u.userID + ORDER BY pt.trainingDate DESC + """) + programs = self.cursor.fetchall() + + self.programs_table.setRowCount(len(programs)) + for row, program_data in enumerate(programs): + for col, data in enumerate(program_data): + self.programs_table.setItem(row, col, QTableWidgetItem(str(data))) + + def load_equipment_data(self): + """Загрузка данных о запросах оборудования""" + self.cursor.execute(""" + SELECT requestID, equipment, quantity, requestDate, status + FROM EquipmentRequests + ORDER BY requestDate DESC + """) + equipment = self.cursor.fetchall() + + self.equipment_table.setRowCount(len(equipment)) + for row, equipment_data in enumerate(equipment): + for col, data in enumerate(equipment_data): + self.equipment_table.setItem(row, col, QTableWidgetItem(str(data))) + + def load_director_data(self): + """Загрузка данных для директора""" + # Общая статистика + self.cursor.execute("SELECT COUNT(*) FROM Users WHERE userType = 'Клиент'") + total_clients = self.cursor.fetchone()[0] + self.total_members_label.setText(str(total_clients)) + + self.cursor.execute("SELECT COUNT(*) FROM Memberships WHERE membershipStatus = 'Активен'") + active_memberships = self.cursor.fetchone()[0] + self.active_members_label.setText(str(active_memberships)) + + self.cursor.execute(""" + SELECT SUM(cost) FROM Memberships + WHERE strftime('%Y-%m', startDate) = strftime('%Y-%m', 'now') + """) + monthly_revenue = self.cursor.fetchone()[0] or 0 + self.monthly_revenue_label.setText(f"{monthly_revenue:.2f} руб.") + + # Диаграмма доходов + self.cursor.execute(""" + SELECT membershipType, SUM(cost) + FROM Memberships + WHERE strftime('%Y', startDate) = strftime('%Y', 'now') + GROUP BY membershipType + """) + revenue_data = self.cursor.fetchall() + + series = QPieSeries() + for membership_type, revenue in revenue_data: + series.append(membership_type, revenue) + + chart = QChart() + chart.addSeries(series) + chart.setTitle("Доходы по типам абонементов") + chart.legend().setVisible(True) + chart.legend().setAlignment(Qt.AlignmentFlag.AlignBottom) + + self.revenue_chart.setChart(chart) + + # Данные по тренерам + self.cursor.execute(""" + SELECT u.fio, + COUNT(DISTINCT gc.classID) as group_classes, + COUNT(DISTINCT pt.trainingID) as personal_trainings, + '4.5' as avg_rating, + SUM(m.cost * 0.1) as revenue, + COUNT(DISTINCT pt.trainingID) * 100 as bonuses + FROM Users u + LEFT JOIN GroupClasses gc ON u.userID = gc.trainerID + LEFT JOIN PersonalTraining pt ON u.userID = pt.trainerID + LEFT JOIN Memberships m ON pt.clientID = m.clientID + WHERE u.userType = 'Тренер' + GROUP BY u.userID, u.fio + """) + trainers = self.cursor.fetchall() + + self.trainers_table.setRowCount(len(trainers)) + for row, trainer_data in enumerate(trainers): + for col, data in enumerate(trainer_data): + self.trainers_table.setItem(row, col, QTableWidgetItem(str(data))) + + def add_class(self): + """Добавление нового группового занятия""" + dialog = AddClassDialog(self) + if dialog.exec(): + class_data = dialog.get_data() + try: + self.cursor.execute(""" + INSERT INTO GroupClasses (className, trainerID, classDate, startTime, endTime, hall, maxParticipants, enrolledParticipants, classStatus) + VALUES (?, ?, ?, ?, ?, ?, ?, 0, 'Запланировано') + """, class_data) + self.conn.commit() + self.load_classes_data() + QMessageBox.information(self, "Успех", "Занятие успешно добавлено!") + except Exception as e: + QMessageBox.critical(self, "Ошибка", f"Не удалось добавить занятие: {str(e)}") + + def generate_stats(self): + """Генерация статистики посещаемости""" + start_date = self.stats_start_date.date().toString("yyyy-MM-dd") + end_date = self.stats_end_date.date().toString("yyyy-MM-dd") + + self.cursor.execute(""" + SELECT zone, COUNT(*) as visits + FROM Visits + WHERE visitDate BETWEEN ? AND ? + GROUP BY zone + """, (start_date, end_date)) + zone_stats = self.cursor.fetchall() + + series = QBarSeries() + bar_set = QBarSet("Посещения по зонам") + + categories = [] + visits = [] + + for zone, count in zone_stats: + categories.append(zone) + visits.append(count) + + bar_set.append(visits) + series.append(bar_set) + + chart = QChart() + chart.addSeries(series) + chart.setTitle(f"Посещаемость по зонам ({start_date} - {end_date})") + + axis_x = QBarCategoryAxis() + axis_x.append(categories) + chart.addAxis(axis_x, Qt.AlignmentFlag.AlignBottom) + series.attachAxis(axis_x) + + axis_y = QValueAxis() + chart.addAxis(axis_y, Qt.AlignmentFlag.AlignLeft) + series.attachAxis(axis_y) + + self.stats_chart.setChart(chart) + +class AddClassDialog(QMessageBox): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("Добавить групповое занятие") + self.setModal(True) + + self.class_name = QLineEdit() + self.trainer = QComboBox() + self.class_date = QDateEdit() + self.start_time = QTimeEdit() + self.end_time = QTimeEdit() + self.hall = QComboBox() + self.max_participants = QSpinBox() + + self.class_date.setDate(QDate.currentDate()) + self.start_time.setTime(QTime.currentTime()) + self.end_time.setTime(QTime.currentTime().addSecs(3600)) + self.max_participants.setRange(1, 100) + + # Заполнение комбобоксов + parent.cursor.execute("SELECT userID, fio FROM Users WHERE userType = 'Тренер'") + trainers = parent.cursor.fetchall() + for trainer_id, fio in trainers: + self.trainer.addItem(fio, trainer_id) + + self.hall.addItems(['Зал 1', 'Зал 2', 'Зал 3', 'Бассейн']) + + layout = QFormLayout() + layout.addRow("Название:", self.class_name) + layout.addRow("Тренер:", self.trainer) + layout.addRow("Дата:", self.class_date) + layout.addRow("Время начала:", self.start_time) + layout.addRow("Время окончания:", self.end_time) + layout.addRow("Зал:", self.hall) + layout.addRow("Макс. участников:", self.max_participants) + + widget = QWidget() + widget.setLayout(layout) + self.layout().addWidget(widget, 0, 0, 1, self.layout().columnCount()) + + self.addButton(QPushButton("Добавить"), QMessageBox.ButtonRole.AcceptRole) + self.addButton(QPushButton("Отмена"), QMessageBox.ButtonRole.RejectRole) + + def get_data(self): + """Получение данных из формы""" + return ( + self.class_name.text(), + self.trainer.currentData(), + self.class_date.date().toString("yyyy-MM-dd"), + self.start_time.time().toString("hh:mm"), + self.end_time.time().toString("hh:mm"), + self.hall.currentText(), + self.max_participants.value() + ) + +if __name__ == '__main__': + app = QApplication(sys.argv) + + # Установка стиля + app.setStyle('Fusion') + + window = FitnessApp() + window.show() + + sys.exit(app.exec()) diff --git a/fitness.db b/fitness.db new file mode 100644 index 0000000000000000000000000000000000000000..8472fdebbe62c0f8c7ff4c2c166eccc28304fbef GIT binary patch literal 40960 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 He**sn>yZe< literal 0 HcmV?d00001 diff --git a/service_requests.db b/service_requests.db new file mode 100644 index 0000000000000000000000000000000000000000..65c34220f6d36e45ef7459e037396d560c71b622 GIT binary patch literal 36864 zcmeI4?{Cva7{~1aU z-76?7+tg`8`=KEQ+t`b}?ik(5pzv<*CY`mHd&9f^1(Wt-FZS3;nx<(2+Yn6QYw7B< z@1C>o^W1Zf?|PRL$0u}0W2X%(t2%5@ct#LK;YG#-LD)v2l|t!grbw`KL1%GYyjFCZ zuFh;^;AYP zRm)KfE3H}fx@hZILXIV6mW;hLA+vP}tf!4_IP^3dk0<5Ba)KR8j8DcAC)rW?B#TWa zr^e${&ZHbq?yZutwAs97J4#u)I*x9zPuSQT3Ez^srFkSDaB-)_2}u|3X;7Sq);f**(qKe z^|fhfreQgD@zheG#p2D!s==&kJDOEj$!{6f8%kQUQlv+% z0eZ=%>de$oN#4vDYWe|&RLJz?wK9KpODM8ukGRlXw4_4+G^5*&Va=5+w0PNEp`8th zk5xlt5%uj$P1Ug~&uFNR%$iTM6I+-Ih9Z4^;$oMNeq*<{@p?-I{EexPjD9MklT9jX z&7Z}mk}N(wv02wr$ZF0Qxq`2%lb8ISZhF?PICJJd$elIPTBhL1YQKnS8RXNmRsCt5 z_u1S-KwQc=Ojye*<)GU?ku||ipmapJzpjK{Mif*D;3-OHp-GErV4rg)*~|(ue7qIOvlHMPs?>0ok^M^$6xT}b@Ew8=7A=1I-kirl$Dmv zvr+}UP0f*m5mC@{Y3*#0xtezj|JwWHuxv&0PXYSmZt*_5+bIOM|0RTf4!;+Eg(5gW z00ck)1V8`;KmY_l00ck)1VEs{2pkE9gdH6brAtj`^_(OP4Ehm%iKc$;(9{o2|G4Mf zd72Dba_9M7_Z;Kb3%9&E9(Qq1bW|GY@9!tcffoWHp|fMhSXW`f#w)B4Bavk);d>If z<;j;WmiY}|espwn|Ndz7`KFMtv*X!lS7~C%E1?wUUy&HS2P;LqIW1p0Ix;*oI3Vp8 z{ijE}3R8WaY$3)kdy`*$m6alEl9Xj=cqH0JIt_-o5a!`O!oP)oZ!i_3Iv@Z7AOHd& z00JNY0w4eaAOHd&(2xY$0z1UQbVhJ%pi?Z)Mg-aeJH^s$g4EO&=xi;{6nOvtZwlWR zX!L)-A%UP$AOHd&00JNY0w4eaAOHd&00JQJ6cFfW3iP#lJ2%#Ezibr)5z*fV5cJ0X zA}#yJ%KxXJF`$Yd00JNY0w4eaAOHd&00JQJloFU1#jp^Sq|uZl9aMxtp}n+9zH+(z z$jAsQ2a575w2b}^zvq77en>k5Xi0zkRXwe%j5ZD2DH(G3%1KY82{K^J;`6#Pv_-_-moyxKQP=UNqqyuOo|>D9y%~M00@8p2!H?xfB*=900@8p2owom{Ewyp0w4ea xAOHd&00JNY0w4eaAOHf5PXOcp#@8`w2m&Ag0w4eaAOHd&00JNY0w4eae*>LdKg|FD literal 0 HcmV?d00001