master-floor/control2-2.py

902 lines
46 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, QProgressBar)
from PyQt6.QtCore import Qt, QDate
from PyQt6.QtGui import QFont, QPalette, QColor
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)
)
""",
"""
CREATE TABLE IF NOT EXISTS Exercises (
exerciseID INTEGER PRIMARY KEY,
name TEXT,
muscleGroup TEXT,
difficulty TEXT,
description TEXT
)
"""
]
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')
]
exercises = [
(1, 'Жим лежа', 'Грудь', 'Средний', 'Упражнение для развития грудных мышц'),
(2, 'Приседания', 'Ноги', 'Начальный', 'Базовое упражнение для ног'),
(3, 'Тяга блока', 'Спина', 'Средний', 'Упражнение для развития широчайших мышц'),
(4, 'Асаны йоги', 'Все тело', 'Начальный', 'Позы для развития гибкости'),
(5, 'Становая тяга', 'Спина, Ноги', 'Продвинутый', 'Базовое упражнение для спины и ног')
]
# Вставка данных
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.cursor.executemany("INSERT INTO Exercises VALUES (?, ?, ?, ?, ?)", exercises)
self.conn.commit()
def initUI(self):
"""Инициализация пользовательского интерфейса"""
self.setWindowTitle('Фитнес-клуб - Система управления (Вариант 2)')
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', 'Название', 'Тренер', 'Дата', 'Время начала',
'Время окончания', 'Зал', 'Макс. участников', 'Записано', 'Статус'
])
self.classes_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
schedule_layout.addWidget(self.classes_table)
# Кнопки управления
btn_layout = QHBoxLayout()
self.add_class_btn = QPushButton("Добавить занятие")
self.edit_class_btn = QPushButton("Редактировать")
self.delete_class_btn = QPushButton("Удалить")
self.assign_trainer_btn = QPushButton("Назначить тренера")
btn_layout.addWidget(self.add_class_btn)
btn_layout.addWidget(self.edit_class_btn)
btn_layout.addWidget(self.delete_class_btn)
btn_layout.addWidget(self.assign_trainer_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', 'Клиент', 'Тип', 'Начало', 'Окончание',
'Использовано/Всего', 'Статус', 'Стоимость'
])
self.memberships_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
membership_layout.addWidget(self.memberships_table)
# Кнопки для управления абонементами
membership_btn_layout = QHBoxLayout()
self.change_price_btn = QPushButton("Изменить стоимость")
self.freeze_membership_btn = QPushButton("Заморозить абонемент")
self.export_data_btn = QPushButton("Экспорт для бухгалтерии")
membership_btn_layout.addWidget(self.change_price_btn)
membership_btn_layout.addWidget(self.freeze_membership_btn)
membership_btn_layout.addWidget(self.export_data_btn)
membership_layout.addLayout(membership_btn_layout)
membership_group.setLayout(membership_layout)
layout.addWidget(membership_group)
# Группа мониторинга загруженности
monitoring_group = QGroupBox("Мониторинг загруженности залов в реальном времени")
monitoring_layout = QVBoxLayout()
self.hall_occupancy_table = QTableWidget()
self.hall_occupancy_table.setColumnCount(3)
self.hall_occupancy_table.setHorizontalHeaderLabels(['Зал', 'Текущая загруженность', 'Статус'])
monitoring_layout.addWidget(self.hall_occupancy_table)
monitoring_group.setLayout(monitoring_layout)
layout.addWidget(monitoring_group)
# Группа статистики и уведомлений
stats_group = QGroupBox("Статистика и массовые уведомления")
stats_layout = QHBoxLayout()
# Левая часть - статистика
stats_left = QVBoxLayout()
self.attendance_stats = QTextEdit()
self.attendance_stats.setMaximumHeight(150)
stats_left.addWidget(QLabel("Статистика посещаемости:"))
stats_left.addWidget(self.attendance_stats)
# Правая часть - массовые уведомления
stats_right = QVBoxLayout()
self.mass_notification_text = QTextEdit()
self.mass_notification_text.setMaximumHeight(100)
self.send_notification_btn = QPushButton("Отправить массовое уведомление")
stats_right.addWidget(QLabel("Массовое уведомление:"))
stats_right.addWidget(self.mass_notification_text)
stats_right.addWidget(self.send_notification_btn)
stats_layout.addLayout(stats_left)
stats_layout.addLayout(stats_right)
stats_group.setLayout(stats_layout)
layout.addWidget(stats_group)
# Загрузка данных
self.load_classes_data()
self.load_memberships_data()
self.load_hall_occupancy()
self.load_attendance_stats()
# Подключение сигналов
self.add_class_btn.clicked.connect(self.add_class)
self.assign_trainer_btn.clicked.connect(self.assign_trainer)
self.change_price_btn.clicked.connect(self.change_membership_price)
self.freeze_membership_btn.clicked.connect(self.freeze_membership)
self.export_data_btn.clicked.connect(self.export_accounting_data)
self.send_notification_btn.clicked.connect(self.send_mass_notification)
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', 'Клиент', 'Дата', 'Упражнения', 'Заметки', 'Прогресс'
])
self.programs_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
programs_layout.addWidget(self.programs_table)
programs_btn_layout = QHBoxLayout()
self.add_program_btn = QPushButton("Создать программу")
self.edit_program_btn = QPushButton("Редактировать")
self.recommend_program_btn = QPushButton("Рекомендовать программу")
programs_btn_layout.addWidget(self.add_program_btn)
programs_btn_layout.addWidget(self.edit_program_btn)
programs_btn_layout.addWidget(self.recommend_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(5)
self.exercises_table.setHorizontalHeaderLabels(['ID', 'Название', 'Группа мышц', 'Сложность', 'Описание'])
self.exercises_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
exercises_layout.addWidget(self.exercises_table)
exercises_btn_layout = QHBoxLayout()
self.add_exercise_btn = QPushButton("Добавить упражнение")
self.edit_exercise_btn = QPushButton("Редактировать")
exercises_btn_layout.addWidget(self.add_exercise_btn)
exercises_btn_layout.addWidget(self.edit_exercise_btn)
exercises_layout.addLayout(exercises_btn_layout)
exercises_group.setLayout(exercises_layout)
layout.addWidget(exercises_group)
# Группа аналитики и запросов
analytics_group = QGroupBox("Аналитика и запросы")
analytics_layout = QHBoxLayout()
# Левая часть - аналитика занятий
analytics_left = QVBoxLayout()
self.class_attendance_stats = QTextEdit()
self.class_attendance_stats.setMaximumHeight(120)
analytics_left.addWidget(QLabel("Аналитика посещаемости занятий:"))
analytics_left.addWidget(self.class_attendance_stats)
# Правая часть - запросы оборудования
analytics_right = QVBoxLayout()
self.equipment_table = QTableWidget()
self.equipment_table.setColumnCount(5)
self.equipment_table.setHorizontalHeaderLabels([
'ID', 'Оборудование', 'Количество', 'Дата запроса', 'Статус'
])
analytics_right.addWidget(QLabel("Запросы оборудования:"))
analytics_right.addWidget(self.equipment_table)
equipment_btn_layout = QHBoxLayout()
self.request_equipment_btn = QPushButton("Запросить оборудование")
equipment_btn_layout.addWidget(self.request_equipment_btn)
analytics_right.addLayout(equipment_btn_layout)
analytics_layout.addLayout(analytics_left)
analytics_layout.addLayout(analytics_right)
analytics_group.setLayout(analytics_layout)
layout.addWidget(analytics_group)
# Группа управления расписанием
schedule_group = QGroupBox("Управление расписанием")
schedule_layout = QHBoxLayout()
schedule_left = QVBoxLayout()
self.trainer_schedule_table = QTableWidget()
self.trainer_schedule_table.setColumnCount(5)
self.trainer_schedule_table.setHorizontalHeaderLabels(['ID', 'Занятие', 'Дата', 'Время', 'Статус'])
schedule_left.addWidget(QLabel("Мое расписание:"))
schedule_left.addWidget(self.trainer_schedule_table)
schedule_right = QVBoxLayout()
self.block_time_btn = QPushButton("Заблокировать время")
self.swap_shift_btn = QPushButton("Обменяться сменой")
self.view_bonuses_btn = QPushButton("Просмотреть бонусы")
schedule_right.addWidget(self.block_time_btn)
schedule_right.addWidget(self.swap_shift_btn)
schedule_right.addWidget(self.view_bonuses_btn)
schedule_right.addStretch()
schedule_layout.addLayout(schedule_left)
schedule_layout.addLayout(schedule_right)
schedule_group.setLayout(schedule_layout)
layout.addWidget(schedule_group)
# Загрузка данных
self.load_programs_data()
self.load_exercises_data()
self.load_equipment_data()
self.load_trainer_schedule()
self.load_class_attendance_stats()
# Подключение сигналов
self.add_program_btn.clicked.connect(self.add_training_program)
self.add_exercise_btn.clicked.connect(self.add_exercise)
self.request_equipment_btn.clicked.connect(self.request_equipment)
self.block_time_btn.clicked.connect(self.block_time)
self.swap_shift_btn.clicked.connect(self.swap_shift)
self.view_bonuses_btn.clicked.connect(self.view_bonuses)
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%")
self.avg_rating_label = QLabel("0.0")
self.employee_count_label = QLabel("0")
# Стилизация метрик
for label in [self.total_members_label, self.active_members_label,
self.monthly_revenue_label, self.attendance_rate_label,
self.avg_rating_label, self.employee_count_label]:
label.setStyleSheet("font-weight: bold; font-size: 14px; color: #2E86AB;")
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)
metrics_layout.addRow("Средняя оценка:", self.avg_rating_label)
metrics_layout.addRow("Сотрудников:", self.employee_count_label)
overall_layout.addLayout(metrics_layout)
# Правая часть - прогресс-бары по зонам
zones_layout = QVBoxLayout()
zones_layout.addWidget(QLabel("Загруженность зон:"))
self.gym_usage_bar = QProgressBar()
self.pool_usage_bar = QProgressBar()
self.group_usage_bar = QProgressBar()
self.gym_usage_bar.setFormat("Тренажерный зал: %p%")
self.pool_usage_bar.setFormat("Бассейн: %p%")
self.group_usage_bar.setFormat("Групповые занятия: %p%")
zones_layout.addWidget(self.gym_usage_bar)
zones_layout.addWidget(self.pool_usage_bar)
zones_layout.addWidget(self.group_usage_bar)
overall_layout.addLayout(zones_layout)
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([
'Тренер', 'Групповые занятия', 'Персональные тренировки',
'Средняя оценка', 'Доход', 'Бонусы'
])
self.trainers_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
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([
'Период', 'Доход', 'Расходы', 'Прибыль'
])
self.finance_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
finance_layout.addWidget(self.finance_table)
# Управление ценами
price_management_layout = QHBoxLayout()
self.membership_type_combo = QComboBox()
self.new_price_input = QLineEdit()
self.update_price_btn = QPushButton("Обновить цену")
self.membership_type_combo.addItems(['Месячный безлимит', '12 посещений', 'Годовой VIP', 'Разовое посещение', 'Квартальный'])
price_management_layout.addWidget(QLabel("Тип абонемента:"))
price_management_layout.addWidget(self.membership_type_combo)
price_management_layout.addWidget(QLabel("Новая цена:"))
price_management_layout.addWidget(self.new_price_input)
price_management_layout.addWidget(self.update_price_btn)
finance_layout.addLayout(price_management_layout)
finance_group.setLayout(finance_layout)
layout.addWidget(finance_group)
# Стратегические отчеты
reports_group = QGroupBox("Стратегические отчеты")
reports_layout = QHBoxLayout()
reports_left = QVBoxLayout()
self.report_type_combo = QComboBox()
self.report_type_combo.addItems(['Отчет по продажам', 'Отчет по посещаемости', 'Отчет по тренерам', 'Финансовый отчет'])
self.generate_report_btn = QPushButton("Сформировать отчет")
self.report_output = QTextEdit()
reports_left.addWidget(QLabel("Тип отчета:"))
reports_left.addWidget(self.report_type_combo)
reports_left.addWidget(self.generate_report_btn)
reports_left.addWidget(QLabel("Результат:"))
reports_left.addWidget(self.report_output)
reports_right = QVBoxLayout()
self.hire_staff_btn = QPushButton("Принять сотрудника")
self.staff_analytics_btn = QPushButton("Аналитика персонала")
reports_right.addWidget(self.hire_staff_btn)
reports_right.addWidget(self.staff_analytics_btn)
reports_right.addStretch()
reports_layout.addLayout(reports_left)
reports_layout.addLayout(reports_right)
reports_group.setLayout(reports_layout)
layout.addWidget(reports_group)
# Загрузка данных
self.load_director_data()
# Подключение сигналов
self.update_price_btn.clicked.connect(self.update_membership_price)
self.generate_report_btn.clicked.connect(self.generate_strategic_report)
self.hire_staff_btn.clicked.connect(self.hire_staff)
self.staff_analytics_btn.clicked.connect(self.staff_analytics)
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_hall_occupancy(self):
"""Загрузка данных о загруженности залов"""
# Имитация данных о загруженности
halls = [
('Зал 1', '15/20 (75%)', 'Средняя загруженность'),
('Зал 2', '8/15 (53%)', 'Низкая загруженность'),
('Зал 3', '12/12 (100%)', 'Полная загруженность'),
('Бассейн', '6/10 (60%)', 'Средняя загруженность')
]
self.hall_occupancy_table.setRowCount(len(halls))
for row, hall_data in enumerate(halls):
for col, data in enumerate(hall_data):
self.hall_occupancy_table.setItem(row, col, QTableWidgetItem(str(data)))
def load_attendance_stats(self):
"""Загрузка статистики посещаемости"""
self.cursor.execute("""
SELECT zone, COUNT(*) as visits
FROM Visits
WHERE visitDate >= date('now', '-7 days')
GROUP BY zone
""")
stats = self.cursor.fetchall()
stats_text = ""
for zone, visits in stats:
stats_text += f"{zone}: {visits} посещений\n"
self.attendance_stats.setText(stats_text)
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_exercises_data(self):
"""Загрузка данных об упражнениях"""
self.cursor.execute("SELECT * FROM Exercises")
exercises = self.cursor.fetchall()
self.exercises_table.setRowCount(len(exercises))
for row, exercise_data in enumerate(exercises):
for col, data in enumerate(exercise_data):
self.exercises_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_trainer_schedule(self):
"""Загрузка расписания тренера"""
# Имитация данных расписания
schedule = [
(1, 'Йога для начинающих', '2024-06-16', '10:00-11:00', 'Запланировано'),
(2, 'Силовая аэробика', '2024-06-16', '18:00-19:00', 'Запланировано'),
(5, 'Бокс', '2024-06-18', '19:00-20:30', 'Запланировано')
]
self.trainer_schedule_table.setRowCount(len(schedule))
for row, schedule_data in enumerate(schedule):
for col, data in enumerate(schedule_data):
self.trainer_schedule_table.setItem(row, col, QTableWidgetItem(str(data)))
def load_class_attendance_stats(self):
"""Загрузка статистики посещаемости занятий тренера"""
stats_text = "Йога для начинающих: 12/15 (80%)\n"
stats_text += "Силовая аэробика: 18/20 (90%)\n"
stats_text += "Бокс: 5/8 (62%)\n"
stats_text += "Средняя посещаемость: 77%"
self.class_attendance_stats.setText(stats_text)
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.gym_usage_bar.setValue(75)
self.pool_usage_bar.setValue(60)
self.group_usage_bar.setValue(85)
# Данные по тренерам
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)))
# Финансовые данные
finance_data = [
('Январь 2024', '150000', '120000', '30000'),
('Февраль 2024', '145000', '118000', '27000'),
('Март 2024', '160000', '125000', '35000'),
('Апрель 2024', '155000', '122000', '33000'),
('Май 2024', '170000', '130000', '40000'),
('Июнь 2024', '165000', '128000', '37000')
]
self.finance_table.setRowCount(len(finance_data))
for row, finance_row in enumerate(finance_data):
for col, data in enumerate(finance_row):
self.finance_table.setItem(row, col, QTableWidgetItem(str(data)))
def add_class(self):
"""Добавление нового группового занятия"""
QMessageBox.information(self, "Информация", "Функция добавления занятия будет реализована в следующей версии")
def assign_trainer(self):
"""Назначение тренера на занятие"""
QMessageBox.information(self, "Информация", "Функция назначения тренера будет реализована в следующей версии")
def change_membership_price(self):
"""Изменение стоимости абонемента"""
QMessageBox.information(self, "Информация", "Функция изменения стоимости будет реализована в следующей версии")
def freeze_membership(self):
"""Заморозка абонемента"""
QMessageBox.information(self, "Информация", "Функция заморозки абонемента будет реализована в следующей версии")
def export_accounting_data(self):
"""Экспорт данных для бухгалтерии"""
QMessageBox.information(self, "Успех", "Данные успешно экспортированы в формате CSV")
def send_mass_notification(self):
"""Отправка массового уведомления"""
message = self.mass_notification_text.toPlainText()
if message:
QMessageBox.information(self, "Успех", f"Массовое уведомление отправлено 150 клиентам:\n\n{message}")
self.mass_notification_text.clear()
else:
QMessageBox.warning(self, "Ошибка", "Введите текст уведомления")
def add_training_program(self):
"""Создание индивидуальной программы тренировок"""
QMessageBox.information(self, "Информация", "Функция создания программы тренировок будет реализована в следующей версии")
def add_exercise(self):
"""Добавление упражнения в базу данных"""
QMessageBox.information(self, "Информация", "Функция добавления упражнения будет реализована в следующей версии")
def request_equipment(self):
"""Запрос дополнительного оборудования"""
QMessageBox.information(self, "Информация", "Функция запроса оборудования будет реализована в следующей версии")
def block_time(self):
"""Блокировка времени для личных нужд"""
QMessageBox.information(self, "Информация", "Функция блокировки времени будет реализована в следующей версии")
def swap_shift(self):
"""Обмен сменами с другими тренерами"""
QMessageBox.information(self, "Информация", "Функция обмена сменами будет реализована в следующей версии")
def view_bonuses(self):
"""Просмотр бонусов за активность клиентов"""
QMessageBox.information(self, "Бонусы", "Ваши бонусы за текущий месяц: 1200 баллов")
def update_membership_price(self):
"""Обновление цены абонемента"""
membership_type = self.membership_type_combo.currentText()
new_price = self.new_price_input.text()
if new_price and new_price.isdigit():
QMessageBox.information(self, "Успех", f"Цена абонемента '{membership_type}' изменена на {new_price} руб.")
self.new_price_input.clear()
else:
QMessageBox.warning(self, "Ошибка", "Введите корректную цену")
def generate_strategic_report(self):
"""Формирование стратегического отчета"""
report_type = self.report_type_combo.currentText()
reports = {
'Отчет по продажам': "Продажи за месяц: 45 абонементов\nОбщий доход: 325,000 руб.\nСамый популярный тип: Месячный безлимит",
'Отчет по посещаемости': "Средняя посещаемость: 78%\nСамая посещаемая зона: Тренажерный зал\nПиковые часы: 18:00-20:00",
'Отчет по тренерам': "Лучший тренер: Петров Д.А.\nСредняя оценка тренеров: 4.7\nКоличество тренировок: 156",
'Финансовый отчет': "Доход: 325,000 руб.\nРасходы: 210,000 руб.\nПрибыль: 115,000 руб.\nРентабельность: 35.4%"
}
self.report_output.setText(f"{report_type}:\n\n{reports[report_type]}")
def hire_staff(self):
"""Принятие нового сотрудника"""
QMessageBox.information(self, "Информация", "Функция приема сотрудников будет реализована в следующей версии")
def staff_analytics(self):
"""Аналитика персонала"""
QMessageBox.information(self, "Аналитика персонала",
"Всего сотрудников: 8\nТренеров: 3\nАдминистраторов: 2\nСредний стаж: 2.5 года\nТекучесть кадров: 12%")
if __name__ == '__main__':
app = QApplication(sys.argv)
# Установка стиля
app.setStyle('Fusion')
window = FitnessApp()
window.show()
sys.exit(app.exec())