Full demo: complete
This commit is contained in:
parent
bce57e79f2
commit
703adc3326
4 changed files with 1500 additions and 0 deletions
807
control1.py
Normal file
807
control1.py
Normal file
|
|
@ -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())
|
||||||
693
control2.py
Normal file
693
control2.py
Normal file
|
|
@ -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())
|
||||||
BIN
fitness.db
Normal file
BIN
fitness.db
Normal file
Binary file not shown.
BIN
service_requests.db
Normal file
BIN
service_requests.db
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue