diff --git a/control1-2.py b/control1-2.py deleted file mode 100644 index 360f50d..0000000 --- a/control1-2.py +++ /dev/null @@ -1,1302 +0,0 @@ -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, QDateEdit) -from PyQt6.QtCore import Qt, pyqtSignal, QDate -from PyQt6.QtGui import QIcon, QAction -import os - -class DatabaseManager: - def __init__(self): - self.conn = sqlite3.connect('service_requests_v2.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, - email 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, - client_email TEXT, - equipment_type TEXT NOT NULL, - equipment_model TEXT NOT NULL, - serial_number TEXT, - problem_description TEXT NOT NULL, - status TEXT DEFAULT 'Новая', - priority TEXT DEFAULT 'Средний', - request_type TEXT DEFAULT 'Ремонт', - assigned_operator TEXT, - assigned_master TEXT, - observer_group TEXT, - created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - completed_date TIMESTAMP, - deadline DATE, - marked_for_deletion BOOLEAN DEFAULT 0, - duplicate_of INTEGER - ) - ''') - - # Таблица истории заявок - cursor.execute(''' - CREATE TABLE IF NOT EXISTS request_history ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - request_id INTEGER, - user_id INTEGER, - action TEXT, - details 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, - file_type 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, - labor_cost REAL, - parts_cost REAL, - total_cost 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, - part_number TEXT, - quantity INTEGER, - supplier TEXT, - estimated_cost REAL, - status TEXT DEFAULT 'Заказан', - ordered_by INTEGER, - order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - expected_date DATE, - FOREIGN KEY (request_id) REFERENCES service_requests (id) - ) - ''') - - # Таблица МТР (материально-технических ресурсов) - cursor.execute(''' - CREATE TABLE IF NOT EXISTS material_needs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - request_id INTEGER, - material_name TEXT, - material_type TEXT, - quantity INTEGER, - unit TEXT, - urgency TEXT DEFAULT 'Обычная', - status TEXT DEFAULT 'Требуется', - estimated_cost REAL, - notes TEXT, - created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (request_id) REFERENCES service_requests (id) - ) - ''') - - # Создаем тестовых пользователей для варианта 2 - self.create_test_users() - - self.conn.commit() - - def create_test_users(self): - cursor = self.conn.cursor() - - test_users = [ - ('operator1', '123', 'operator', 'Петр Петров', '+79167654321', 'operator@company.ru'), - ('master1', '123', 'master', 'Сергей Сергеев', '+79169998877', 'master@company.ru'), - ('admin1', '123', 'admin', 'Администратор Системы', '+79160001122', 'admin@company.ru') - ] - - for user in test_users: - try: - cursor.execute( - 'INSERT INTO users (username, password, role, full_name, phone, email) VALUES (?, ?, ?, ?, ?, ?)', - user - ) - except sqlite3.IntegrityError: - pass # Пользователь уже существует - - self.conn.commit() - -class ServiceRequestAppV2(QMainWindow): - def __init__(self): - super().__init__() - self.db = DatabaseManager() - self.current_user = None - self.init_ui() - - def init_ui(self): - self.setWindowTitle('Система учета заявок на ремонт оргтехники - Вариант 2') - self.setGeometry(100, 100, 1400, 800) - - # Центральный виджет - 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('Вход в систему - Вариант 2')) - - 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) - - # Подсказка с тестовыми пользователями - hint = QLabel('Тестовые пользователи: operator1/123, master1/123, admin1/123') - hint.setStyleSheet('color: gray; font-size: 10px;') - layout.addWidget(hint) - - 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 == 'operator': - self.setup_operator_interface() - elif role == 'master': - self.setup_master_interface() - elif role == 'admin': - self.setup_admin_interface() - - def setup_operator_interface(self): - # Вкладка регистрации заявок - register_tab = QWidget() - layout = QVBoxLayout(register_tab) - - layout.addWidget(QLabel('Регистрация новой заявки')) - - form_layout = QFormLayout() - - self.op_client_name = QLineEdit() - self.op_client_phone = QLineEdit() - self.op_client_email = QLineEdit() - self.op_equipment_type = QComboBox() - self.op_equipment_type.addItems(['Принтер', 'Копир', 'Сканер', 'МФУ', 'Компьютер', 'Монитор', 'Телефон', 'Другое']) - self.op_equipment_model = QLineEdit() - self.op_serial_number = QLineEdit() - self.op_problem_description = QTextEdit() - - form_layout.addRow('ФИО клиента:', self.op_client_name) - form_layout.addRow('Телефон:', self.op_client_phone) - form_layout.addRow('Email:', self.op_client_email) - form_layout.addRow('Тип оборудования:', self.op_equipment_type) - form_layout.addRow('Модель:', self.op_equipment_model) - form_layout.addRow('Серийный номер:', self.op_serial_number) - form_layout.addRow('Описание проблемы:', self.op_problem_description) - - layout.addLayout(form_layout) - - submit_btn = QPushButton('Зарегистрировать заявку') - submit_btn.clicked.connect(self.register_service_request) - layout.addWidget(submit_btn) - - self.main_tabs.addTab(register_tab, 'Регистрация заявки') - - # Вкладка управления заявками - management_tab = QWidget() - layout = QVBoxLayout(management_tab) - - # Панель фильтров - filter_layout = QHBoxLayout() - filter_layout.addWidget(QLabel('Статус:')) - self.status_filter = QComboBox() - self.status_filter.addItems(['Все', 'Новая', 'В работе', 'Ожидает запчасти', 'Выполнена', 'Отменена']) - filter_layout.addWidget(self.status_filter) - - filter_layout.addWidget(QLabel('Приоритет:')) - self.priority_filter = QComboBox() - self.priority_filter.addItems(['Все', 'Низкий', 'Средний', 'Высокий', 'Критичный']) - filter_layout.addWidget(self.priority_filter) - - filter_btn = QPushButton('Применить фильтры') - filter_btn.clicked.connect(self.load_operator_requests) - filter_layout.addWidget(filter_btn) - - layout.addLayout(filter_layout) - - self.operator_requests_table = QTableWidget() - self.operator_requests_table.setColumnCount(10) - 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() - self.observer_group.setPlaceholderText('Группа наблюдателей') - - mark_delete_btn = QPushButton('Пометить на удаление') - mark_delete_btn.clicked.connect(self.mark_request_for_deletion) - - 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(self.observer_group) - control_layout.addWidget(update_btn) - control_layout.addWidget(mark_delete_btn) - - layout.addWidget(control_group) - - self.main_tabs.addTab(management_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(9) - 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(8) - 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_group = QGroupBox('Заказ запчастей') - parts_layout = QFormLayout(parts_group) - - self.part_name = QLineEdit() - self.part_number = QLineEdit() - self.part_quantity = QLineEdit() - self.part_quantity.setText('1') - self.part_supplier = QLineEdit() - self.part_cost = QLineEdit() - - parts_layout.addRow('Название запчасти:', self.part_name) - parts_layout.addRow('Номер запчасти:', self.part_number) - parts_layout.addRow('Количество:', self.part_quantity) - parts_layout.addRow('Поставщик:', self.part_supplier) - parts_layout.addRow('Примерная стоимость:', self.part_cost) - - order_parts_btn = QPushButton('Заказать запчасти') - order_parts_btn.clicked.connect(self.order_parts) - parts_layout.addRow(order_parts_btn) - - control_layout.addWidget(parts_group) - - # Прикрепление файлов - file_layout = QHBoxLayout() - attach_photo_btn = QPushButton('Прикрепить фото') - attach_photo_btn.clicked.connect(self.attach_photo) - attach_file_btn = QPushButton('Прикрепить файл') - attach_file_btn.clicked.connect(self.attach_file) - - file_layout.addWidget(attach_photo_btn) - file_layout.addWidget(attach_file_btn) - control_layout.addLayout(file_layout) - - # Создание отчета - report_btn = QPushButton('Создать отчет о выполненной работе') - report_btn.clicked.connect(self.create_report) - control_layout.addWidget(report_btn) - - # История заявки - history_btn = QPushButton('Просмотреть историю заявки') - history_btn.clicked.connect(self.show_request_history) - control_layout.addWidget(history_btn) - - layout.addWidget(control_group) - - self.main_tabs.addTab(requests_tab, 'Мои заявки') - - # Вкладка заказанных запчастей - parts_tab = QWidget() - layout = QVBoxLayout(parts_tab) - - self.parts_table = QTableWidget() - self.parts_table.setColumnCount(8) - self.parts_table.setHorizontalHeaderLabels([ - 'ID', 'Заявка', 'Запчасть', 'Номер', 'Кол-во', 'Статус', 'Дата заказа', 'Поставщик' - ]) - layout.addWidget(self.parts_table) - - self.main_tabs.addTab(parts_tab, 'Заказанные запчасти') - - self.load_master_requests() - self.load_master_parts() - - def setup_admin_interface(self): - # Вкладка управления пользователями - users_tab = QWidget() - layout = QVBoxLayout(users_tab) - - self.users_table = QTableWidget() - self.users_table.setColumnCount(6) - self.users_table.setHorizontalHeaderLabels(['ID', 'Логин', 'ФИО', 'Роль', 'Телефон', 'Email']) - 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, 'Пользователи') - - # Вкладка всех заявок - requests_tab = QWidget() - layout = QVBoxLayout(requests_tab) - - self.admin_requests_table = QTableWidget() - self.admin_requests_table.setColumnCount(11) - self.admin_requests_table.setHorizontalHeaderLabels([ - 'ID', 'Клиент', 'Оборудование', 'Модель', 'Статус', 'Приоритет', 'Тип', 'Дата', 'Оператор', 'Мастер', 'Помечена на удаление' - ]) - layout.addWidget(self.admin_requests_table) - - delete_btn = QPushButton('Удалить выбранные заявки') - delete_btn.clicked.connect(self.delete_marked_requests) - layout.addWidget(delete_btn) - - self.main_tabs.addTab(requests_tab, 'Все заявки') - - # Вкладка распределения заявок - distribution_tab = QWidget() - layout = QVBoxLayout(distribution_tab) - - self.distribution_table = QTableWidget() - self.distribution_table.setColumnCount(9) - self.distribution_table.setHorizontalHeaderLabels([ - 'ID', 'Клиент', 'Оборудование', 'Статус', 'Оператор', 'Мастер', 'Группа наблюдателей', 'Срок исполнения', 'Приоритет' - ]) - layout.addWidget(self.distribution_table) - - self.main_tabs.addTab(distribution_tab, 'Распределение заявок') - - # Вкладка МТР (материально-технических ресурсов) - materials_tab = QWidget() - layout = QVBoxLayout(materials_tab) - - # Панель управления МТР - mtr_control_layout = QHBoxLayout() - consolidate_btn = QPushButton('Консолидировать потребности в МТР') - consolidate_btn.clicked.connect(self.consolidate_material_needs) - generate_report_btn = QPushButton('Сформировать отчет по МТР') - generate_report_btn.clicked.connect(self.generate_mtr_report) - - mtr_control_layout.addWidget(consolidate_btn) - mtr_control_layout.addWidget(generate_report_btn) - layout.addLayout(mtr_control_layout) - - self.materials_table = QTableWidget() - self.materials_table.setColumnCount(10) - self.materials_table.setHorizontalHeaderLabels([ - 'ID', 'Заявка', 'Материал', 'Тип', 'Кол-во', 'Ед.изм', 'Срочность', 'Статус', 'Примерная стоимость', 'Примечания' - ]) - layout.addWidget(self.materials_table) - - self.main_tabs.addTab(materials_tab, 'МТР') - - # Вкладка архива - archive_tab = QWidget() - layout = QVBoxLayout(archive_tab) - - search_layout = QHBoxLayout() - self.admin_archive_search = QLineEdit() - self.admin_archive_search.setPlaceholderText('Поиск в архиве...') - admin_search_btn = QPushButton('Найти') - admin_search_btn.clicked.connect(self.search_admin_archive) - - search_layout.addWidget(self.admin_archive_search) - search_layout.addWidget(admin_search_btn) - layout.addLayout(search_layout) - - self.admin_archive_table = QTableWidget() - self.admin_archive_table.setColumnCount(10) - self.admin_archive_table.setHorizontalHeaderLabels([ - 'ID', 'Клиент', 'Оборудование', 'Модель', 'Статус', 'Дата создания', 'Дата завершения', 'Мастер', 'Тип', 'Стоимость' - ]) - layout.addWidget(self.admin_archive_table) - - self.main_tabs.addTab(archive_tab, 'Архив') - - self.load_users() - self.load_admin_requests() - self.load_distribution() - self.load_materials() - self.load_admin_archive() - - def register_service_request(self): - cursor = self.db.conn.cursor() - cursor.execute(''' - INSERT INTO service_requests - (client_name, client_phone, client_email, equipment_type, equipment_model, serial_number, problem_description, assigned_operator) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ''', ( - self.op_client_name.text(), - self.op_client_phone.text(), - self.op_client_email.text(), - self.op_equipment_type.currentText(), - self.op_equipment_model.text(), - self.op_serial_number.text(), - self.op_problem_description.toPlainText(), - self.current_user['full_name'] - )) - - self.db.conn.commit() - - # Запись в историю - request_id = cursor.lastrowid - cursor.execute(''' - INSERT INTO request_history (request_id, user_id, action, details) - VALUES (?, ?, ?, ?) - ''', (request_id, self.current_user['id'], 'Создание заявки', 'Заявка зарегистрирована оператором')) - - self.db.conn.commit() - - QMessageBox.information(self, 'Успех', 'Заявка успешно зарегистрирована!') - - # Очищаем форму - self.op_client_name.clear() - self.op_client_phone.clear() - self.op_client_email.clear() - self.op_equipment_model.clear() - self.op_serial_number.clear() - self.op_problem_description.clear() - - def load_operator_requests(self): - cursor = self.db.conn.cursor() - - status_filter = self.status_filter.currentText() - priority_filter = self.priority_filter.currentText() - - query = ''' - SELECT id, client_name, equipment_type, equipment_model, status, priority, request_type, - created_date, assigned_operator, marked_for_deletion - FROM service_requests - WHERE 1=1 - ''' - params = [] - - if status_filter != 'Все': - query += ' AND status = ?' - params.append(status_filter) - - if priority_filter != 'Все': - query += ' AND priority = ?' - params.append(priority_filter) - - query += ' ORDER BY created_date DESC' - - cursor.execute(query, params) - requests = cursor.fetchall() - - self.operator_requests_table.setRowCount(len(requests)) - for row, request in enumerate(requests): - for col, value in enumerate(request): - item = QTableWidgetItem(str(value) if value is not None else '') - - # Помечаем заявки на удаление - if col == 9 and value == 1: - item.setBackground(Qt.GlobalColor.yellow) - - self.operator_requests_table.setItem(row, col, item) - - def load_master_requests(self): - cursor = self.db.conn.cursor() - cursor.execute(''' - SELECT id, client_name, equipment_type, equipment_model, status, priority, created_date, deadline - 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): - item = QTableWidgetItem(str(value) if value is not None else '') - - # Подсвечиваем просроченные заявки - if col == 7 and value: - deadline = QDate.fromString(value, 'yyyy-MM-dd') - if deadline < QDate.currentDate(): - item.setBackground(Qt.GlobalColor.red) - - self.master_requests_table.setItem(row, col, item) - - def load_master_parts(self): - cursor = self.db.conn.cursor() - cursor.execute(''' - SELECT po.id, sr.id, po.part_name, po.part_number, po.quantity, po.status, po.order_date, po.supplier - FROM part_orders po - JOIN service_requests sr ON po.request_id = sr.id - WHERE sr.assigned_master = ? OR ? = 'admin1' - ORDER BY po.order_date DESC - ''', (self.current_user['full_name'], self.current_user['username'])) - - parts = cursor.fetchall() - - self.parts_table.setRowCount(len(parts)) - for row, part in enumerate(parts): - for col, value in enumerate(part): - self.parts_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else '')) - - def load_admin_requests(self): - cursor = self.db.conn.cursor() - cursor.execute(''' - SELECT id, client_name, equipment_type, equipment_model, status, priority, request_type, - created_date, assigned_operator, assigned_master, marked_for_deletion - FROM service_requests - ORDER BY created_date DESC - ''') - - requests = cursor.fetchall() - - self.admin_requests_table.setRowCount(len(requests)) - for row, request in enumerate(requests): - for col, value in enumerate(request): - item = QTableWidgetItem(str(value) if value is not None else '') - - # Помечаем заявки на удаление - if col == 10 and value == 1: - item.setBackground(Qt.GlobalColor.yellow) - - self.admin_requests_table.setItem(row, col, item) - - def load_distribution(self): - cursor = self.db.conn.cursor() - cursor.execute(''' - SELECT id, client_name, equipment_type, status, assigned_operator, assigned_master, - observer_group, deadline, priority - FROM service_requests - WHERE status != 'Выполнена' AND status != 'Отменена' - ORDER BY priority, created_date - ''') - - distributions = cursor.fetchall() - - self.distribution_table.setRowCount(len(distributions)) - for row, dist in enumerate(distributions): - for col, value in enumerate(dist): - item = QTableWidgetItem(str(value) if value is not None else '') - self.distribution_table.setItem(row, col, item) - - def load_materials(self): - cursor = self.db.conn.cursor() - cursor.execute(''' - SELECT mn.id, sr.id, mn.material_name, mn.material_type, mn.quantity, mn.unit, - mn.urgency, mn.status, mn.estimated_cost, mn.notes - FROM material_needs mn - JOIN service_requests sr ON mn.request_id = sr.id - ORDER BY mn.urgency DESC, mn.created_date - ''') - - materials = cursor.fetchall() - - self.materials_table.setRowCount(len(materials)) - for row, material in enumerate(materials): - for col, value in enumerate(material): - self.materials_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else '')) - - 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, request_type - 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_admin_archive(self): - cursor = self.db.conn.cursor() - cursor.execute(''' - SELECT sr.id, sr.client_name, sr.equipment_type, sr.equipment_model, sr.status, - sr.created_date, sr.completed_date, sr.assigned_master, sr.request_type, - COALESCE(r.total_cost, 0) - FROM service_requests sr - LEFT JOIN reports r ON sr.id = r.request_id - WHERE sr.status = 'Выполнена' - ORDER BY sr.completed_date DESC - ''') - - requests = cursor.fetchall() - - self.admin_archive_table.setRowCount(len(requests)) - for row, request in enumerate(requests): - for col, value in enumerate(request): - self.admin_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, phone, email 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 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 - )) - - # Запись в историю - cursor.execute(''' - INSERT INTO request_history (request_id, user_id, action, details) - VALUES (?, ?, ?, ?) - ''', (request_id, self.current_user['id'], 'Изменение заявки', - f'Оператор изменил статус на {self.status_combo.currentText()}, приоритет на {self.priority_combo.currentText()}')) - - self.db.conn.commit() - self.load_operator_requests() - QMessageBox.information(self, 'Успех', 'Заявка обновлена!') - - def mark_request_for_deletion(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 marked_for_deletion = 1 - WHERE id = ? - ''', (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 - )) - - # Запись в историю - cursor.execute(''' - INSERT INTO request_history (request_id, user_id, action, details) - VALUES (?, ?, ?, ?) - ''', (request_id, self.current_user['id'], 'Изменение статуса', - f'Мастер изменил статус на {self.master_status_combo.currentText()}')) - - 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, part_number, quantity, supplier, estimated_cost, ordered_by) - VALUES (?, ?, ?, ?, ?, ?, ?) - ''', ( - request_id, - self.part_name.text(), - self.part_number.text(), - int(self.part_quantity.text()), - self.part_supplier.text(), - float(self.part_cost.text() or 0), - self.current_user['id'] - )) - - self.db.conn.commit() - - # Обновляем статус заявки - cursor.execute(''' - UPDATE service_requests - SET status = 'Ожидает запчасти' - WHERE id = ? - ''', (request_id,)) - - # Запись в историю - cursor.execute(''' - INSERT INTO request_history (request_id, user_id, action, details) - VALUES (?, ?, ?, ?) - ''', (request_id, self.current_user['id'], 'Заказ запчастей', - f'Заказана запчасть: {self.part_name.text()}, количество: {self.part_quantity.text()}')) - - self.db.conn.commit() - - # Очищаем форму - self.part_name.clear() - self.part_number.clear() - self.part_quantity.setText('1') - self.part_supplier.clear() - self.part_cost.clear() - - self.load_master_requests() - self.load_master_parts() - - QMessageBox.information(self, 'Успех', 'Запчасти заказаны!') - - def attach_photo(self): - self.attach_file(file_type='photo') - - def attach_file(self, file_type='document'): - 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, file_type, uploaded_by) - VALUES (?, ?, ?, ?, ?) - ''', ( - request_id, - filename, - file_path, - file_type, - self.current_user['id'] - )) - - # Запись в историю - cursor.execute(''' - INSERT INTO request_history (request_id, user_id, action, details) - VALUES (?, ?, ?, ?) - ''', (request_id, self.current_user['id'], 'Прикрепление файла', - f'Прикреплен файл: {filename}')) - - 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) - report_dialog.resize(500, 400) - layout = QVBoxLayout(report_dialog) - - form_layout = QFormLayout() - work_description = QTextEdit() - parts_used = QLineEdit() - time_spent = QLineEdit() - labor_cost = QLineEdit() - parts_cost = QLineEdit() - - form_layout.addRow('Описание выполненной работы:', work_description) - form_layout.addRow('Использованные запчасти:', parts_used) - form_layout.addRow('Затраченное время (часы):', time_spent) - form_layout.addRow('Стоимость работы:', labor_cost) - form_layout.addRow('Стоимость запчастей:', parts_cost) - - 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(): - total = float(labor_cost.text() or 0) + float(parts_cost.text() or 0) - - cursor = self.db.conn.cursor() - cursor.execute(''' - INSERT INTO reports (request_id, master_id, work_description, parts_used, time_spent, labor_cost, parts_cost, total_cost) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ''', ( - request_id, - self.current_user['id'], - work_description.toPlainText(), - parts_used.text(), - float(time_spent.text() or 0), - float(labor_cost.text() or 0), - float(parts_cost.text() or 0), - total - )) - - # Обновляем статус заявки на выполненную - cursor.execute(''' - UPDATE service_requests - SET status = 'Выполнена', completed_date = CURRENT_TIMESTAMP - WHERE id = ? - ''', (request_id,)) - - # Запись в историю - cursor.execute(''' - INSERT INTO request_history (request_id, user_id, action, details) - VALUES (?, ?, ?, ?) - ''', (request_id, self.current_user['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 show_request_history(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(''' - SELECT h.timestamp, u.full_name, h.action, h.details - FROM request_history h - JOIN users u ON h.user_id = u.id - WHERE h.request_id = ? - ORDER BY h.timestamp DESC - ''', (request_id,)) - - history = cursor.fetchall() - - history_dialog = QDialog(self) - history_dialog.setWindowTitle(f'История заявки #{request_id}') - history_dialog.setModal(True) - history_dialog.resize(600, 400) - layout = QVBoxLayout(history_dialog) - - history_table = QTableWidget() - history_table.setColumnCount(4) - history_table.setHorizontalHeaderLabels(['Дата', 'Пользователь', 'Действие', 'Детали']) - history_table.setRowCount(len(history)) - - for row, record in enumerate(history): - for col, value in enumerate(record): - history_table.setItem(row, col, QTableWidgetItem(str(value))) - - layout.addWidget(history_table) - - close_btn = QPushButton('Закрыть') - close_btn.clicked.connect(history_dialog.accept) - layout.addWidget(close_btn) - - history_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, request_type - FROM service_requests - WHERE status = 'Выполнена' AND - (client_name LIKE ? OR equipment_type LIKE ? OR equipment_model LIKE ? OR assigned_master LIKE ?) - ORDER BY completed_date DESC - ''', (f'%{search_text}%', 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, request_type - 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 search_admin_archive(self): - search_text = self.admin_archive_search.text() - cursor = self.db.conn.cursor() - - if search_text: - cursor.execute(''' - SELECT sr.id, sr.client_name, sr.equipment_type, sr.equipment_model, sr.status, - sr.created_date, sr.completed_date, sr.assigned_master, sr.request_type, - COALESCE(r.total_cost, 0) - FROM service_requests sr - LEFT JOIN reports r ON sr.id = r.request_id - WHERE sr.status = 'Выполнена' AND - (sr.client_name LIKE ? OR sr.equipment_type LIKE ? OR sr.equipment_model LIKE ? OR sr.assigned_master LIKE ?) - ORDER BY sr.completed_date DESC - ''', (f'%{search_text}%', f'%{search_text}%', f'%{search_text}%', f'%{search_text}%')) - else: - cursor.execute(''' - SELECT sr.id, sr.client_name, sr.equipment_type, sr.equipment_model, sr.status, - sr.created_date, sr.completed_date, sr.assigned_master, sr.request_type, - COALESCE(r.total_cost, 0) - FROM service_requests sr - LEFT JOIN reports r ON sr.id = r.request_id - WHERE sr.status = 'Выполнена' - ORDER BY sr.completed_date DESC - ''') - - requests = cursor.fetchall() - - self.admin_archive_table.setRowCount(len(requests)) - for row, request in enumerate(requests): - for col, value in enumerate(request): - self.admin_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() - email = QLineEdit() - role = QComboBox() - role.addItems(['operator', 'master', 'admin']) - - form_layout.addRow('Логин:', username) - form_layout.addRow('Пароль:', password) - form_layout.addRow('ФИО:', full_name) - form_layout.addRow('Телефон:', phone) - form_layout.addRow('Email:', email) - 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, email) - VALUES (?, ?, ?, ?, ?, ?) - ''', ( - username.text(), - password.text(), - role.currentText(), - full_name.text(), - phone.text(), - email.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() - - def delete_marked_requests(self): - cursor = self.db.conn.cursor() - cursor.execute('SELECT COUNT(*) FROM service_requests WHERE marked_for_deletion = 1') - count = cursor.fetchone()[0] - - if count == 0: - QMessageBox.information(self, 'Информация', 'Нет заявок, помеченных на удаление') - return - - reply = QMessageBox.question(self, 'Подтверждение', - f'Вы уверены, что хотите удалить {count} заявок, помеченных на удаление?', - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) - - if reply == QMessageBox.StandardButton.Yes: - cursor.execute('DELETE FROM service_requests WHERE marked_for_deletion = 1') - self.db.conn.commit() - self.load_admin_requests() - QMessageBox.information(self, 'Успех', f'Удалено {count} заявок') - - def consolidate_material_needs(self): - cursor = self.db.conn.cursor() - - # Создаем консолидированный список потребностей в МТР - cursor.execute(''' - SELECT material_name, material_type, SUM(quantity), unit, urgency, SUM(estimated_cost) - FROM material_needs - WHERE status = 'Требуется' - GROUP BY material_name, material_type, unit, urgency - ORDER BY urgency, material_type, material_name - ''') - - consolidated = cursor.fetchall() - - dialog = QDialog(self) - dialog.setWindowTitle('Консолидированные потребности в МТР') - dialog.setModal(True) - dialog.resize(700, 500) - layout = QVBoxLayout(dialog) - - table = QTableWidget() - table.setColumnCount(6) - table.setHorizontalHeaderLabels(['Материал', 'Тип', 'Количество', 'Ед.изм', 'Срочность', 'Общая стоимость']) - table.setRowCount(len(consolidated)) - - for row, record in enumerate(consolidated): - for col, value in enumerate(record): - table.setItem(row, col, QTableWidgetItem(str(value))) - - layout.addWidget(table) - - close_btn = QPushButton('Закрыть') - close_btn.clicked.connect(dialog.accept) - layout.addWidget(close_btn) - - dialog.exec() - - def generate_mtr_report(self): - cursor = self.db.conn.cursor() - - # Формируем отчет по МТР - cursor.execute(''' - SELECT - mn.material_name, - mn.material_type, - SUM(mn.quantity) as total_quantity, - mn.unit, - mn.urgency, - COUNT(DISTINCT mn.request_id) as request_count, - SUM(mn.estimated_cost) as total_cost - FROM material_needs mn - WHERE mn.status = 'Требуется' - GROUP BY mn.material_name, mn.material_type, mn.unit, mn.urgency - ORDER BY mn.urgency DESC, total_cost DESC - ''') - - report_data = cursor.fetchall() - - dialog = QDialog(self) - dialog.setWindowTitle('Отчет по материально-техническим ресурсам') - dialog.setModal(True) - dialog.resize(800, 600) - layout = QVBoxLayout(dialog) - - layout.addWidget(QLabel('Отчет по потребностям в МТР')) - - table = QTableWidget() - table.setColumnCount(7) - table.setHorizontalHeaderLabels(['Материал', 'Тип', 'Общее кол-во', 'Ед.изм', 'Срочность', 'Кол-во заявок', 'Общая стоимость']) - table.setRowCount(len(report_data)) - - total_cost = 0 - for row, record in enumerate(report_data): - for col, value in enumerate(record): - table.setItem(row, col, QTableWidgetItem(str(value))) - total_cost += float(record[6] or 0) - - layout.addWidget(table) - layout.addWidget(QLabel(f'Общая стоимость всех МТР: {total_cost:.2f} руб.')) - - close_btn = QPushButton('Закрыть') - close_btn.clicked.connect(dialog.accept) - layout.addWidget(close_btn) - - dialog.exec() - -if __name__ == '__main__': - app = QApplication(sys.argv) - window = ServiceRequestAppV2() - window.show() - sys.exit(app.exec()) diff --git a/control2-2.py b/control2-2.py deleted file mode 100644 index e68aa72..0000000 --- a/control2-2.py +++ /dev/null @@ -1,902 +0,0 @@ -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()) diff --git a/control2.py b/control2.py index 49c25e4..d55c171 100644 --- a/control2.py +++ b/control2.py @@ -5,10 +5,10 @@ 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, QDialog, - QDialogButtonBox) -from PyQt6.QtCore import Qt, QDate, QTime + 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): @@ -299,11 +299,9 @@ class FitnessApp(QMainWindow): stats_layout.addLayout(stats_form) - # Правая часть - таблица для статистики - self.stats_table = QTableWidget() - self.stats_table.setColumnCount(2) - self.stats_table.setHorizontalHeaderLabels(['Зона', 'Количество посещений']) - stats_layout.addWidget(self.stats_table) + # Правая часть - диаграмма + self.stats_chart = QChartView() + stats_layout.addWidget(self.stats_chart) stats_group.setLayout(stats_layout) layout.addWidget(stats_group) @@ -408,11 +406,9 @@ class FitnessApp(QMainWindow): overall_layout.addLayout(metrics_layout) - # Правая часть - таблица доходов по типам абонементов - self.revenue_table = QTableWidget() - self.revenue_table.setColumnCount(2) - self.revenue_table.setHorizontalHeaderLabels(['Тип абонемента', 'Доход']) - overall_layout.addWidget(self.revenue_table) + # Правая часть - диаграмма доходов + self.revenue_chart = QChartView() + overall_layout.addWidget(self.revenue_chart) overall_stats_group.setLayout(overall_layout) layout.addWidget(overall_stats_group) @@ -530,7 +526,7 @@ class FitnessApp(QMainWindow): 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 @@ -539,10 +535,17 @@ class FitnessApp(QMainWindow): """) revenue_data = self.cursor.fetchall() - self.revenue_table.setRowCount(len(revenue_data)) - for row, (membership_type, revenue) in enumerate(revenue_data): - self.revenue_table.setItem(row, 0, QTableWidgetItem(membership_type)) - self.revenue_table.setItem(row, 1, QTableWidgetItem(f"{revenue:.2f} руб.")) + 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(""" @@ -595,21 +598,40 @@ class FitnessApp(QMainWindow): """, (start_date, end_date)) zone_stats = self.cursor.fetchall() - self.stats_table.setRowCount(len(zone_stats)) - for row, (zone, count) in enumerate(zone_stats): - self.stats_table.setItem(row, 0, QTableWidgetItem(zone)) - self.stats_table.setItem(row, 1, QTableWidgetItem(str(count))) + 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(QDialog): +class AddClassDialog(QMessageBox): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Добавить групповое занятие") self.setModal(True) - layout = QVBoxLayout() - - form_layout = QFormLayout() - self.class_name = QLineEdit() self.trainer = QComboBox() self.class_date = QDateEdit() @@ -631,23 +653,21 @@ class AddClassDialog(QDialog): self.hall.addItems(['Зал 1', 'Зал 2', 'Зал 3', 'Бассейн']) - form_layout.addRow("Название:", self.class_name) - form_layout.addRow("Тренер:", self.trainer) - form_layout.addRow("Дата:", self.class_date) - form_layout.addRow("Время начала:", self.start_time) - form_layout.addRow("Время окончания:", self.end_time) - form_layout.addRow("Зал:", self.hall) - form_layout.addRow("Макс. участников:", self.max_participants) + 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) - layout.addLayout(form_layout) + widget = QWidget() + widget.setLayout(layout) + self.layout().addWidget(widget, 0, 0, 1, self.layout().columnCount()) - # Кнопки - button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) - button_box.accepted.connect(self.accept) - button_box.rejected.connect(self.reject) - layout.addWidget(button_box) - - self.setLayout(layout) + self.addButton(QPushButton("Добавить"), QMessageBox.ButtonRole.AcceptRole) + self.addButton(QPushButton("Отмена"), QMessageBox.ButtonRole.RejectRole) def get_data(self): """Получение данных из формы""" diff --git a/control2.py.bak b/control2.py.bak deleted file mode 100644 index d55c171..0000000 --- a/control2.py.bak +++ /dev/null @@ -1,693 +0,0 @@ -import sys -import sqlite3 -from datetime import datetime, date -from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, - QHBoxLayout, QTabWidget, QTableWidget, QTableWidgetItem, - QPushButton, QLabel, QLineEdit, QComboBox, QDateEdit, - QTextEdit, QMessageBox, QHeaderView, QGroupBox, - QFormLayout, QSpinBox, QCheckBox, QTimeEdit) -from PyQt6.QtCore import Qt, QDate -from PyQt6.QtGui import QFont, QPalette, QColor -from PyQt6.QtCharts import QChart, QChartView, QPieSeries, QBarSeries, QBarSet, QBarCategoryAxis, QValueAxis - -class FitnessApp(QMainWindow): - def __init__(self): - super().__init__() - self.initDB() - self.initUI() - - def initDB(self): - """Инициализация базы данных""" - self.conn = sqlite3.connect('fitness.db') - self.cursor = self.conn.cursor() - - # Создание таблиц - self.create_tables() - # Заполнение тестовыми данными - self.insert_sample_data() - - def create_tables(self): - """Создание таблиц базы данных""" - tables = [ - """ - CREATE TABLE IF NOT EXISTS Users ( - userID INTEGER PRIMARY KEY, - fio TEXT NOT NULL, - phone TEXT, - email TEXT, - login TEXT UNIQUE, - password TEXT, - userType TEXT, - specialization TEXT, - birthDate DATE - ) - """, - """ - CREATE TABLE IF NOT EXISTS Memberships ( - membershipID INTEGER PRIMARY KEY, - clientID INTEGER, - membershipType TEXT, - startDate DATE, - endDate DATE, - visitsTotal INTEGER, - visitsUsed INTEGER, - zones TEXT, - membershipStatus TEXT, - cost REAL, - adminID INTEGER, - FOREIGN KEY (clientID) REFERENCES Users(userID), - FOREIGN KEY (adminID) REFERENCES Users(userID) - ) - """, - """ - CREATE TABLE IF NOT EXISTS Visits ( - visitID INTEGER PRIMARY KEY, - clientID INTEGER, - visitDate DATE, - checkInTime TIME, - checkOutTime TIME, - zone TEXT, - membershipID INTEGER, - FOREIGN KEY (clientID) REFERENCES Users(userID), - FOREIGN KEY (membershipID) REFERENCES Memberships(membershipID) - ) - """, - """ - CREATE TABLE IF NOT EXISTS GroupClasses ( - classID INTEGER PRIMARY KEY, - className TEXT, - trainerID INTEGER, - classDate DATE, - startTime TIME, - endTime TIME, - hall TEXT, - maxParticipants INTEGER, - enrolledParticipants INTEGER, - classStatus TEXT, - FOREIGN KEY (trainerID) REFERENCES Users(userID) - ) - """, - """ - CREATE TABLE IF NOT EXISTS PersonalTraining ( - trainingID INTEGER PRIMARY KEY, - clientID INTEGER, - trainerID INTEGER, - trainingDate DATE, - startTime TIME, - endTime TIME, - exercises TEXT, - notes TEXT, - progressMetrics TEXT, - FOREIGN KEY (clientID) REFERENCES Users(userID), - FOREIGN KEY (trainerID) REFERENCES Users(userID) - ) - """, - """ - CREATE TABLE IF NOT EXISTS ClassRegistrations ( - registrationID INTEGER PRIMARY KEY, - classID INTEGER, - clientID INTEGER, - registrationDate DATE, - status TEXT, - FOREIGN KEY (classID) REFERENCES GroupClasses(classID), - FOREIGN KEY (clientID) REFERENCES Users(userID) - ) - """, - """ - CREATE TABLE IF NOT EXISTS EquipmentRequests ( - requestID INTEGER PRIMARY KEY, - trainerID INTEGER, - equipment TEXT, - quantity INTEGER, - requestDate DATE, - status TEXT, - FOREIGN KEY (trainerID) REFERENCES Users(userID) - ) - """, - """ - CREATE TABLE IF NOT EXISTS ShiftSwaps ( - swapID INTEGER PRIMARY KEY, - trainerID1 INTEGER, - trainerID2 INTEGER, - shiftDate DATE, - status TEXT, - FOREIGN KEY (trainerID1) REFERENCES Users(userID), - FOREIGN KEY (trainerID2) REFERENCES Users(userID) - ) - """ - ] - - for table in tables: - self.cursor.execute(table) - self.conn.commit() - - def insert_sample_data(self): - """Вставка тестовых данных""" - # Проверяем, есть ли уже данные - self.cursor.execute("SELECT COUNT(*) FROM Users") - if self.cursor.fetchone()[0] > 0: - return - - users = [ - (1, 'Сидорова Марина Петровна', '89219014567', 'director@fitness.ru', 'director1', 'pass1', 'Директор', '', '1980-05-15'), - (2, 'Романова Анна Сергеевна', '89210125678', 'admin1@fitness.ru', 'admin1', 'pass2', 'Администратор', '', '1992-08-22'), - (4, 'Яковлева Елена Викторовна', '89211236789', 'admin2@fitness.ru', 'admin2', 'pass3', 'Администратор', '', '1988-11-10'), - (7, 'Петров Дмитрий Александрович', '89212347890', 'petrov@fitness.ru', 'trainer1', 'pass4', 'Тренер', 'Силовые тренировки', '1985-03-18'), - (9, 'Смирнова Ольга Игоревна', '89213458901', 'smirnova@fitness.ru', 'trainer2', 'pass5', 'Тренер', 'Йога, Пилатес', '1990-07-25'), - (11, 'Козлов Сергей Николаевич', '89214569012', 'kozlov@fitness.ru', 'trainer3', 'pass6', 'Тренер', 'Плавание', '1987-12-05'), - (16, 'Федорова Екатерина Дмитриевна', '89161112236', 'fedorova@mail.ru', 'client1', 'pass7', 'Клиент', '', '1995-04-12'), - (21, 'Михайлов Алексей Владимирович', '89162223347', 'mikhailov@gmail.com', 'client2', 'pass8', 'Клиент', '', '1988-09-30'), - (26, 'Новикова Ирина Сергеевна', '89163334458', 'novikova@yandex.ru', 'client3', 'pass9', 'Клиент', '', '1992-06-18'), - (30, 'Соколов Игорь Петрович', '89164445569', 'sokolov@mail.ru', 'client4', 'pass10', 'Клиент', '', '1983-02-28'), - (34, 'Павлова Мария Александровна', '89165556670', 'pavlova@gmail.com', 'client5', 'pass11', 'Клиент', '', '1997-11-07') - ] - - memberships = [ - (1, 16, 'Месячный безлимит', '2024-06-01', '2024-06-30', 999, 42, 'Зал, Бассейн, Групповые', 'Активен', 5000.00, 2), - (2, 21, '12 посещений', '2024-06-05', '2024-09-05', 12, 8, 'Зал', 'Активен', 4000.00, 2), - (3, 26, 'Годовой VIP', '2024-01-10', '2025-01-10', 999, 156, 'Все зоны', 'Активен', 45000.00, 4), - (4, 30, 'Разовое посещение', '2024-06-15', '2024-06-15', 1, 1, 'Зал', 'Завершен', 500.00, 2), - (5, 34, 'Квартальный', '2024-06-01', '2024-08-31', 999, 15, 'Зал, Групповые', 'Активен', 12000.00, 4) - ] - - visits = [ - (1, 16, '2024-06-15', '08:30', '10:15', 'Тренажерный зал', 1), - (2, 21, '2024-06-15', '09:00', '10:30', 'Тренажерный зал', 2), - (3, 26, '2024-06-15', '07:00', '08:30', 'Бассейн', 3), - (4, 16, '2024-06-15', '18:00', '19:45', 'Групповое занятие', 1), - (5, 34, '2024-06-15', '19:00', '20:30', 'Групповое занятие', 5) - ] - - group_classes = [ - (1, 'Йога для начинающих', 9, '2024-06-16', '10:00', '11:00', 'Зал 2', 15, 12, 'Запланировано'), - (2, 'Силовая аэробика', 7, '2024-06-16', '18:00', '19:00', 'Зал 1', 20, 18, 'Запланировано'), - (3, 'Пилатес', 9, '2024-06-17', '11:00', '12:00', 'Зал 2', 12, 12, 'Группа заполнена'), - (4, 'Аквааэробика', 11, '2024-06-17', '15:00', '16:00', 'Бассейн', 10, 7, 'Запланировано'), - (5, 'Бокс', 7, '2024-06-18', '19:00', '20:30', 'Зал 3', 8, 5, 'Запланировано') - ] - - personal_training = [ - (1, 26, 7, '2024-06-14', '16:00', '17:00', 'Жим лежа, Приседания, Тяга блока', 'Хорошая техника', 'Жим 80кг x 8'), - (2, 16, 9, '2024-06-13', '10:00', '11:00', 'Асаны йоги, Растяжка', 'Улучшилась гибкость', ''), - (3, 21, 7, '2024-06-12', '14:00', '15:00', 'Становая тяга, Жим гантелей', 'Нужно работать над техникой', 'Становая 60кг x 6') - ] - - # Вставка данных - self.cursor.executemany("INSERT INTO Users VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", users) - self.cursor.executemany("INSERT INTO Memberships VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", memberships) - self.cursor.executemany("INSERT INTO Visits VALUES (?, ?, ?, ?, ?, ?, ?)", visits) - self.cursor.executemany("INSERT INTO GroupClasses VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", group_classes) - self.cursor.executemany("INSERT INTO PersonalTraining VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", personal_training) - self.conn.commit() - - def initUI(self): - """Инициализация пользовательского интерфейса""" - self.setWindowTitle('Фитнес-клуб - Система управления') - self.setGeometry(100, 100, 1200, 800) - - # Центральный виджет с вкладками для разных ролей - self.tabs = QTabWidget() - - # Вкладка для администратора - self.admin_tab = QWidget() - self.init_admin_tab() - self.tabs.addTab(self.admin_tab, "Администратор") - - # Вкладка для тренера - self.trainer_tab = QWidget() - self.init_trainer_tab() - self.tabs.addTab(self.trainer_tab, "Тренер") - - # Вкладка для директора - self.director_tab = QWidget() - self.init_director_tab() - self.tabs.addTab(self.director_tab, "Директор") - - self.setCentralWidget(self.tabs) - - def init_admin_tab(self): - """Инициализация вкладки администратора""" - layout = QVBoxLayout() - - # Группа управления расписанием - schedule_group = QGroupBox("Управление расписанием групповых занятий") - schedule_layout = QVBoxLayout() - - # Таблица групповых занятий - self.classes_table = QTableWidget() - self.classes_table.setColumnCount(10) - self.classes_table.setHorizontalHeaderLabels([ - 'ID', 'Название', 'Тренер', 'Дата', 'Время начала', - 'Время окончания', 'Зал', 'Макс. участников', 'Записано', 'Статус' - ]) - schedule_layout.addWidget(self.classes_table) - - # Кнопки управления - btn_layout = QHBoxLayout() - self.add_class_btn = QPushButton("Добавить занятие") - self.edit_class_btn = QPushButton("Редактировать") - self.delete_class_btn = QPushButton("Удалить") - - btn_layout.addWidget(self.add_class_btn) - btn_layout.addWidget(self.edit_class_btn) - btn_layout.addWidget(self.delete_class_btn) - - schedule_layout.addLayout(btn_layout) - schedule_group.setLayout(schedule_layout) - layout.addWidget(schedule_group) - - # Группа управления абонементами - membership_group = QGroupBox("Управление абонементами") - membership_layout = QVBoxLayout() - - self.memberships_table = QTableWidget() - self.memberships_table.setColumnCount(8) - self.memberships_table.setHorizontalHeaderLabels([ - 'ID', 'Клиент', 'Тип', 'Начало', 'Окончание', - 'Использовано/Всего', 'Статус', 'Стоимость' - ]) - membership_layout.addWidget(self.memberships_table) - - # Кнопки для управления абонементами - membership_btn_layout = QHBoxLayout() - self.change_price_btn = QPushButton("Изменить стоимость") - self.freeze_membership_btn = QPushButton("Заморозить абонемент") - - membership_btn_layout.addWidget(self.change_price_btn) - membership_btn_layout.addWidget(self.freeze_membership_btn) - membership_layout.addLayout(membership_btn_layout) - - membership_group.setLayout(membership_layout) - layout.addWidget(membership_group) - - # Группа статистики - stats_group = QGroupBox("Статистика и отчетность") - stats_layout = QHBoxLayout() - - # Левая часть - форма для фильтров - stats_form = QFormLayout() - self.stats_start_date = QDateEdit() - self.stats_end_date = QDateEdit() - self.stats_start_date.setDate(QDate.currentDate().addMonths(-1)) - self.stats_end_date.setDate(QDate.currentDate()) - - stats_form.addRow("С:", self.stats_start_date) - stats_form.addRow("По:", self.stats_end_date) - - self.generate_stats_btn = QPushButton("Сформировать отчет") - stats_form.addRow(self.generate_stats_btn) - - stats_layout.addLayout(stats_form) - - # Правая часть - диаграмма - self.stats_chart = QChartView() - stats_layout.addWidget(self.stats_chart) - - stats_group.setLayout(stats_layout) - layout.addWidget(stats_group) - - # Загрузка данных - self.load_classes_data() - self.load_memberships_data() - - # Подключение сигналов - self.add_class_btn.clicked.connect(self.add_class) - self.generate_stats_btn.clicked.connect(self.generate_stats) - - self.admin_tab.setLayout(layout) - - def init_trainer_tab(self): - """Инициализация вкладки тренера""" - layout = QVBoxLayout() - - # Группа программ тренировок - programs_group = QGroupBox("Индивидуальные программы тренировок") - programs_layout = QVBoxLayout() - - self.programs_table = QTableWidget() - self.programs_table.setColumnCount(6) - self.programs_table.setHorizontalHeaderLabels([ - 'ID', 'Клиент', 'Дата', 'Упражнения', 'Заметки', 'Прогресс' - ]) - programs_layout.addWidget(self.programs_table) - - programs_btn_layout = QHBoxLayout() - self.add_program_btn = QPushButton("Добавить программу") - self.edit_program_btn = QPushButton("Редактировать") - - programs_btn_layout.addWidget(self.add_program_btn) - programs_btn_layout.addWidget(self.edit_program_btn) - programs_layout.addLayout(programs_btn_layout) - - programs_group.setLayout(programs_layout) - layout.addWidget(programs_group) - - # Группа базы упражнений - exercises_group = QGroupBox("База упражнений") - exercises_layout = QVBoxLayout() - - self.exercises_table = QTableWidget() - self.exercises_table.setColumnCount(3) - self.exercises_table.setHorizontalHeaderLabels(['ID', 'Название', 'Группа мышц']) - exercises_layout.addWidget(self.exercises_table) - - exercises_btn_layout = QHBoxLayout() - self.add_exercise_btn = QPushButton("Добавить упражнение") - exercises_btn_layout.addWidget(self.add_exercise_btn) - exercises_layout.addLayout(exercises_btn_layout) - - exercises_group.setLayout(exercises_layout) - layout.addWidget(exercises_group) - - # Группа запросов оборудования - equipment_group = QGroupBox("Запросы оборудования") - equipment_layout = QVBoxLayout() - - self.equipment_table = QTableWidget() - self.equipment_table.setColumnCount(5) - self.equipment_table.setHorizontalHeaderLabels([ - 'ID', 'Оборудование', 'Количество', 'Дата запроса', 'Статус' - ]) - equipment_layout.addWidget(self.equipment_table) - - equipment_btn_layout = QHBoxLayout() - self.request_equipment_btn = QPushButton("Запросить оборудование") - equipment_btn_layout.addWidget(self.request_equipment_btn) - equipment_layout.addLayout(equipment_btn_layout) - - equipment_group.setLayout(equipment_layout) - layout.addWidget(equipment_group) - - # Загрузка данных - self.load_programs_data() - self.load_equipment_data() - - self.trainer_tab.setLayout(layout) - - def init_director_tab(self): - """Инициализация вкладки директора""" - layout = QVBoxLayout() - - # Общая статистика - overall_stats_group = QGroupBox("Общая статистика клуба") - overall_layout = QHBoxLayout() - - # Левая часть - ключевые показатели - metrics_layout = QFormLayout() - self.total_members_label = QLabel("0") - self.active_members_label = QLabel("0") - self.monthly_revenue_label = QLabel("0 руб.") - self.attendance_rate_label = QLabel("0%") - - metrics_layout.addRow("Всего клиентов:", self.total_members_label) - metrics_layout.addRow("Активных абонементов:", self.active_members_label) - metrics_layout.addRow("Доход за месяц:", self.monthly_revenue_label) - metrics_layout.addRow("Посещаемость:", self.attendance_rate_label) - - overall_layout.addLayout(metrics_layout) - - # Правая часть - диаграмма доходов - self.revenue_chart = QChartView() - overall_layout.addWidget(self.revenue_chart) - - overall_stats_group.setLayout(overall_layout) - layout.addWidget(overall_stats_group) - - # Эффективность тренеров - trainers_group = QGroupBox("Эффективность тренеров") - trainers_layout = QVBoxLayout() - - self.trainers_table = QTableWidget() - self.trainers_table.setColumnCount(6) - self.trainers_table.setHorizontalHeaderLabels([ - 'Тренер', 'Групповые занятия', 'Персональные тренировки', - 'Средняя оценка', 'Доход', 'Бонусы' - ]) - trainers_layout.addWidget(self.trainers_table) - - trainers_group.setLayout(trainers_layout) - layout.addWidget(trainers_group) - - # Финансовые показатели - finance_group = QGroupBox("Финансовые показатели") - finance_layout = QVBoxLayout() - - self.finance_table = QTableWidget() - self.finance_table.setColumnCount(4) - self.finance_table.setHorizontalHeaderLabels([ - 'Период', 'Доход', 'Расходы', 'Прибыль' - ]) - finance_layout.addWidget(self.finance_table) - - finance_group.setLayout(finance_layout) - layout.addWidget(finance_group) - - # Загрузка данных - self.load_director_data() - - self.director_tab.setLayout(layout) - - def load_classes_data(self): - """Загрузка данных о групповых занятиях""" - self.cursor.execute(""" - SELECT gc.classID, gc.className, u.fio, gc.classDate, gc.startTime, - gc.endTime, gc.hall, gc.maxParticipants, gc.enrolledParticipants, gc.classStatus - FROM GroupClasses gc - LEFT JOIN Users u ON gc.trainerID = u.userID - ORDER BY gc.classDate, gc.startTime - """) - classes = self.cursor.fetchall() - - self.classes_table.setRowCount(len(classes)) - for row, class_data in enumerate(classes): - for col, data in enumerate(class_data): - self.classes_table.setItem(row, col, QTableWidgetItem(str(data))) - - def load_memberships_data(self): - """Загрузка данных об абонементах""" - self.cursor.execute(""" - SELECT m.membershipID, u.fio, m.membershipType, m.startDate, m.endDate, - m.visitsUsed || '/' || m.visitsTotal, m.membershipStatus, m.cost - FROM Memberships m - JOIN Users u ON m.clientID = u.userID - ORDER BY m.endDate - """) - memberships = self.cursor.fetchall() - - self.memberships_table.setRowCount(len(memberships)) - for row, membership_data in enumerate(memberships): - for col, data in enumerate(membership_data): - self.memberships_table.setItem(row, col, QTableWidgetItem(str(data))) - - def load_programs_data(self): - """Загрузка данных о программах тренировок""" - self.cursor.execute(""" - SELECT pt.trainingID, u.fio, pt.trainingDate, pt.exercises, pt.notes, pt.progressMetrics - FROM PersonalTraining pt - JOIN Users u ON pt.clientID = u.userID - ORDER BY pt.trainingDate DESC - """) - programs = self.cursor.fetchall() - - self.programs_table.setRowCount(len(programs)) - for row, program_data in enumerate(programs): - for col, data in enumerate(program_data): - self.programs_table.setItem(row, col, QTableWidgetItem(str(data))) - - def load_equipment_data(self): - """Загрузка данных о запросах оборудования""" - self.cursor.execute(""" - SELECT requestID, equipment, quantity, requestDate, status - FROM EquipmentRequests - ORDER BY requestDate DESC - """) - equipment = self.cursor.fetchall() - - self.equipment_table.setRowCount(len(equipment)) - for row, equipment_data in enumerate(equipment): - for col, data in enumerate(equipment_data): - self.equipment_table.setItem(row, col, QTableWidgetItem(str(data))) - - def load_director_data(self): - """Загрузка данных для директора""" - # Общая статистика - self.cursor.execute("SELECT COUNT(*) FROM Users WHERE userType = 'Клиент'") - total_clients = self.cursor.fetchone()[0] - self.total_members_label.setText(str(total_clients)) - - self.cursor.execute("SELECT COUNT(*) FROM Memberships WHERE membershipStatus = 'Активен'") - active_memberships = self.cursor.fetchone()[0] - self.active_members_label.setText(str(active_memberships)) - - self.cursor.execute(""" - SELECT SUM(cost) FROM Memberships - WHERE strftime('%Y-%m', startDate) = strftime('%Y-%m', 'now') - """) - monthly_revenue = self.cursor.fetchone()[0] or 0 - self.monthly_revenue_label.setText(f"{monthly_revenue:.2f} руб.") - - # Диаграмма доходов - self.cursor.execute(""" - SELECT membershipType, SUM(cost) - FROM Memberships - WHERE strftime('%Y', startDate) = strftime('%Y', 'now') - GROUP BY membershipType - """) - revenue_data = self.cursor.fetchall() - - series = QPieSeries() - for membership_type, revenue in revenue_data: - series.append(membership_type, revenue) - - chart = QChart() - chart.addSeries(series) - chart.setTitle("Доходы по типам абонементов") - chart.legend().setVisible(True) - chart.legend().setAlignment(Qt.AlignmentFlag.AlignBottom) - - self.revenue_chart.setChart(chart) - - # Данные по тренерам - self.cursor.execute(""" - SELECT u.fio, - COUNT(DISTINCT gc.classID) as group_classes, - COUNT(DISTINCT pt.trainingID) as personal_trainings, - '4.5' as avg_rating, - SUM(m.cost * 0.1) as revenue, - COUNT(DISTINCT pt.trainingID) * 100 as bonuses - FROM Users u - LEFT JOIN GroupClasses gc ON u.userID = gc.trainerID - LEFT JOIN PersonalTraining pt ON u.userID = pt.trainerID - LEFT JOIN Memberships m ON pt.clientID = m.clientID - WHERE u.userType = 'Тренер' - GROUP BY u.userID, u.fio - """) - trainers = self.cursor.fetchall() - - self.trainers_table.setRowCount(len(trainers)) - for row, trainer_data in enumerate(trainers): - for col, data in enumerate(trainer_data): - self.trainers_table.setItem(row, col, QTableWidgetItem(str(data))) - - def add_class(self): - """Добавление нового группового занятия""" - dialog = AddClassDialog(self) - if dialog.exec(): - class_data = dialog.get_data() - try: - self.cursor.execute(""" - INSERT INTO GroupClasses (className, trainerID, classDate, startTime, endTime, hall, maxParticipants, enrolledParticipants, classStatus) - VALUES (?, ?, ?, ?, ?, ?, ?, 0, 'Запланировано') - """, class_data) - self.conn.commit() - self.load_classes_data() - QMessageBox.information(self, "Успех", "Занятие успешно добавлено!") - except Exception as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось добавить занятие: {str(e)}") - - def generate_stats(self): - """Генерация статистики посещаемости""" - start_date = self.stats_start_date.date().toString("yyyy-MM-dd") - end_date = self.stats_end_date.date().toString("yyyy-MM-dd") - - self.cursor.execute(""" - SELECT zone, COUNT(*) as visits - FROM Visits - WHERE visitDate BETWEEN ? AND ? - GROUP BY zone - """, (start_date, end_date)) - zone_stats = self.cursor.fetchall() - - series = QBarSeries() - bar_set = QBarSet("Посещения по зонам") - - categories = [] - visits = [] - - for zone, count in zone_stats: - categories.append(zone) - visits.append(count) - - bar_set.append(visits) - series.append(bar_set) - - chart = QChart() - chart.addSeries(series) - chart.setTitle(f"Посещаемость по зонам ({start_date} - {end_date})") - - axis_x = QBarCategoryAxis() - axis_x.append(categories) - chart.addAxis(axis_x, Qt.AlignmentFlag.AlignBottom) - series.attachAxis(axis_x) - - axis_y = QValueAxis() - chart.addAxis(axis_y, Qt.AlignmentFlag.AlignLeft) - series.attachAxis(axis_y) - - self.stats_chart.setChart(chart) - -class AddClassDialog(QMessageBox): - def __init__(self, parent=None): - super().__init__(parent) - self.setWindowTitle("Добавить групповое занятие") - self.setModal(True) - - self.class_name = QLineEdit() - self.trainer = QComboBox() - self.class_date = QDateEdit() - self.start_time = QTimeEdit() - self.end_time = QTimeEdit() - self.hall = QComboBox() - self.max_participants = QSpinBox() - - self.class_date.setDate(QDate.currentDate()) - self.start_time.setTime(QTime.currentTime()) - self.end_time.setTime(QTime.currentTime().addSecs(3600)) - self.max_participants.setRange(1, 100) - - # Заполнение комбобоксов - parent.cursor.execute("SELECT userID, fio FROM Users WHERE userType = 'Тренер'") - trainers = parent.cursor.fetchall() - for trainer_id, fio in trainers: - self.trainer.addItem(fio, trainer_id) - - self.hall.addItems(['Зал 1', 'Зал 2', 'Зал 3', 'Бассейн']) - - layout = QFormLayout() - layout.addRow("Название:", self.class_name) - layout.addRow("Тренер:", self.trainer) - layout.addRow("Дата:", self.class_date) - layout.addRow("Время начала:", self.start_time) - layout.addRow("Время окончания:", self.end_time) - layout.addRow("Зал:", self.hall) - layout.addRow("Макс. участников:", self.max_participants) - - widget = QWidget() - widget.setLayout(layout) - self.layout().addWidget(widget, 0, 0, 1, self.layout().columnCount()) - - self.addButton(QPushButton("Добавить"), QMessageBox.ButtonRole.AcceptRole) - self.addButton(QPushButton("Отмена"), QMessageBox.ButtonRole.RejectRole) - - def get_data(self): - """Получение данных из формы""" - return ( - self.class_name.text(), - self.trainer.currentData(), - self.class_date.date().toString("yyyy-MM-dd"), - self.start_time.time().toString("hh:mm"), - self.end_time.time().toString("hh:mm"), - self.hall.currentText(), - self.max_participants.value() - ) - -if __name__ == '__main__': - app = QApplication(sys.argv) - - # Установка стиля - app.setStyle('Fusion') - - window = FitnessApp() - window.show() - - sys.exit(app.exec()) diff --git a/fitness.db b/fitness.db index 6659418..8472fde 100644 Binary files a/fitness.db and b/fitness.db differ diff --git a/main3.py b/main3.py deleted file mode 100644 index 553ac1f..0000000 --- a/main3.py +++ /dev/null @@ -1,2461 +0,0 @@ -import sys -import sqlite3 -import os -import math -from datetime import datetime, date -from decimal import Decimal - -from PyQt6.QtWidgets import ( - QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, - QLabel, QLineEdit, QTextEdit, QPushButton, QTableWidget, QTableWidgetItem, - QHeaderView, QMessageBox, QDialog, QFormLayout, QGroupBox, QTabWidget, - QComboBox, QDateEdit, QSpinBox, QDoubleSpinBox, QCheckBox, QStackedWidget -) -from PyQt6.QtCore import Qt, QRegularExpression, QDate -from PyQt6.QtGui import QRegularExpressionValidator, QFont, QPixmap, QIcon -from PyQt6.QtSql import QSqlDatabase, QSqlQuery - - -# === Стили приложения === -APP_STYLES = { - 'primary_bg': '#FFFFFF', - 'secondary_bg': '#F4E8D3', - 'accent_color': '#67BA80', - 'font_family': 'Segoe UI' -} - - -# === Функция расчета скидки === -def calculate_partner_discount(rating, total_sales, sales_count): - """ - Расчет скидки для партнера на основе рейтинга и количества продаж - - Формула: - - Базовая скидка от рейтинга: rating * 2 (макс 10%) - - Бонус за количество продаж: log10(sales_count + 1) * 3 (макс 15%) - - Максимальная общая скидка: 25% - - Args: - rating (float): Рейтинг партнера от 0.00 до 5.00 - total_sales (float): Общая сумма продаж - sales_count (int): Количество совершенных продаж - - Returns: - float: Размер скидки в процентах - """ - # Базовая скидка от рейтинга (0-10%) - rating_discount = min(rating * 2.0, 10.0) - - # Бонусная скидка от количества продаж (0-15%) - # Логарифмическая зависимость - чем больше продаж, тем медленнее рост скидки - if sales_count > 0: - sales_bonus = min(math.log10(sales_count + 1) * 3.0, 15.0) - else: - sales_bonus = 0.0 - - # Дополнительный бонус за крупные суммы продаж - volume_bonus = 0.0 - if total_sales > 5000000: # 5 млн руб - volume_bonus = 2.0 - elif total_sales > 2000000: # 2 млн руб - volume_bonus = 1.0 - elif total_sales > 1000000: # 1 млн руб - volume_bonus = 0.5 - - total_discount = rating_discount + sales_bonus + volume_bonus - - # Ограничение максимальной скидки 25% - return min(total_discount, 25.0) - - -# === Функция обновления скидок всех партнеров === -def update_all_partners_discounts(): - """Обновление скидок для всех партнеров на основе актуальных данных""" - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - - try: - # Получаем актуальные данные по продажам для каждого партнера - cursor.execute(""" - SELECT - p.partner_id, - p.rating, - COALESCE(SUM(o.final_amount), 0) as total_sales, - COUNT(o.order_id) as sales_count - FROM partners p - LEFT JOIN orders o ON p.partner_id = o.partner_id AND o.status = 'COMPLETED' - GROUP BY p.partner_id - """) - - partners_data = cursor.fetchall() - - updated_count = 0 - for partner_id, rating, total_sales, sales_count in partners_data: - # Рассчитываем новую скидку - new_discount = calculate_partner_discount( - float(rating) if rating else 0.0, - float(total_sales) if total_sales else 0.0, - sales_count - ) - - # Обновляем скидку в базе - cursor.execute(""" - UPDATE partners - SET discount_rate = ?, total_sales = ? - WHERE partner_id = ? - """, (new_discount, total_sales, partner_id)) - - updated_count += 1 - - conn.commit() - return updated_count - - except sqlite3.Error as e: - conn.rollback() - raise e - finally: - conn.close() - - -# === Функция обновления скидки конкретного партнера === -def update_partner_discount(partner_id): - """Обновление скидки для конкретного партнера""" - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - - try: - # Получаем актуальные данные по продажам партнера - cursor.execute(""" - SELECT - p.rating, - COALESCE(SUM(o.final_amount), 0) as total_sales, - COUNT(o.order_id) as sales_count - FROM partners p - LEFT JOIN orders o ON p.partner_id = o.partner_id AND o.status = 'COMPLETED' - WHERE p.partner_id = ? - GROUP BY p.partner_id - """, (partner_id,)) - - data = cursor.fetchone() - - if data: - rating, total_sales, sales_count = data - - # Рассчитываем новую скидку - new_discount = calculate_partner_discount( - float(rating) if rating else 0.0, - float(total_sales) if total_sales else 0.0, - sales_count - ) - - # Обновляем скидку в базе - cursor.execute(""" - UPDATE partners - SET discount_rate = ?, total_sales = ? - WHERE partner_id = ? - """, (new_discount, total_sales, partner_id)) - - conn.commit() - return new_discount - - return None - - except sqlite3.Error as e: - conn.rollback() - raise e - finally: - conn.close() - - -# === Инициализация базы данных SQLite === -def init_database(): - """Инициализация базы данных SQLite со всеми таблицами""" - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - - # Включаем иностранные ключи - cursor.execute("PRAGMA foreign_keys = ON") - - # Таблица партнеров - cursor.execute(""" - CREATE TABLE IF NOT EXISTS partners ( - partner_id INTEGER PRIMARY KEY AUTOINCREMENT, - partner_type VARCHAR(50) NOT NULL, - company_name VARCHAR(255) NOT NULL, - legal_address TEXT, - inn VARCHAR(12) NOT NULL UNIQUE, - director_name VARCHAR(255), - phone VARCHAR(20), - email VARCHAR(255), - rating DECIMAL(3,2) CHECK (rating BETWEEN 0.00 AND 5.00), - sales_locations TEXT, - total_sales DECIMAL(15,2) DEFAULT 0, - discount_rate DECIMAL(5,2) DEFAULT 0, - created_date DATE DEFAULT CURRENT_DATE - ) - """) - - # Таблица сотрудников - cursor.execute(""" - CREATE TABLE IF NOT EXISTS employees ( - employee_id INTEGER PRIMARY KEY AUTOINCREMENT, - full_name VARCHAR(255) NOT NULL, - birth_date DATE, - passport_data TEXT, - bank_details TEXT, - has_family BOOLEAN DEFAULT FALSE, - health_info TEXT, - position VARCHAR(100), - hire_date DATE DEFAULT CURRENT_DATE, - salary DECIMAL(10,2), - is_active BOOLEAN DEFAULT TRUE - ) - """) - - # Таблица оборудования и доступов - cursor.execute(""" - CREATE TABLE IF NOT EXISTS equipment_access ( - access_id INTEGER PRIMARY KEY AUTOINCREMENT, - employee_id INTEGER, - equipment_name VARCHAR(255) NOT NULL, - access_level VARCHAR(50), - granted_date DATE DEFAULT CURRENT_DATE, - FOREIGN KEY (employee_id) REFERENCES employees(employee_id) ON DELETE CASCADE - ) - """) - - # Таблица поставщиков - cursor.execute(""" - CREATE TABLE IF NOT EXISTS suppliers ( - supplier_id INTEGER PRIMARY KEY AUTOINCREMENT, - supplier_type VARCHAR(50), - company_name VARCHAR(255) NOT NULL, - inn VARCHAR(12) NOT NULL UNIQUE, - contact_person VARCHAR(255), - phone VARCHAR(20), - email VARCHAR(255), - rating DECIMAL(3,2) CHECK (rating BETWEEN 0.00 AND 5.00), - supplied_materials TEXT - ) - """) - - # Таблица материалов - cursor.execute(""" - CREATE TABLE IF NOT EXISTS materials ( - material_id INTEGER PRIMARY KEY AUTOINCREMENT, - material_type VARCHAR(100) NOT NULL, - material_name VARCHAR(255) NOT NULL, - supplier_id INTEGER, - package_quantity DECIMAL(10,3), - unit_of_measure VARCHAR(50), - description TEXT, - cost_per_unit DECIMAL(10,2), - current_stock DECIMAL(10,3) DEFAULT 0, - min_stock_level DECIMAL(10,3) DEFAULT 0, - image_path TEXT, - FOREIGN KEY (supplier_id) REFERENCES suppliers(supplier_id) - ) - """) - - # Таблица истории изменений запасов материалов - cursor.execute(""" - CREATE TABLE IF NOT EXISTS material_stock_history ( - history_id INTEGER PRIMARY KEY AUTOINCREMENT, - material_id INTEGER NOT NULL, - change_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - change_type VARCHAR(20) NOT NULL, -- 'IN', 'OUT', 'ADJUST' - quantity DECIMAL(10,3) NOT NULL, - reason TEXT, - employee_id INTEGER, - FOREIGN KEY (material_id) REFERENCES materials(material_id), - FOREIGN KEY (employee_id) REFERENCES employees(employee_id) - ) - """) - - # Таблица продукции - cursor.execute(""" - CREATE TABLE IF NOT EXISTS products ( - product_id INTEGER PRIMARY KEY AUTOINCREMENT, - article_number VARCHAR(100) UNIQUE NOT NULL, - product_type VARCHAR(100) NOT NULL, - product_name VARCHAR(255) NOT NULL, - description TEXT, - min_partner_price DECIMAL(10,2) NOT NULL, - package_length DECIMAL(8,2), - package_width DECIMAL(8,2), - package_height DECIMAL(8,2), - net_weight DECIMAL(8,2), - gross_weight DECIMAL(8,2), - certificate_path TEXT, - standard_number VARCHAR(100), - production_time_days INTEGER DEFAULT 1, - cost_price DECIMAL(10,2), - workshop_number INTEGER, - required_workers INTEGER, - is_active BOOLEAN DEFAULT TRUE - ) - """) - - # Таблица истории цен продукции - cursor.execute(""" - CREATE TABLE IF NOT EXISTS product_price_history ( - price_history_id INTEGER PRIMARY KEY AUTOINCREMENT, - product_id INTEGER NOT NULL, - change_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - old_price DECIMAL(10,2), - new_price DECIMAL(10,2) NOT NULL, - changed_by INTEGER, - reason TEXT, - FOREIGN KEY (product_id) REFERENCES products(product_id), - FOREIGN KEY (changed_by) REFERENCES employees(employee_id) - ) - """) - - # Таблица материалов для продукции - cursor.execute(""" - CREATE TABLE IF NOT EXISTS product_materials ( - product_material_id INTEGER PRIMARY KEY AUTOINCREMENT, - product_id INTEGER NOT NULL, - material_id INTEGER NOT NULL, - material_quantity DECIMAL(10,3) NOT NULL, - FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE, - FOREIGN KEY (material_id) REFERENCES materials(material_id) - ) - """) - - # Таблица заказов (заявок) - cursor.execute(""" - CREATE TABLE IF NOT EXISTS orders ( - order_id INTEGER PRIMARY KEY AUTOINCREMENT, - partner_id INTEGER NOT NULL, - manager_id INTEGER NOT NULL, - order_date DATE DEFAULT CURRENT_DATE, - status VARCHAR(50) DEFAULT 'NEW', -- NEW, WAITING_PREPAYMENT, IN_PRODUCTION, READY_FOR_SHIPMENT, SHIPPED, COMPLETED, CANCELLED - total_amount DECIMAL(15,2), - discount_amount DECIMAL(15,2) DEFAULT 0, - final_amount DECIMAL(15,2), - prepayment_amount DECIMAL(15,2) DEFAULT 0, - prepayment_date DATE, - full_payment_date DATE, - expected_production_date DATE, - actual_production_date DATE, - delivery_method VARCHAR(100), - delivery_address TEXT, - notes TEXT, - cancellation_reason TEXT, - FOREIGN KEY (partner_id) REFERENCES partners(partner_id), - FOREIGN KEY (manager_id) REFERENCES employees(employee_id) - ) - """) - - # Таблица позиций заказа - cursor.execute(""" - CREATE TABLE IF NOT EXISTS order_items ( - order_item_id INTEGER PRIMARY KEY AUTOINCREMENT, - order_id INTEGER NOT NULL, - product_id INTEGER NOT NULL, - quantity DECIMAL(10,3) NOT NULL, - unit_price DECIMAL(10,2) NOT NULL, - total_price DECIMAL(15,2) NOT NULL, - production_cost DECIMAL(10,2), - FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE, - FOREIGN KEY (product_id) REFERENCES products(product_id) - ) - """) - - # Таблица продаж (история реализации) - cursor.execute(""" - CREATE TABLE IF NOT EXISTS sales ( - sale_id INTEGER PRIMARY KEY AUTOINCREMENT, - partner_id INTEGER NOT NULL, - product_name VARCHAR(255) NOT NULL, - quantity DECIMAL(10,3) NOT NULL CHECK (quantity > 0), - sale_date DATE NOT NULL DEFAULT CURRENT_DATE, - unit_price DECIMAL(10,2), - total_amount DECIMAL(15,2), - FOREIGN KEY (partner_id) REFERENCES partners(partner_id) ON DELETE CASCADE - ) - """) - - # Таблица истории продаж партнеров (для расчета скидок) - cursor.execute(""" - CREATE TABLE IF NOT EXISTS partner_sales_history ( - history_id INTEGER PRIMARY KEY AUTOINCREMENT, - partner_id INTEGER NOT NULL, - period_start DATE NOT NULL, - period_end DATE NOT NULL, - total_sales DECIMAL(15,2) NOT NULL, - discount_rate DECIMAL(5,2) NOT NULL, - FOREIGN KEY (partner_id) REFERENCES partners(partner_id) ON DELETE CASCADE - ) - """) - - # Таблица запасов готовой продукции - cursor.execute(""" - CREATE TABLE IF NOT EXISTS finished_goods_stock ( - stock_id INTEGER PRIMARY KEY AUTOINCREMENT, - product_id INTEGER NOT NULL, - current_stock DECIMAL(10,3) DEFAULT 0, - reserved_stock DECIMAL(10,3) DEFAULT 0, - min_stock_level DECIMAL(10,3) DEFAULT 0, - last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (product_id) REFERENCES products(product_id) - ) - """) - - # Таблица движения готовой продукции - cursor.execute(""" - CREATE TABLE IF NOT EXISTS finished_goods_movements ( - movement_id INTEGER PRIMARY KEY AUTOINCREMENT, - product_id INTEGER NOT NULL, - movement_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - movement_type VARCHAR(20) NOT NULL, -- 'IN', 'OUT', 'RESERVE', 'UNRESERVE' - quantity DECIMAL(10,3) NOT NULL, - reference_id INTEGER, -- order_id or other reference - notes TEXT, - employee_id INTEGER, - FOREIGN KEY (product_id) REFERENCES products(product_id), - FOREIGN KEY (employee_id) REFERENCES employees(employee_id) - ) - """) - - # Добавляем тестовые данные - add_test_data(cursor) - - conn.commit() - conn.close() - - -def add_test_data(cursor): - """Добавление тестовых данных в базу""" - - # Добавляем менеджера - cursor.execute(""" - INSERT OR IGNORE INTO employees (employee_id, full_name, position, hire_date, salary, is_active) - VALUES (1, 'Иванов Алексей Петрович', 'Менеджер по продажам', '2023-01-15', 50000.00, 1) - """) - - # Добавляем пользователя - cursor.execute(""" - INSERT OR IGNORE INTO employees (employee_id, full_name, position, hire_date, salary, is_active) - VALUES (2, 'Петрова Мария Сергеевна', 'Аналитик', '2023-02-20', 45000.00, 1) - """) - - # Добавляем партнеров - partners_data = [ - (1, "ООО", "ООО «СтройГрад»", "г. Москва, ул. Ленина, 10", "770123456789", "Иван Петров", "+79001112233", "buildgrad@example.com", 4.5, "Москва, СПб", 1500000.00, 5.0), - (2, "ИП", "ИП Сидоров А.В.", "г. Казань, пр. Победы, 5", "165432109876", "Андрей Сидоров", "+79054445566", "sidorov@example.com", 4.2, "Казань", 800000.00, 3.0), - (3, "ТОО", "Торговый дом «Полимер+»", "г. Екатеринбург, ул. Мира, 22", "667890123456", "Елена Кузнецова", "+79107778899", "polymer@example.com", 4.8, "Екатеринбург, Челябинск", 2500000.00, 7.0), - ] - - for p in partners_data: - cursor.execute(""" - INSERT OR IGNORE INTO partners - (partner_id, partner_type, company_name, legal_address, inn, director_name, phone, email, rating, sales_locations, total_sales, discount_rate) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, p) - - # Добавляем поставщиков - suppliers_data = [ - (1, "ООО", "ООО «Сырье-Про»", "770987654321", "Петр Васильев", "+79012345678", "syrie@example.com", 4.7, "Древесина, клей, ламинация"), - (2, "ИП", "ИП Колесников С.И.", "163218765432", "Сергей Колесников", "+79087654321", "kolesnikov@example.com", 4.3, "ПВХ, пластификаторы"), - ] - - for s in suppliers_data: - cursor.execute(""" - INSERT OR IGNORE INTO suppliers - (supplier_id, supplier_type, company_name, inn, contact_person, phone, email, rating, supplied_materials) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - """, s) - - # Добавляем материалы - materials_data = [ - (1, "Древесина", "Дубовая доска", 1, 1.0, "м³", "Дубовая доска высшего сорта", 15000.00, 100.5, 20.0), - (2, "Клей", "Клей для ламината", 1, 25.0, "кг", "Водостойкий клей", 450.00, 500.0, 50.0), - (3, "ПВХ", "ПВХ пленка", 2, 50.0, "м²", "Декоративная ПВХ пленка", 320.00, 800.0, 100.0), - ] - - for m in materials_data: - cursor.execute(""" - INSERT OR IGNORE INTO materials - (material_id, material_type, material_name, supplier_id, package_quantity, unit_of_measure, description, cost_per_unit, current_stock, min_stock_level) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, m) - - # Добавляем продукцию - products_data = [ - (1, "LAM-001", "Ламинат", "Ламинат Quick-Step Classic", "Ламинат 32 класса, толщина 8мм", 1250.00, 1.2, 0.2, 0.08, 8.5, 9.2, "STD-045", 3, 850.00, 1, 2), - (2, "LAM-002", "Ламинат", "Ламинат Classen Premium", "Ламинат 33 класса, толщина 10мм", 1450.00, 1.3, 0.2, 0.09, 9.8, 10.5, "STD-048", 4, 950.00, 1, 2), - (3, "PL-001", "Плинтус", "Плинтус ПВХ белый", "Плинтус ПВХ 60мм, длина 2.5м", 350.00, 2.5, 0.06, 0.04, 0.45, 0.55, "STD-012", 1, 220.00, 2, 1), - ] - - for p in products_data: - cursor.execute(""" - INSERT OR IGNORE INTO products - (product_id, article_number, product_type, product_name, description, min_partner_price, package_length, package_width, package_height, net_weight, gross_weight, standard_number, production_time_days, cost_price, workshop_number, required_workers) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, p) - - # Добавляем связь продукции с материалами - product_materials_data = [ - (1, 1, 1, 0.05), # Ламинат Quick-Step -> Дубовая доска - (2, 1, 2, 0.8), # Ламинат Quick-Step -> Клей - (3, 1, 3, 1.2), # Ламинат Quick-Step -> ПВХ пленка - (4, 2, 1, 0.06), # Ламинат Classen -> Дубовая доска - (5, 2, 2, 1.0), # Ламинат Classen -> Клей - (6, 2, 3, 1.5), # Ламинат Classen -> ПВХ пленка - (7, 3, 3, 0.3), # Плинтус -> ПВХ пленка - ] - - for pm in product_materials_data: - cursor.execute(""" - INSERT OR IGNORE INTO product_materials (product_material_id, product_id, material_id, material_quantity) - VALUES (?, ?, ?, ?) - """, pm) - - # Добавляем запасы готовой продукции - finished_goods_data = [ - (1, 1, 500.0, 0, 50.0), - (2, 2, 300.0, 0, 30.0), - (3, 3, 1000.0, 0, 100.0), - ] - - for fg in finished_goods_data: - cursor.execute(""" - INSERT OR IGNORE INTO finished_goods_stock (stock_id, product_id, current_stock, reserved_stock, min_stock_level) - VALUES (?, ?, ?, ?, ?) - """, fg) - - # Добавляем тестовые заявки - orders_data = [ - (1, 1, 1, '2024-01-15', 'COMPLETED', 185000.00, 9250.00, 175750.00, 50000.00, '2024-01-16', '2024-01-25', '2024-01-20', '2024-01-22', 'Самовывоз', '', 'Первый заказ'), - (2, 2, 1, '2024-02-10', 'IN_PRODUCTION', 120000.00, 3600.00, 116400.00, 30000.00, '2024-02-11', None, '2024-02-25', None, 'Доставка курьером', 'г. Казань, пр. Победы, 5', 'Срочный заказ'), - (3, 3, 1, '2024-03-01', 'WAITING_PREPAYMENT', 250000.00, 17500.00, 232500.00, 0, None, None, '2024-03-20', None, 'Доставка транспортной компанией', 'г. Екатеринбург, ул. Мира, 22', 'Крупный опт'), - ] - - for order in orders_data: - cursor.execute(""" - INSERT OR IGNORE INTO orders - (order_id, partner_id, manager_id, order_date, status, total_amount, discount_amount, - final_amount, prepayment_amount, prepayment_date, full_payment_date, - expected_production_date, actual_production_date, delivery_method, delivery_address, notes) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, order) - - # Добавляем позиции заказов - order_items_data = [ - (1, 1, 1, 100.0, 1250.00, 125000.00, 850.00), - (2, 1, 3, 200.0, 300.00, 60000.00, 220.00), - (3, 2, 2, 60.0, 1450.00, 87000.00, 950.00), - (4, 2, 3, 110.0, 300.00, 33000.00, 220.00), - (5, 3, 1, 150.0, 1250.00, 187500.00, 850.00), - (6, 3, 2, 40.0, 1450.00, 58000.00, 950.00), - (7, 3, 3, 150.0, 300.00, 45000.00, 220.00), - ] - - for item in order_items_data: - cursor.execute(""" - INSERT OR IGNORE INTO order_items - (order_item_id, order_id, product_id, quantity, unit_price, total_price, production_cost) - VALUES (?, ?, ?, ?, ?, ?, ?) - """, item) - - # Добавляем больше тестовых продаж для демонстрации скидок - sales_data = [ - (1, "Ламинат Quick-Step Classic", 50.0, '2024-01-20', 1250.00, 62500.00), - (1, "Плинтус ПВХ белый", 100.0, '2024-01-20', 300.00, 30000.00), - (1, "Ламинат Classen Premium", 30.0, '2024-02-15', 1450.00, 43500.00), - (2, "Ламинат Quick-Step Classic", 25.0, '2024-01-25', 1250.00, 31250.00), - (2, "Плинтус ПВХ белый", 50.0, '2024-02-10', 300.00, 15000.00), - (3, "Ламинат Classen Premium", 100.0, '2024-01-30', 1450.00, 145000.00), - (3, "Ламинат Quick-Step Classic", 80.0, '2024-02-20', 1250.00, 100000.00), - (3, "Плинтус ПВХ белый", 200.0, '2024-03-01', 300.00, 60000.00), - ] - - for sale in sales_data: - cursor.execute(""" - INSERT OR IGNORE INTO sales - (partner_id, product_name, quantity, sale_date, unit_price, total_amount) - VALUES (?, ?, ?, ?, ?, ?) - """, sale) - - -# === Диалог авторизации === -class AuthDialog(QDialog): - def __init__(self, parent=None): - super().__init__(parent) - self.setWindowTitle("Авторизация - Мастер пол") - self.setFixedSize(350, 250) - self.setStyleSheet(f""" - QDialog {{ - background-color: {APP_STYLES['primary_bg']}; - font-family: {APP_STYLES['font_family']}; - }} - QPushButton {{ - background-color: {APP_STYLES['accent_color']}; - color: white; - border: none; - padding: 8px 15px; - border-radius: 4px; - font-weight: bold; - }} - QPushButton:hover {{ - background-color: #5AA870; - }} - QLineEdit, QComboBox {{ - padding: 8px; - border: 1px solid #ccc; - border-radius: 4px; - }} - """) - - self.authenticated = False - self.user_id = None - self.user_name = None - self.user_role = None - - layout = QVBoxLayout() - - # Заголовок - title = QLabel("Вход в систему") - title.setAlignment(Qt.AlignmentFlag.AlignCenter) - title.setStyleSheet("font-size: 18px; font-weight: bold; margin: 10px;") - layout.addWidget(title) - - # Поля ввода - form_layout = QFormLayout() - - self.role_combo = QComboBox() - self.role_combo.addItems(["Менеджер", "Пользователь"]) - form_layout.addRow("Роль:", self.role_combo) - - self.login_edit = QLineEdit() - self.login_edit.setPlaceholderText("Введите логин") - form_layout.addRow("Логин:", self.login_edit) - - self.pass_edit = QLineEdit() - self.pass_edit.setPlaceholderText("Введите пароль") - self.pass_edit.setEchoMode(QLineEdit.EchoMode.Password) - form_layout.addRow("Пароль:", self.pass_edit) - - layout.addLayout(form_layout) - - # Кнопки - btn_layout = QHBoxLayout() - self.login_btn = QPushButton("Войти") - self.login_btn.clicked.connect(self.login) - self.cancel_btn = QPushButton("Отмена") - self.cancel_btn.clicked.connect(self.reject) - - btn_layout.addWidget(self.login_btn) - btn_layout.addWidget(self.cancel_btn) - layout.addLayout(btn_layout) - - # Подсказка - hint = QLabel("Менеджер: manager/pass123\nПользователь: user/user123") - hint.setAlignment(Qt.AlignmentFlag.AlignCenter) - hint.setStyleSheet("color: #666; font-size: 12px; margin-top: 10px;") - layout.addWidget(hint) - - self.setLayout(layout) - - def login(self): - role = self.role_combo.currentText() - login = self.login_edit.text().strip() - password = self.pass_edit.text() - - if role == "Менеджер": - if login == "manager" and password == "pass123": - self.authenticated = True - self.user_id = 1 - self.user_name = "Иванов Алексей Петрович" - self.user_role = "manager" - self.accept() - else: - QMessageBox.warning(self, "Ошибка", "Неверный логин или пароль менеджера!") - else: # Пользователь - if login == "user" and password == "user123": - self.authenticated = True - self.user_id = 2 - self.user_name = "Петрова Мария Сергеевна" - self.user_role = "user" - self.accept() - else: - QMessageBox.warning(self, "Ошибка", "Неверный логин или пароль пользователя!") - - -# === Базовый класс для диалогов === -class BaseDialog(QDialog): - def __init__(self, parent=None): - super().__init__(parent) - self.setStyleSheet(f""" - QDialog {{ - background-color: {APP_STYLES['primary_bg']}; - font-family: {APP_STYLES['font_family']}; - }} - QPushButton {{ - background-color: {APP_STYLES['accent_color']}; - color: white; - border: none; - padding: 8px 15px; - border-radius: 4px; - font-weight: bold; - }} - QPushButton:hover {{ - background-color: #5AA870; - }} - QLineEdit, QTextEdit, QComboBox, QDateEdit, QSpinBox, QDoubleSpinBox {{ - padding: 6px; - border: 1px solid #ccc; - border-radius: 4px; - }} - QGroupBox {{ - font-weight: bold; - margin-top: 10px; - }} - QGroupBox::title {{ - subcontrol-origin: margin; - left: 10px; - padding: 0 5px 0 5px; - }} - """) - - -# === Диалог партнера === -class PartnerDialog(BaseDialog): - def __init__(self, partner_data=None, parent=None): - super().__init__(parent) - self.partner_data = partner_data - title = "Добавить партнёра" if not partner_data else "Редактировать партнёра" - self.setWindowTitle(title) - self.setFixedSize(500, 550) - - layout = QVBoxLayout() - - # Основные данные - form_group = QGroupBox("Данные партнёра") - form_layout = QFormLayout() - - self.fields = { - "type": QComboBox(), - "name": QLineEdit(), - "address": QTextEdit(), - "inn": QLineEdit(), - "director": QLineEdit(), - "phone": QLineEdit(), - "email": QLineEdit(), - "rating": QDoubleSpinBox(), - "locations": QTextEdit(), - } - - # Настройка полей - self.fields["type"].addItems(["ООО", "ИП", "ТОО", "ЗАО", "ОАО", "Иное"]) - - inn_validator = QRegularExpressionValidator(QRegularExpression(r"^\d{10,12}$")) - self.fields["inn"].setValidator(inn_validator) - - self.fields["rating"].setRange(0.0, 5.0) - self.fields["rating"].setDecimals(2) - self.fields["rating"].setSingleStep(0.1) - - self.fields["address"].setMaximumHeight(70) - self.fields["locations"].setMaximumHeight(70) - - # Добавление полей в форму - form_layout.addRow("Тип партнёра *:", self.fields["type"]) - form_layout.addRow("Название компании *:", self.fields["name"]) - form_layout.addRow("Юридический адрес:", self.fields["address"]) - form_layout.addRow("ИНН *:", self.fields["inn"]) - form_layout.addRow("ФИО директора:", self.fields["director"]) - form_layout.addRow("Телефон:", self.fields["phone"]) - form_layout.addRow("Email:", self.fields["email"]) - form_layout.addRow("Рейтинг (0-5):", self.fields["rating"]) - form_layout.addRow("Места продаж:", self.fields["locations"]) - - form_group.setLayout(form_layout) - layout.addWidget(form_group) - - # Кнопки - btn_layout = QHBoxLayout() - self.save_btn = QPushButton("Сохранить") - self.save_btn.clicked.connect(self.accept) - self.cancel_btn = QPushButton("Отмена") - self.cancel_btn.clicked.connect(self.reject) - - btn_layout.addWidget(self.save_btn) - btn_layout.addWidget(self.cancel_btn) - layout.addLayout(btn_layout) - - self.setLayout(layout) - - if partner_data: - self.load_data(partner_data) - - def load_data(self, data): - self.fields["type"].setCurrentText(data.get("partner_type") or "") - self.fields["name"].setText(data.get("company_name") or "") - self.fields["address"].setPlainText(data.get("legal_address") or "") - self.fields["inn"].setText(data.get("inn") or "") - self.fields["director"].setText(data.get("director_name") or "") - self.fields["phone"].setText(data.get("phone") or "") - self.fields["email"].setText(data.get("email") or "") - self.fields["rating"].setValue(float(data.get("rating") or 0.0)) - self.fields["locations"].setPlainText(data.get("sales_locations") or "") - - def get_data(self): - return { - "partner_type": self.fields["type"].currentText(), - "company_name": self.fields["name"].text().strip(), - "legal_address": self.fields["address"].toPlainText().strip() or None, - "inn": self.fields["inn"].text().strip(), - "director_name": self.fields["director"].text().strip() or None, - "phone": self.fields["phone"].text().strip() or None, - "email": self.fields["email"].text().strip() or None, - "rating": self.fields["rating"].value(), - "sales_locations": self.fields["locations"].toPlainText().strip() or None, - } - - def validate(self): - if not self.fields["name"].text().strip(): - QMessageBox.warning(self, "Ошибка", "Поле «Название компании» обязательно.") - return False - if not self.fields["inn"].text().strip(): - QMessageBox.warning(self, "Ошибка", "Поле «ИНН» обязательно.") - return False - if not self.fields["inn"].hasAcceptableInput(): - QMessageBox.warning(self, "Ошибка", "ИНН должен содержать 10 или 12 цифр.") - return False - return True - - def accept(self): - if self.validate(): - super().accept() - - -# === Диалог заказа === -class OrderDialog(BaseDialog): - def __init__(self, order_data=None, parent=None): - super().__init__(parent) - self.order_data = order_data - self.order_items = [] - - title = "Создать заявку" if not order_data else "Редактировать заявку" - self.setWindowTitle(title) - self.setMinimumSize(700, 600) - - layout = QVBoxLayout() - - # Основные данные заказа - form_group = QGroupBox("Данные заявки") - form_layout = QFormLayout() - - self.partner_combo = QComboBox() - self.status_combo = QComboBox() - self.order_date = QDateEdit() - self.expected_date = QDateEdit() - self.delivery_method = QComboBox() - self.delivery_address = QTextEdit() - self.notes = QTextEdit() - - # Настройка полей - self.status_combo.addItems(["NEW", "WAITING_PREPAYMENT", "IN_PRODUCTION", "READY_FOR_SHIPMENT", "SHIPPED", "COMPLETED", "CANCELLED"]) - self.order_date.setDate(QDate.currentDate()) - self.expected_date.setDate(QDate.currentDate().addDays(7)) - self.delivery_method.addItems(["Самовывоз", "Доставка курьером", "Доставка транспортной компанией"]) - - self.delivery_address.setMaximumHeight(60) - self.notes.setMaximumHeight(60) - - form_layout.addRow("Партнёр *:", self.partner_combo) - form_layout.addRow("Статус:", self.status_combo) - form_layout.addRow("Дата заявки:", self.order_date) - form_layout.addRow("Ожидаемая дата:", self.expected_date) - form_layout.addRow("Способ доставки:", self.delivery_method) - form_layout.addRow("Адрес доставки:", self.delivery_address) - form_layout.addRow("Примечания:", self.notes) - - form_group.setLayout(form_layout) - layout.addWidget(form_group) - - # Позиции заказа - items_group = QGroupBox("Позиции заказа") - items_layout = QVBoxLayout() - - # Таблица позиций - self.items_table = QTableWidget() - self.items_table.setColumnCount(5) - self.items_table.setHorizontalHeaderLabels(["Продукт", "Количество", "Цена", "Сумма", ""]) - self.items_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) - items_layout.addWidget(self.items_table) - - # Кнопки для позиций - items_btn_layout = QHBoxLayout() - self.add_item_btn = QPushButton("Добавить позицию") - self.add_item_btn.clicked.connect(self.add_order_item) - self.remove_item_btn = QPushButton("Удалить позицию") - self.remove_item_btn.clicked.connect(self.remove_order_item) - - items_btn_layout.addWidget(self.add_item_btn) - items_btn_layout.addWidget(self.remove_item_btn) - items_btn_layout.addStretch() - - items_layout.addLayout(items_btn_layout) - items_group.setLayout(items_layout) - layout.addWidget(items_group) - - # Итоги - totals_layout = QHBoxLayout() - self.total_label = QLabel("Итого: 0.00 руб.") - self.total_label.setStyleSheet("font-weight: bold; font-size: 14px;") - totals_layout.addStretch() - totals_layout.addWidget(self.total_label) - layout.addLayout(totals_layout) - - # Кнопки сохранения/отмены - btn_layout = QHBoxLayout() - self.save_btn = QPushButton("Сохранить заявку") - self.save_btn.clicked.connect(self.accept) - self.cancel_btn = QPushButton("Отмена") - self.cancel_btn.clicked.connect(self.reject) - - btn_layout.addWidget(self.save_btn) - btn_layout.addWidget(self.cancel_btn) - layout.addLayout(btn_layout) - - self.setLayout(layout) - - self.load_partners() - - if order_data: - self.load_data(order_data) - - def load_partners(self): - """Загрузка списка партнеров в комбобокс""" - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute("SELECT partner_id, company_name FROM partners ORDER BY company_name") - partners = cursor.fetchall() - conn.close() - - self.partner_combo.clear() - for partner_id, name in partners: - self.partner_combo.addItem(name, partner_id) - - def load_data(self, data): - """Загрузка данных заказа""" - # Загрузка основных данных - partner_index = self.partner_combo.findData(data.get("partner_id")) - if partner_index >= 0: - self.partner_combo.setCurrentIndex(partner_index) - - status_index = self.status_combo.findText(data.get("status", "NEW")) - if status_index >= 0: - self.status_combo.setCurrentIndex(status_index) - - order_date = QDate.fromString(data.get("order_date"), "yyyy-MM-dd") if data.get("order_date") else QDate.currentDate() - self.order_date.setDate(order_date) - - expected_date = QDate.fromString(data.get("expected_production_date"), "yyyy-MM-dd") if data.get("expected_production_date") else QDate.currentDate() - self.expected_date.setDate(expected_date) - - self.delivery_method.setCurrentText(data.get("delivery_method") or "") - self.delivery_address.setPlainText(data.get("delivery_address") or "") - self.notes.setPlainText(data.get("notes") or "") - - # Загрузка позиций заказа - self.load_order_items(data.get("order_id")) - - def load_order_items(self, order_id): - """Загрузка позиций заказа из БД""" - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute(""" - SELECT oi.product_id, p.product_name, oi.quantity, oi.unit_price, oi.total_price - FROM order_items oi - JOIN products p ON oi.product_id = p.product_id - WHERE oi.order_id = ? - """, (order_id,)) - - items = cursor.fetchall() - conn.close() - - self.order_items = [] - self.items_table.setRowCount(len(items)) - - for i, (product_id, product_name, quantity, unit_price, total_price) in enumerate(items): - self.items_table.setItem(i, 0, QTableWidgetItem(product_name)) - self.items_table.setItem(i, 1, QTableWidgetItem(str(quantity))) - self.items_table.setItem(i, 2, QTableWidgetItem(f"{unit_price:.2f}")) - self.items_table.setItem(i, 3, QTableWidgetItem(f"{total_price:.2f}")) - - remove_btn = QPushButton("Удалить") - remove_btn.clicked.connect(lambda checked, row=i: self.remove_specific_item(row)) - self.items_table.setCellWidget(i, 4, remove_btn) - - self.order_items.append({ - "product_id": product_id, - "product_name": product_name, - "quantity": quantity, - "unit_price": unit_price, - "total_price": total_price - }) - - self.update_totals() - - def add_order_item(self): - """Добавление новой позиции в заказ""" - dialog = OrderItemDialog(self) - if dialog.exec() == QDialog.DialogCode.Accepted: - item_data = dialog.get_data() - if item_data: - self.order_items.append(item_data) - self.update_items_table() - - def remove_order_item(self): - """Удаление выбранной позиции""" - current_row = self.items_table.currentRow() - if current_row >= 0 and current_row < len(self.order_items): - self.order_items.pop(current_row) - self.update_items_table() - - def remove_specific_item(self, row): - """Удаление конкретной позиции по кнопке""" - if 0 <= row < len(self.order_items): - self.order_items.pop(row) - self.update_items_table() - - def update_items_table(self): - """Обновление таблицы позиций""" - self.items_table.setRowCount(len(self.order_items)) - - for i, item in enumerate(self.order_items): - self.items_table.setItem(i, 0, QTableWidgetItem(item["product_name"])) - self.items_table.setItem(i, 1, QTableWidgetItem(str(item["quantity"]))) - self.items_table.setItem(i, 2, QTableWidgetItem(f"{item['unit_price']:.2f}")) - self.items_table.setItem(i, 3, QTableWidgetItem(f"{item['total_price']:.2f}")) - - remove_btn = QPushButton("Удалить") - remove_btn.clicked.connect(lambda checked, row=i: self.remove_specific_item(row)) - self.items_table.setCellWidget(i, 4, remove_btn) - - self.update_totals() - - def update_totals(self): - """Пересчет итоговой суммы""" - total = sum(item["total_price"] for item in self.order_items) - self.total_label.setText(f"Итого: {total:.2f} руб.") - - def get_data(self): - """Получение данных формы""" - data = { - "partner_id": self.partner_combo.currentData(), - "status": self.status_combo.currentText(), - "order_date": self.order_date.date().toString("yyyy-MM-dd"), - "expected_production_date": self.expected_date.date().toString("yyyy-MM-dd"), - "delivery_method": self.delivery_method.currentText(), - "delivery_address": self.delivery_address.toPlainText().strip(), - "notes": self.notes.toPlainText().strip(), - "total_amount": sum(item["total_price"] for item in self.order_items), - "order_items": self.order_items - } - - return data - - def validate(self): - """Валидация данных""" - if not self.partner_combo.currentData(): - QMessageBox.warning(self, "Ошибка", "Не выбран партнёр.") - return False - - if not self.order_items: - QMessageBox.warning(self, "Ошибка", "Добавьте хотя бы одну позицию в заказ.") - return False - - return True - - def accept(self): - if self.validate(): - super().accept() - - -# === Диалог позиции заказа === -class OrderItemDialog(BaseDialog): - def __init__(self, parent=None): - super().__init__(parent) - self.setWindowTitle("Добавить позицию") - self.setFixedSize(400, 300) - - layout = QVBoxLayout() - - form_layout = QFormLayout() - - self.product_combo = QComboBox() - self.quantity = QDoubleSpinBox() - self.unit_price = QDoubleSpinBox() - self.total_price = QLabel("0.00") - - # Настройка полей - self.quantity.setRange(0.1, 10000.0) - self.quantity.setDecimals(3) - self.quantity.setValue(1.0) - - self.unit_price.setRange(0.01, 100000.0) - self.unit_price.setDecimals(2) - - # Связывание сигналов для автоматического пересчета - self.quantity.valueChanged.connect(self.calculate_total) - self.unit_price.valueChanged.connect(self.calculate_total) - - form_layout.addRow("Продукт *:", self.product_combo) - form_layout.addRow("Количество *:", self.quantity) - form_layout.addRow("Цена за единицу *:", self.unit_price) - form_layout.addRow("Общая сумма:", self.total_price) - - layout.addLayout(form_layout) - - # Кнопки - btn_layout = QHBoxLayout() - self.add_btn = QPushButton("Добавить") - self.add_btn.clicked.connect(self.accept) - self.cancel_btn = QPushButton("Отмена") - self.cancel_btn.clicked.connect(self.reject) - - btn_layout.addWidget(self.add_btn) - btn_layout.addWidget(self.cancel_btn) - layout.addLayout(btn_layout) - - self.setLayout(layout) - - self.load_products() - - def load_products(self): - """Загрузка списка продукции""" - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute("SELECT product_id, product_name, min_partner_price FROM products WHERE is_active = 1") - products = cursor.fetchall() - conn.close() - - self.product_combo.clear() - for product_id, product_name, min_price in products: - self.product_combo.addItem(f"{product_name} ({min_price:.2f} руб.)", (product_id, min_price)) - - if self.product_combo.count() > 0: - self.product_combo.currentIndexChanged.connect(self.product_changed) - self.product_changed(0) # Установить цену для первого товара - - def product_changed(self, index): - """Обработчик изменения выбранного продукта""" - if index >= 0: - product_data = self.product_combo.currentData() - if product_data: - product_id, min_price = product_data - self.unit_price.setValue(float(min_price)) - - def calculate_total(self): - """Пересчет общей суммы""" - total = self.quantity.value() * self.unit_price.value() - self.total_price.setText(f"{total:.2f}") - - def get_data(self): - """Получение данных позиции""" - if self.product_combo.currentIndex() < 0: - return None - - product_data = self.product_combo.currentData() - if not product_data: - return None - - product_id, min_price = product_data - quantity = self.quantity.value() - unit_price = self.unit_price.value() - total_price = quantity * unit_price - - return { - "product_id": product_id, - "product_name": self.product_combo.currentText().split(' (')[0], # Извлекаем название без цены - "quantity": quantity, - "unit_price": unit_price, - "total_price": total_price - } - - def validate(self): - """Валидация данных""" - if self.product_combo.currentIndex() < 0: - QMessageBox.warning(self, "Ошибка", "Не выбран продукт.") - return False - - if self.quantity.value() <= 0: - QMessageBox.warning(self, "Ошибка", "Количество должно быть больше 0.") - return False - - if self.unit_price.value() <= 0: - QMessageBox.warning(self, "Ошибка", "Цена должна быть больше 0.") - return False - - return True - - def accept(self): - if self.validate(): - super().accept() - - -# === Основное окно приложения === -class MainWindow(QMainWindow): - def __init__(self, user_id, user_name, user_role): - super().__init__() - self.user_id = user_id - self.user_name = user_name - self.user_role = user_role - - role_display = "Менеджер" if user_role == "manager" else "Пользователь" - self.setWindowTitle(f"Мастер пол - Система управления ({role_display}: {user_name})") - self.setMinimumSize(1200, 700) - - # Установка стилей - self.setStyleSheet(f""" - QMainWindow {{ - background-color: {APP_STYLES['primary_bg']}; - font-family: {APP_STYLES['font_family']}; - }} - QTabWidget::pane {{ - border: 1px solid #C2C7CB; - background-color: {APP_STYLES['primary_bg']}; - }} - QTabBar::tab {{ - background-color: {APP_STYLES['secondary_bg']}; - border: 1px solid #C2C7CB; - padding: 8px 15px; - margin-right: 2px; - }} - QTabBar::tab:selected {{ - background-color: {APP_STYLES['accent_color']}; - color: white; - }} - QPushButton {{ - background-color: {APP_STYLES['accent_color']}; - color: white; - border: none; - padding: 8px 15px; - border-radius: 4px; - font-weight: bold; - }} - QPushButton:hover {{ - background-color: #5AA870; - }} - QPushButton:disabled {{ - background-color: #CCCCCC; - color: #666666; - }} - QTableWidget {{ - gridline-color: #D0D0D0; - selection-background-color: {APP_STYLES['accent_color']}; - }} - QHeaderView::section {{ - background-color: {APP_STYLES['secondary_bg']}; - padding: 5px; - border: 1px solid #D0D0D0; - font-weight: bold; - }} - """) - - self.init_ui() - self.load_initial_data() - - def init_ui(self): - """Инициализация пользовательского интерфейса""" - central_widget = QWidget() - self.setCentralWidget(central_widget) - - layout = QVBoxLayout() - - # Заголовок - header_layout = QHBoxLayout() - title = QLabel("Система управления производством «Мастер пол»") - title.setStyleSheet("font-size: 20px; font-weight: bold; color: #333;") - header_layout.addWidget(title) - header_layout.addStretch() - - user_label = QLabel(f"{'Менеджер' if self.user_role == 'manager' else 'Пользователь'}: {self.user_name}") - user_label.setStyleSheet("color: #666;") - header_layout.addWidget(user_label) - - layout.addLayout(header_layout) - - # Вкладки - self.tabs = QTabWidget() - - # Создаем вкладки - self.partners_tab = self.create_partners_tab() - self.orders_tab = self.create_orders_tab() - self.products_tab = self.create_products_tab() - self.employees_tab = self.create_employees_tab() - self.materials_tab = self.create_materials_tab() - - self.tabs.addTab(self.partners_tab, "Партнёры") - self.tabs.addTab(self.orders_tab, "Заявки") - self.tabs.addTab(self.products_tab, "Продукция") - self.tabs.addTab(self.employees_tab, "Сотрудники") - self.tabs.addTab(self.materials_tab, "Материалы") - - layout.addWidget(self.tabs) - central_widget.setLayout(layout) - - # Настройка прав доступа в зависимости от роли - self.setup_permissions() - - def setup_permissions(self): - """Настройка прав доступа в зависимости от роли пользователя""" - is_manager = self.user_role == "manager" - - # Партнеры - self.add_partner_btn.setEnabled(is_manager) - self.edit_partner_btn.setEnabled(is_manager) - self.delete_partner_btn.setEnabled(is_manager) - self.update_discounts_btn.setEnabled(is_manager) - - # Заявки - self.add_order_btn.setEnabled(is_manager) - self.edit_order_btn.setEnabled(is_manager) - self.update_status_btn.setEnabled(is_manager) - self.delete_order_btn.setEnabled(is_manager) - - # Продукция - self.add_product_btn.setEnabled(is_manager) - self.edit_product_btn.setEnabled(is_manager) - self.delete_product_btn.setEnabled(is_manager) - - # Сотрудники - self.add_employee_btn.setEnabled(is_manager) - self.edit_employee_btn.setEnabled(is_manager) - self.delete_employee_btn.setEnabled(is_manager) - - # Материалы - self.add_material_btn.setEnabled(is_manager) - self.edit_material_btn.setEnabled(is_manager) - self.delete_material_btn.setEnabled(is_manager) - - def create_partners_tab(self): - """Создание вкладки партнеров""" - widget = QWidget() - layout = QVBoxLayout() - - # Панель управления - control_layout = QHBoxLayout() - - self.add_partner_btn = QPushButton("➕ Добавить партнёра") - self.edit_partner_btn = QPushButton("✏️ Редактировать") - self.view_sales_btn = QPushButton("📊 История продаж") - self.delete_partner_btn = QPushButton("🗑 Удалить") - self.update_discounts_btn = QPushButton("🎯 Обновить скидки") # Новая кнопка - self.refresh_partners_btn = QPushButton("🔄 Обновить") - - self.add_partner_btn.clicked.connect(self.add_partner) - self.edit_partner_btn.clicked.connect(self.edit_partner) - self.view_sales_btn.clicked.connect(self.view_sales_history) - self.delete_partner_btn.clicked.connect(self.delete_partner) - self.update_discounts_btn.clicked.connect(self.update_partner_discounts) # Новый обработчик - self.refresh_partners_btn.clicked.connect(self.load_partners) - - control_layout.addWidget(self.add_partner_btn) - control_layout.addWidget(self.edit_partner_btn) - control_layout.addWidget(self.view_sales_btn) - control_layout.addWidget(self.delete_partner_btn) - control_layout.addWidget(self.update_discounts_btn) # Добавляем кнопку в layout - control_layout.addStretch() - control_layout.addWidget(self.refresh_partners_btn) - - layout.addLayout(control_layout) - - # Таблица партнеров - self.partners_table = QTableWidget() - self.partners_table.setColumnCount(10) - self.partners_table.setHorizontalHeaderLabels([ - "ID", "Тип", "Компания", "ИНН", "Директор", "Телефон", "Email", "Рейтинг", "Общие продажи", "Скидка %" - ]) - - # Настройка таблицы - header = self.partners_table.horizontalHeader() - header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) - header.setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(7, QHeaderView.ResizeMode.ResizeToContents) - - self.partners_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) - self.partners_table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection) - self.partners_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) - - # Двойной клик для редактирования - self.partners_table.doubleClicked.connect(self.edit_partner) - - layout.addWidget(self.partners_table) - widget.setLayout(layout) - - return widget - - def create_orders_tab(self): - """Создание вкладки заявок""" - widget = QWidget() - layout = QVBoxLayout() - - # Панель управления - control_layout = QHBoxLayout() - - self.add_order_btn = QPushButton("➕ Новая заявка") - self.edit_order_btn = QPushButton("✏️ Редактировать") - self.view_order_btn = QPushButton("👁 Просмотреть") - self.update_status_btn = QPushButton("🔄 Обновить статус") - self.delete_order_btn = QPushButton("🗑 Удалить") - self.refresh_orders_btn = QPushButton("🔄 Обновить") - - self.add_order_btn.clicked.connect(self.add_order) - self.edit_order_btn.clicked.connect(self.edit_order) - self.view_order_btn.clicked.connect(self.view_order) - self.update_status_btn.clicked.connect(self.update_order_status) - self.delete_order_btn.clicked.connect(self.delete_order) - self.refresh_orders_btn.clicked.connect(self.load_orders) - - control_layout.addWidget(self.add_order_btn) - control_layout.addWidget(self.edit_order_btn) - control_layout.addWidget(self.view_order_btn) - control_layout.addWidget(self.update_status_btn) - control_layout.addWidget(self.delete_order_btn) - control_layout.addStretch() - control_layout.addWidget(self.refresh_orders_btn) - - layout.addLayout(control_layout) - - # Таблица заявок - self.orders_table = QTableWidget() - self.orders_table.setColumnCount(8) - self.orders_table.setHorizontalHeaderLabels([ - "ID", "Партнёр", "Дата", "Статус", "Сумма", "Скидка", "Итог", "Менеджер" - ]) - - # Настройка таблицы - header = self.orders_table.horizontalHeader() - header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) - header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) - - self.orders_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) - self.orders_table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection) - self.orders_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) - - layout.addWidget(self.orders_table) - widget.setLayout(layout) - - return widget - - def create_products_tab(self): - """Создание вкладки продукции""" - widget = QWidget() - layout = QVBoxLayout() - - # Панель управления - control_layout = QHBoxLayout() - - self.add_product_btn = QPushButton("➕ Добавить продукт") - self.edit_product_btn = QPushButton("✏️ Редактировать") - self.view_materials_btn = QPushButton("📋 Состав продукции") - self.delete_product_btn = QPushButton("🗑 Удалить") - self.refresh_products_btn = QPushButton("🔄 Обновить") - - self.add_product_btn.clicked.connect(self.add_product) - self.edit_product_btn.clicked.connect(self.edit_product) - self.view_materials_btn.clicked.connect(self.view_product_materials) - self.delete_product_btn.clicked.connect(self.delete_product) - self.refresh_products_btn.clicked.connect(self.load_products) - - control_layout.addWidget(self.add_product_btn) - control_layout.addWidget(self.edit_product_btn) - control_layout.addWidget(self.view_materials_btn) - control_layout.addWidget(self.delete_product_btn) - control_layout.addStretch() - control_layout.addWidget(self.refresh_products_btn) - - layout.addLayout(control_layout) - - # Таблица продукции - self.products_table = QTableWidget() - self.products_table.setColumnCount(10) - self.products_table.setHorizontalHeaderLabels([ - "ID", "Артикул", "Тип", "Наименование", "Мин. цена", "Вес нетто", "Вес брутто", "Время пр-ва", "Себестоимость", "Цех" - ]) - - # Настройка таблицы - header = self.products_table.horizontalHeader() - header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch) - - self.products_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) - self.products_table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection) - self.products_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) - - layout.addWidget(self.products_table) - widget.setLayout(layout) - - return widget - - def create_employees_tab(self): - """Создание вкладки сотрудников""" - widget = QWidget() - layout = QVBoxLayout() - - # Панель управления - control_layout = QHBoxLayout() - - self.add_employee_btn = QPushButton("➕ Добавить сотрудника") - self.edit_employee_btn = QPushButton("✏️ Редактировать") - self.view_access_btn = QPushButton("🔧 Доступ к оборудованию") - self.delete_employee_btn = QPushButton("🗑 Удалить") - self.refresh_employees_btn = QPushButton("🔄 Обновить") - - self.add_employee_btn.clicked.connect(self.add_employee) - self.edit_employee_btn.clicked.connect(self.edit_employee) - self.view_access_btn.clicked.connect(self.view_equipment_access) - self.delete_employee_btn.clicked.connect(self.delete_employee) - self.refresh_employees_btn.clicked.connect(self.load_employees) - - control_layout.addWidget(self.add_employee_btn) - control_layout.addWidget(self.edit_employee_btn) - control_layout.addWidget(self.view_access_btn) - control_layout.addWidget(self.delete_employee_btn) - control_layout.addStretch() - control_layout.addWidget(self.refresh_employees_btn) - - layout.addLayout(control_layout) - - # Таблица сотрудников - self.employees_table = QTableWidget() - self.employees_table.setColumnCount(8) - self.employees_table.setHorizontalHeaderLabels([ - "ID", "ФИО", "Должность", "Дата найма", "Зарплата", "Дата рождения", "Семья", "Статус" - ]) - - # Настройка таблицы - header = self.employees_table.horizontalHeader() - header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) - header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) - - self.employees_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) - self.employees_table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection) - self.employees_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) - - layout.addWidget(self.employees_table) - widget.setLayout(layout) - - return widget - - def create_materials_tab(self): - """Создание вкладки материалов""" - widget = QWidget() - layout = QVBoxLayout() - - # Панель управления - control_layout = QHBoxLayout() - - self.add_material_btn = QPushButton("➕ Добавить материал") - self.edit_material_btn = QPushButton("✏️ Редактировать") - self.view_stock_btn = QPushButton("📊 История запасов") - self.delete_material_btn = QPushButton("🗑 Удалить") - self.refresh_materials_btn = QPushButton("🔄 Обновить") - - self.add_material_btn.clicked.connect(self.add_material) - self.edit_material_btn.clicked.connect(self.edit_material) - self.view_stock_btn.clicked.connect(self.view_stock_history) - self.delete_material_btn.clicked.connect(self.delete_material) - self.refresh_materials_btn.clicked.connect(self.load_materials) - - control_layout.addWidget(self.add_material_btn) - control_layout.addWidget(self.edit_material_btn) - control_layout.addWidget(self.view_stock_btn) - control_layout.addWidget(self.delete_material_btn) - control_layout.addStretch() - control_layout.addWidget(self.refresh_materials_btn) - - layout.addLayout(control_layout) - - # Таблица материалов - self.materials_table = QTableWidget() - self.materials_table.setColumnCount(9) - self.materials_table.setHorizontalHeaderLabels([ - "ID", "Тип", "Наименование", "Поставщик", "Ед. изм.", "Цена", "Текущий запас", "Мин. запас", "Описание" - ]) - - # Настройка таблицы - header = self.materials_table.horizontalHeader() - header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) - header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) - header.setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) - - self.materials_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) - self.materials_table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection) - self.materials_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) - - layout.addWidget(self.materials_table) - widget.setLayout(layout) - - return widget - - def load_initial_data(self): - """Загрузка начальных данных во все таблицы""" - self.load_partners() - self.load_orders() - self.load_products() - self.load_employees() - self.load_materials() - - def load_partners(self): - """Загрузка данных партнеров""" - self.partners_table.setRowCount(0) - - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute(""" - SELECT partner_id, partner_type, company_name, inn, director_name, - phone, email, rating, total_sales, discount_rate - FROM partners - ORDER BY company_name - """) - rows = cursor.fetchall() - conn.close() - - self.partners_table.setRowCount(len(rows)) - for i, row in enumerate(rows): - for j, val in enumerate(row): - if j in [7, 8, 9] and val is not None: # рейтинг, продажи, скидка - if j == 7: # рейтинг - item = QTableWidgetItem(f"{float(val):.2f}") - elif j == 8: # продажи - item = QTableWidgetItem(f"{float(val):.2f}") - else: # скидка - item = QTableWidgetItem(f"{float(val):.1f}%") - else: - item = QTableWidgetItem(str(val) if val is not None else "") - - item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable) - self.partners_table.setItem(i, j, item) - - def load_orders(self): - """Загрузка данных заявок""" - self.orders_table.setRowCount(0) - - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute(""" - SELECT o.order_id, p.company_name, o.order_date, o.status, - o.total_amount, o.discount_amount, o.final_amount, e.full_name - FROM orders o - JOIN partners p ON o.partner_id = p.partner_id - JOIN employees e ON o.manager_id = e.employee_id - ORDER BY o.order_date DESC - """) - rows = cursor.fetchall() - conn.close() - - self.orders_table.setRowCount(len(rows)) - for i, row in enumerate(rows): - for j, val in enumerate(row): - if j in [4, 5, 6] and val is not None: # суммы - item = QTableWidgetItem(f"{float(val):.2f}") - elif j == 3: # статус - status_text = { - 'NEW': 'Новая', - 'WAITING_PREPAYMENT': 'Ожидает предоплаты', - 'IN_PRODUCTION': 'В производстве', - 'READY_FOR_SHIPMENT': 'Готов к отгрузке', - 'SHIPPED': 'Отгружен', - 'COMPLETED': 'Завершен', - 'CANCELLED': 'Отменен' - }.get(val, val) - item = QTableWidgetItem(status_text) - else: - item = QTableWidgetItem(str(val) if val is not None else "") - - item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable) - self.orders_table.setItem(i, j, item) - - def load_products(self): - """Загрузка данных продукции""" - self.products_table.setRowCount(0) - - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute(""" - SELECT product_id, article_number, product_type, product_name, - min_partner_price, net_weight, gross_weight, production_time_days, - cost_price, workshop_number - FROM products - WHERE is_active = 1 - ORDER BY product_type, product_name - """) - rows = cursor.fetchall() - conn.close() - - self.products_table.setRowCount(len(rows)) - for i, row in enumerate(rows): - for j, val in enumerate(row): - if j in [4, 5, 6, 8] and val is not None: # цены и веса - item = QTableWidgetItem(f"{float(val):.2f}") - else: - item = QTableWidgetItem(str(val) if val is not None else "") - - item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable) - self.products_table.setItem(i, j, item) - - def load_employees(self): - """Загрузка данных сотрудников""" - self.employees_table.setRowCount(0) - - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute(""" - SELECT employee_id, full_name, position, hire_date, salary, - birth_date, has_family, is_active - FROM employees - ORDER BY full_name - """) - rows = cursor.fetchall() - conn.close() - - self.employees_table.setRowCount(len(rows)) - for i, row in enumerate(rows): - for j, val in enumerate(row): - if j == 4 and val is not None: # зарплата - item = QTableWidgetItem(f"{float(val):.2f}") - elif j == 6: # семья - item = QTableWidgetItem("Да" if val else "Нет") - elif j == 7: # статус - item = QTableWidgetItem("Активен" if val else "Неактивен") - else: - item = QTableWidgetItem(str(val) if val is not None else "") - - item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable) - self.employees_table.setItem(i, j, item) - - def load_materials(self): - """Загрузка данных материалов""" - self.materials_table.setRowCount(0) - - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute(""" - SELECT m.material_id, m.material_type, m.material_name, - s.company_name, m.unit_of_measure, m.cost_per_unit, - m.current_stock, m.min_stock_level, m.description - FROM materials m - LEFT JOIN suppliers s ON m.supplier_id = s.supplier_id - ORDER BY m.material_type, m.material_name - """) - rows = cursor.fetchall() - conn.close() - - self.materials_table.setRowCount(len(rows)) - for i, row in enumerate(rows): - for j, val in enumerate(row): - if j in [5, 6, 7] and val is not None: # цена и запасы - item = QTableWidgetItem(f"{float(val):.2f}") - else: - item = QTableWidgetItem(str(val) if val is not None else "") - - item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable) - self.materials_table.setItem(i, j, item) - - def get_selected_partner_id(self): - """Получение ID выбранного партнера""" - selected = self.partners_table.selectedItems() - if not selected: - QMessageBox.warning(self, "Внимание", "Выберите партнёра в таблице.") - return None - - row = selected[0].row() - item = self.partners_table.item(row, 0) - return int(item.text()) if item and item.text() else None - - def get_selected_order_id(self): - """Получение ID выбранной заявки""" - selected = self.orders_table.selectedItems() - if not selected: - QMessageBox.warning(self, "Внимание", "Выберите заявку в таблице.") - return None - - row = selected[0].row() - item = self.orders_table.item(row, 0) - return int(item.text()) if item and item.text() else None - - def get_selected_product_id(self): - """Получение ID выбранной продукции""" - selected = self.products_table.selectedItems() - if not selected: - QMessageBox.warning(self, "Внимание", "Выберите продукт в таблице.") - return None - - row = selected[0].row() - item = self.products_table.item(row, 0) - return int(item.text()) if item and item.text() else None - - def get_selected_employee_id(self): - """Получение ID выбранного сотрудника""" - selected = self.employees_table.selectedItems() - if not selected: - QMessageBox.warning(self, "Внимание", "Выберите сотрудника в таблице.") - return None - - row = selected[0].row() - item = self.employees_table.item(row, 0) - return int(item.text()) if item and item.text() else None - - def get_selected_material_id(self): - """Получение ID выбранного материала""" - selected = self.materials_table.selectedItems() - if not selected: - QMessageBox.warning(self, "Внимание", "Выберите материал в таблице.") - return None - - row = selected[0].row() - item = self.materials_table.item(row, 0) - return int(item.text()) if item and item.text() else None - - # === Методы для партнеров === - def add_partner(self): - """Добавление нового партнера""" - dialog = PartnerDialog() - if dialog.exec() == QDialog.DialogCode.Accepted: - data = dialog.get_data() - self.save_partner_to_db(data) - - def edit_partner(self): - """Редактирование выбранного партнера""" - partner_id = self.get_selected_partner_id() - if not partner_id: - return - - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute("SELECT * FROM partners WHERE partner_id = ?", (partner_id,)) - row = cursor.fetchone() - conn.close() - - if not row: - QMessageBox.warning(self, "Ошибка", "Партнёр не найден.") - return - - # Преобразование в словарь - columns = [description[0] for description in cursor.description] - partner_data = dict(zip(columns, row)) - - dialog = PartnerDialog(partner_data) - if dialog.exec() == QDialog.DialogCode.Accepted: - data = dialog.get_data() - data["partner_id"] = partner_id - self.update_partner_in_db(data) - - def view_sales_history(self): - """Просмотр истории продаж партнера""" - partner_id = self.get_selected_partner_id() - if not partner_id: - return - - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - - # Получение названия компании - cursor.execute("SELECT company_name FROM partners WHERE partner_id = ?", (partner_id,)) - partner_name = cursor.fetchone()[0] - - # Получение истории продаж - cursor.execute(""" - SELECT product_name, quantity, unit_price, total_amount, sale_date - FROM sales - WHERE partner_id = ? - ORDER BY sale_date DESC - """, (partner_id,)) - sales = cursor.fetchall() - conn.close() - - # Создание диалога для отображения истории - dialog = BaseDialog(self) - dialog.setWindowTitle(f"История продаж: {partner_name}") - dialog.setFixedSize(600, 400) - - layout = QVBoxLayout() - - # Таблица продаж - table = QTableWidget() - table.setColumnCount(5) - table.setHorizontalHeaderLabels(["Продукт", "Количество", "Цена", "Сумма", "Дата"]) - table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) - - table.setRowCount(len(sales)) - for i, sale in enumerate(sales): - for j, val in enumerate(sale): - if j in [1, 2, 3] and val is not None: # количества и цены - if j == 1: # количество - item = QTableWidgetItem(f"{float(val):.3f}") - else: # цены - item = QTableWidgetItem(f"{float(val):.2f}") - else: - item = QTableWidgetItem(str(val) if val is not None else "") - table.setItem(i, j, item) - - layout.addWidget(table) - - # Кнопка закрытия - close_btn = QPushButton("Закрыть") - close_btn.clicked.connect(dialog.accept) - layout.addWidget(close_btn) - - dialog.setLayout(layout) - dialog.exec() - - def delete_partner(self): - """Удаление выбранного партнера""" - partner_id = self.get_selected_partner_id() - if not partner_id: - return - - reply = QMessageBox.question( - self, "Подтверждение удаления", - "Вы уверены, что хотите удалить этого партнёра?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - try: - cursor.execute("DELETE FROM partners WHERE partner_id = ?", (partner_id,)) - conn.commit() - QMessageBox.information(self, "Успех", "Партнёр удалён.") - self.load_partners() - except sqlite3.Error as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось удалить партнёра: {e}") - finally: - conn.close() - - def update_partner_discounts(self): - """Обновление скидок для всех партнеров""" - if self.user_role != "manager": - QMessageBox.warning(self, "Ошибка", "Только менеджер может обновлять скидки.") - return - - reply = QMessageBox.question( - self, "Подтверждение", - "Обновить скидки для всех партнеров на основе текущих продаж и рейтингов?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - try: - updated_count = update_all_partners_discounts() - QMessageBox.information( - self, "Успех", - f"Скидки обновлены для {updated_count} партнеров.\n" - f"Скидки рассчитываются по формуле:\n" - f"- Рейтинг × 2% (макс. 10%)\n" - f"- Бонус за количество продаж (макс. 15%)\n" - f"- Бонус за объем продаж (до 2%)\n" - f"- Максимальная скидка: 25%" - ) - self.load_partners() - except Exception as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось обновить скидки: {e}") - - def save_partner_to_db(self, data): - """Сохранение нового партнера в БД""" - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - - try: - cursor.execute(""" - INSERT INTO partners - (partner_type, company_name, legal_address, inn, director_name, phone, email, rating, sales_locations) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - """, ( - data["partner_type"], - data["company_name"], - data["legal_address"], - data["inn"], - data["director_name"], - data["phone"], - data["email"], - data["rating"], - data["sales_locations"] - )) - conn.commit() - QMessageBox.information(self, "Успех", "Партнёр добавлен.") - self.load_partners() - except sqlite3.IntegrityError: - QMessageBox.critical(self, "Ошибка", "Партнёр с таким ИНН уже существует.") - except sqlite3.Error as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось добавить партнёра: {e}") - finally: - conn.close() - - def update_partner_in_db(self, data): - """Обновление данных партнера в БД""" - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - - try: - cursor.execute(""" - UPDATE partners SET - partner_type = ?, company_name = ?, legal_address = ?, inn = ?, - director_name = ?, phone = ?, email = ?, rating = ?, sales_locations = ? - WHERE partner_id = ? - """, ( - data["partner_type"], - data["company_name"], - data["legal_address"], - data["inn"], - data["director_name"], - data["phone"], - data["email"], - data["rating"], - data["sales_locations"], - data["partner_id"] - )) - conn.commit() - QMessageBox.information(self, "Успех", "Данные партнёра обновлены.") - self.load_partners() - except sqlite3.IntegrityError: - QMessageBox.critical(self, "Ошибка", "Партнёр с таким ИНН уже существует.") - except sqlite3.Error as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось обновить данные: {e}") - finally: - conn.close() - - # === Методы для заявок === - def add_order(self): - """Создание новой заявки""" - dialog = OrderDialog() - if dialog.exec() == QDialog.DialogCode.Accepted: - data = dialog.get_data() - self.save_order_to_db(data) - - def edit_order(self): - """Редактирование заявки""" - order_id = self.get_selected_order_id() - if not order_id: - return - - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute("SELECT * FROM orders WHERE order_id = ?", (order_id,)) - row = cursor.fetchone() - conn.close() - - if not row: - QMessageBox.warning(self, "Ошибка", "Заявка не найдена.") - return - - # Преобразование в словарь - columns = [description[0] for description in cursor.description] - order_data = dict(zip(columns, row)) - - dialog = OrderDialog(order_data) - if dialog.exec() == QDialog.DialogCode.Accepted: - data = dialog.get_data() - data["order_id"] = order_id - self.update_order_in_db(data) - - def view_order(self): - """Просмотр деталей заявки""" - order_id = self.get_selected_order_id() - if not order_id: - return - - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - - # Получение основной информации о заявке - cursor.execute(""" - SELECT o.*, p.company_name, e.full_name - FROM orders o - JOIN partners p ON o.partner_id = p.partner_id - JOIN employees e ON o.manager_id = e.employee_id - WHERE o.order_id = ? - """, (order_id,)) - order = cursor.fetchone() - - if not order: - QMessageBox.warning(self, "Ошибка", "Заявка не найдена.") - conn.close() - return - - # Получение позиций заявки - cursor.execute(""" - SELECT p.product_name, oi.quantity, oi.unit_price, oi.total_price - FROM order_items oi - JOIN products p ON oi.product_id = p.product_id - WHERE oi.order_id = ? - """, (order_id,)) - items = cursor.fetchall() - conn.close() - - # Создание диалога для просмотра - dialog = BaseDialog(self) - dialog.setWindowTitle(f"Детали заявки #{order_id}") - dialog.setFixedSize(600, 500) - - layout = QVBoxLayout() - - # Основная информация - info_group = QGroupBox("Информация о заявке") - info_layout = QFormLayout() - - info_layout.addRow("Номер заявки:", QLabel(str(order[0]))) - info_layout.addRow("Партнёр:", QLabel(order[16])) # company_name - info_layout.addRow("Менеджер:", QLabel(order[17])) # full_name - info_layout.addRow("Дата заявки:", QLabel(order[3])) - info_layout.addRow("Статус:", QLabel(order[4])) - info_layout.addRow("Общая сумма:", QLabel(f"{order[5]:.2f} руб.")) - info_layout.addRow("Скидка:", QLabel(f"{order[6]:.2f} руб.")) - info_layout.addRow("Итоговая сумма:", QLabel(f"{order[7]:.2f} руб.")) - - info_group.setLayout(info_layout) - layout.addWidget(info_group) - - # Позиции заявки - items_group = QGroupBox("Позиции заявки") - items_layout = QVBoxLayout() - - table = QTableWidget() - table.setColumnCount(4) - table.setHorizontalHeaderLabels(["Продукт", "Количество", "Цена", "Сумма"]) - table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) - - table.setRowCount(len(items)) - total = 0 - for i, item in enumerate(items): - for j, val in enumerate(item): - if j in [1, 2, 3] and val is not None: - item_text = f"{float(val):.2f}" if j in [2, 3] else f"{float(val):.3f}" - table.setItem(i, j, QTableWidgetItem(item_text)) - else: - table.setItem(i, j, QTableWidgetItem(str(val))) - total += float(item[3]) - - items_layout.addWidget(table) - items_group.setLayout(items_layout) - layout.addWidget(items_group) - - # Кнопка закрытия - close_btn = QPushButton("Закрыть") - close_btn.clicked.connect(dialog.accept) - layout.addWidget(close_btn) - - dialog.setLayout(layout) - dialog.exec() - - def update_order_status(self): - """Обновление статуса заявки""" - order_id = self.get_selected_order_id() - if not order_id: - return - - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute("SELECT status FROM orders WHERE order_id = ?", (order_id,)) - current_status = cursor.fetchone()[0] - conn.close() - - dialog = BaseDialog(self) - dialog.setWindowTitle("Обновление статуса заявки") - dialog.setFixedSize(300, 150) - - layout = QVBoxLayout() - - layout.addWidget(QLabel("Текущий статус: " + current_status)) - - status_combo = QComboBox() - status_combo.addItems(["NEW", "WAITING_PREPAYMENT", "IN_PRODUCTION", "READY_FOR_SHIPMENT", "SHIPPED", "COMPLETED", "CANCELLED"]) - status_combo.setCurrentText(current_status) - layout.addWidget(QLabel("Новый статус:")) - layout.addWidget(status_combo) - - btn_layout = QHBoxLayout() - save_btn = QPushButton("Сохранить") - cancel_btn = QPushButton("Отмена") - - btn_layout.addWidget(save_btn) - btn_layout.addWidget(cancel_btn) - layout.addLayout(btn_layout) - - dialog.setLayout(layout) - - def save_status(): - new_status = status_combo.currentText() - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - cursor.execute("UPDATE orders SET status = ? WHERE order_id = ?", (new_status, order_id)) - conn.commit() - conn.close() - QMessageBox.information(self, "Успех", "Статус заявки обновлен.") - self.load_orders() - dialog.accept() - - save_btn.clicked.connect(save_status) - cancel_btn.clicked.connect(dialog.reject) - - dialog.exec() - - def delete_order(self): - """Удаление заявки""" - order_id = self.get_selected_order_id() - if not order_id: - return - - reply = QMessageBox.question( - self, "Подтверждение удаления", - "Вы уверены, что хотите удалить эту заявку?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - try: - cursor.execute("DELETE FROM orders WHERE order_id = ?", (order_id,)) - conn.commit() - QMessageBox.information(self, "Успех", "Заявка удалена.") - self.load_orders() - except sqlite3.Error as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось удалить заявку: {e}") - finally: - conn.close() - - def save_order_to_db(self, data): - """Сохранение заявки в БД""" - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - - try: - # Вставка основной информации о заявке - cursor.execute(""" - INSERT INTO orders - (partner_id, manager_id, order_date, status, expected_production_date, - delivery_method, delivery_address, notes, total_amount, final_amount) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, ( - data["partner_id"], - self.user_id, # ID текущего менеджера - data["order_date"], - data["status"], - data["expected_production_date"], - data["delivery_method"], - data["delivery_address"], - data["notes"], - data["total_amount"], - data["total_amount"] # final_amount = total_amount (без скидки в демо) - )) - - order_id = cursor.lastrowid - - # Вставка позиций заявки - for item in data["order_items"]: - cursor.execute(""" - INSERT INTO order_items - (order_id, product_id, quantity, unit_price, total_price) - VALUES (?, ?, ?, ?, ?) - """, ( - order_id, - item["product_id"], - item["quantity"], - item["unit_price"], - item["total_price"] - )) - - conn.commit() - QMessageBox.information(self, "Успех", "Заявка создана.") - self.load_orders() - - except sqlite3.Error as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось создать заявку: {e}") - finally: - conn.close() - - def update_order_in_db(self, data): - """Обновление заявки в БД""" - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - - try: - # Обновление основной информации о заявке - cursor.execute(""" - UPDATE orders SET - partner_id = ?, status = ?, order_date = ?, expected_production_date = ?, - delivery_method = ?, delivery_address = ?, notes = ?, total_amount = ?, final_amount = ? - WHERE order_id = ? - """, ( - data["partner_id"], - data["status"], - data["order_date"], - data["expected_production_date"], - data["delivery_method"], - data["delivery_address"], - data["notes"], - data["total_amount"], - data["total_amount"], - data["order_id"] - )) - - # Удаляем старые позиции и добавляем новые - cursor.execute("DELETE FROM order_items WHERE order_id = ?", (data["order_id"],)) - - for item in data["order_items"]: - cursor.execute(""" - INSERT INTO order_items - (order_id, product_id, quantity, unit_price, total_price) - VALUES (?, ?, ?, ?, ?) - """, ( - data["order_id"], - item["product_id"], - item["quantity"], - item["unit_price"], - item["total_price"] - )) - - conn.commit() - QMessageBox.information(self, "Успех", "Заявка обновлена.") - self.load_orders() - - except sqlite3.Error as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось обновить заявку: {e}") - finally: - conn.close() - - # === Методы для продукции (заглушки) === - def add_product(self): - QMessageBox.information(self, "Информация", "Добавление продукции будет реализовано в полной версии.") - - def edit_product(self): - QMessageBox.information(self, "Информация", "Редактирование продукции будет реализовано в полной версии.") - - def view_product_materials(self): - QMessageBox.information(self, "Информация", "Просмотр состава продукции будет реализовано в полной версии.") - - def delete_product(self): - product_id = self.get_selected_product_id() - if not product_id: - return - - reply = QMessageBox.question( - self, "Подтверждение удаления", - "Вы уверены, что хотите удалить этот продукт?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - try: - cursor.execute("UPDATE products SET is_active = 0 WHERE product_id = ?", (product_id,)) - conn.commit() - QMessageBox.information(self, "Успех", "Продукт удален.") - self.load_products() - except sqlite3.Error as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось удалить продукт: {e}") - finally: - conn.close() - - # === Методы для сотрудников (заглушки) === - def add_employee(self): - QMessageBox.information(self, "Информация", "Добавление сотрудника будет реализовано в полной версии.") - - def edit_employee(self): - QMessageBox.information(self, "Информация", "Редактирование сотрудника будет реализовано в полной версии.") - - def view_equipment_access(self): - QMessageBox.information(self, "Информация", "Просмотр доступа к оборудованию будет реализовано в полной версии.") - - def delete_employee(self): - employee_id = self.get_selected_employee_id() - if not employee_id: - return - - reply = QMessageBox.question( - self, "Подтверждение удаления", - "Вы уверены, что хотите удалить этого сотрудника?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - try: - cursor.execute("UPDATE employees SET is_active = 0 WHERE employee_id = ?", (employee_id,)) - conn.commit() - QMessageBox.information(self, "Успех", "Сотрудник удален.") - self.load_employees() - except sqlite3.Error as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось удалить сотрудника: {e}") - finally: - conn.close() - - # === Методы для материалов (заглушки) === - def add_material(self): - QMessageBox.information(self, "Информация", "Добавление материала будет реализовано в полной версии.") - - def edit_material(self): - QMessageBox.information(self, "Информация", "Редактирование материала будет реализовано в полной версии.") - - def view_stock_history(self): - QMessageBox.information(self, "Информация", "Просмотр истории запасов будет реализовано в полной версии.") - - def delete_material(self): - material_id = self.get_selected_material_id() - if not material_id: - return - - reply = QMessageBox.question( - self, "Подтверждение удаления", - "Вы уверены, что хотите удалить этот материал?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - conn = sqlite3.connect('masterpol.db') - cursor = conn.cursor() - try: - cursor.execute("DELETE FROM materials WHERE material_id = ?", (material_id,)) - conn.commit() - QMessageBox.information(self, "Успех", "Материал удален.") - self.load_materials() - except sqlite3.Error as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось удалить материал: {e}") - finally: - conn.close() - - -# === Точка входа === -if __name__ == "__main__": - # Инициализация базы данных - try: - init_database() - print("✅ База данных успешно инициализирована") - except Exception as e: - print(f"❌ Ошибка инициализации БД: {e}") - sys.exit(1) - - # Создание приложения - app = QApplication(sys.argv) - app.setFont(QFont(APP_STYLES['font_family'], 10)) - - # Авторизация - auth_dialog = AuthDialog() - if auth_dialog.exec() != QDialog.DialogCode.Accepted: - sys.exit(0) - - # Создание главного окна - main_window = MainWindow(auth_dialog.user_id, auth_dialog.user_name, auth_dialog.user_role) - main_window.show() - - sys.exit(app.exec()) diff --git a/masterpol.db b/masterpol.db index 6ff4e21..12f3f3d 100644 Binary files a/masterpol.db and b/masterpol.db differ diff --git a/ressult/.env b/ressult/.env deleted file mode 100644 index 5a2d5c7..0000000 --- a/ressult/.env +++ /dev/null @@ -1,6 +0,0 @@ -# .env -DATABASE_URL=postgresql://postgres:213k2010###@localhost/masterpol -SECRET_KEY=your-secret-key-here -DEBUG=True -HOST=0.0.0.0 -PORT=8000 diff --git a/ressult/app/__pycache__/database.cpython-314.pyc b/ressult/app/__pycache__/database.cpython-314.pyc deleted file mode 100644 index a03691d..0000000 Binary files a/ressult/app/__pycache__/database.cpython-314.pyc and /dev/null differ diff --git a/ressult/app/__pycache__/main.cpython-314.pyc b/ressult/app/__pycache__/main.cpython-314.pyc deleted file mode 100644 index b1c1369..0000000 Binary files a/ressult/app/__pycache__/main.cpython-314.pyc and /dev/null differ diff --git a/ressult/app/database.py b/ressult/app/database.py deleted file mode 100644 index 8006102..0000000 --- a/ressult/app/database.py +++ /dev/null @@ -1,60 +0,0 @@ -# app/database.py -""" -Модуль для работы с базой данных PostgreSQL -Соответствует требованиям ТЗ по разработке базы данных -""" -import os -import psycopg2 -from psycopg2.extras import RealDictCursor -from dotenv import load_dotenv -import time - -load_dotenv() - -class Database: - def __init__(self): - self.connection = None - self.max_retries = 3 - self.retry_delay = 1 - - def get_connection(self): - """Получение подключения к базе данных с повторными попытками""" - if self.connection is None or self.connection.closed: - for attempt in range(self.max_retries): - try: - self.connection = psycopg2.connect( - os.getenv('DATABASE_URL'), - cursor_factory=RealDictCursor - ) - break - except psycopg2.OperationalError as e: - if attempt < self.max_retries - 1: - time.sleep(self.retry_delay) - continue - else: - raise e - return self.connection - - def execute_query(self, query, params=None): - """Выполнение SQL запроса с обработкой ошибок""" - conn = self.get_connection() - try: - with conn.cursor() as cursor: - cursor.execute(query, params) - if query.strip().upper().startswith('SELECT'): - return cursor.fetchall() - conn.commit() - return cursor.rowcount - except psycopg2.InterfaceError: - self.connection = None - raise - except Exception as e: - conn.rollback() - raise e - - def close(self): - """Закрытие соединения с базой данных""" - if self.connection and not self.connection.closed: - self.connection.close() - -db = Database() diff --git a/ressult/app/main.py b/ressult/app/main.py deleted file mode 100644 index 65beb14..0000000 --- a/ressult/app/main.py +++ /dev/null @@ -1,48 +0,0 @@ -# app/main.py -""" -Главный модуль FastAPI приложения -Соответствует требованиям ТЗ по интеграции модулей -""" -import os -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware -from dotenv import load_dotenv -from app.routes import partners, sales, upload, calculations, auth, config - -load_dotenv() - -app = FastAPI( - title="MasterPol Partner Management System", - description="REST API для системы управления партнерами согласно ТЗ демонстрационного экзамена", - version="1.0.0" -) - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# Регистрация маршрутов согласно модулям ТЗ -app.include_router(partners.router, prefix="/api/v1/partners", tags=["Partners Management"]) -app.include_router(sales.router, prefix="/api/v1/sales", tags=["Sales History"]) -app.include_router(upload.router, prefix="/api/v1/upload", tags=["Data Import"]) -app.include_router(calculations.router, prefix="/api/v1/calculations", tags=["Calculations"]) -app.include_router(config.router, prefix="/api/v1/config", tags=["Configuration"]) -app.include_router(auth.router, prefix="/api/v1/auth", tags=["Authentication"]) - -@app.get("/") -async def root(): - """Корневой endpoint системы""" - return { - "message": "MasterPol Partner Management System API", - "version": "1.0.0", - "description": "Система управления партнерами согласно ТЗ демонстрационного экзамена" - } - -@app.get("/health") -async def health_check(): - """Проверка здоровья приложения""" - return {"status": "healthy"} diff --git a/ressult/app/models/__init__.py b/ressult/app/models/__init__.py deleted file mode 100644 index 10fccba..0000000 --- a/ressult/app/models/__init__.py +++ /dev/null @@ -1,75 +0,0 @@ -# app/models/__init__.py -""" -Модели данных Pydantic для валидации API запросов и ответов -Соответствует ТЗ демонстрационного экзамена -""" -from pydantic import BaseModel, EmailStr, validator, conint -from typing import Optional -from decimal import Decimal - -class PartnerBase(BaseModel): - partner_type: Optional[str] = None - company_name: str - legal_address: Optional[str] = None - inn: str - director_name: Optional[str] = None - phone: Optional[str] = None - email: Optional[EmailStr] = None - rating: conint(ge=0) # Рейтинг должен быть целым неотрицательным числом - sales_locations: Optional[str] = None - - @validator('phone') - def validate_phone(cls, v): - if v and not v.startswith('+'): - raise ValueError('Телефон должен начинаться с +') - return v - -class PartnerCreate(PartnerBase): - pass - -class PartnerUpdate(PartnerBase): - pass - -class Partner(PartnerBase): - partner_id: int - - class Config: - from_attributes = True - -class SaleBase(BaseModel): - partner_id: int - product_name: str - quantity: Decimal - sale_date: str - -class SaleCreate(SaleBase): - pass - -class Sale(SaleBase): - sale_id: int - - class Config: - from_attributes = True - -class UploadResponse(BaseModel): - message: str - processed_rows: int - errors: list[str] = [] - -class MaterialCalculationRequest(BaseModel): - product_type_id: int - material_type_id: int - quantity: conint(ge=1) - param1: float - param2: float - product_coeff: float - defect_percent: float - -class MaterialCalculationResponse(BaseModel): - material_quantity: int - status: str - -class DiscountResponse(BaseModel): - partner_id: int - total_sales: Decimal - discount_percent: int diff --git a/ressult/app/models/__pycache__/__init__.cpython-314.pyc b/ressult/app/models/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index b4c160b..0000000 Binary files a/ressult/app/models/__pycache__/__init__.cpython-314.pyc and /dev/null differ diff --git a/ressult/app/routes/__init__.py b/ressult/app/routes/__init__.py deleted file mode 100644 index 23f8410..0000000 --- a/ressult/app/routes/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# app/routes/__init__.py -""" -Инициализация маршрутов API -""" -from . import partners, sales, upload, calculations, auth, config diff --git a/ressult/app/routes/__pycache__/__init__.cpython-314.pyc b/ressult/app/routes/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index 0ab0a5d..0000000 Binary files a/ressult/app/routes/__pycache__/__init__.cpython-314.pyc and /dev/null differ diff --git a/ressult/app/routes/__pycache__/auth.cpython-314.pyc b/ressult/app/routes/__pycache__/auth.cpython-314.pyc deleted file mode 100644 index 76a68b9..0000000 Binary files a/ressult/app/routes/__pycache__/auth.cpython-314.pyc and /dev/null differ diff --git a/ressult/app/routes/__pycache__/calculations.cpython-314.pyc b/ressult/app/routes/__pycache__/calculations.cpython-314.pyc deleted file mode 100644 index cf8e18a..0000000 Binary files a/ressult/app/routes/__pycache__/calculations.cpython-314.pyc and /dev/null differ diff --git a/ressult/app/routes/__pycache__/config.cpython-314.pyc b/ressult/app/routes/__pycache__/config.cpython-314.pyc deleted file mode 100644 index f2a5e23..0000000 Binary files a/ressult/app/routes/__pycache__/config.cpython-314.pyc and /dev/null differ diff --git a/ressult/app/routes/__pycache__/partners.cpython-314.pyc b/ressult/app/routes/__pycache__/partners.cpython-314.pyc deleted file mode 100644 index 0a432e2..0000000 Binary files a/ressult/app/routes/__pycache__/partners.cpython-314.pyc and /dev/null differ diff --git a/ressult/app/routes/__pycache__/sales.cpython-314.pyc b/ressult/app/routes/__pycache__/sales.cpython-314.pyc deleted file mode 100644 index 77df468..0000000 Binary files a/ressult/app/routes/__pycache__/sales.cpython-314.pyc and /dev/null differ diff --git a/ressult/app/routes/__pycache__/upload.cpython-314.pyc b/ressult/app/routes/__pycache__/upload.cpython-314.pyc deleted file mode 100644 index a09ba33..0000000 Binary files a/ressult/app/routes/__pycache__/upload.cpython-314.pyc and /dev/null differ diff --git a/ressult/app/routes/auth.py b/ressult/app/routes/auth.py deleted file mode 100644 index acae8ac..0000000 --- a/ressult/app/routes/auth.py +++ /dev/null @@ -1,45 +0,0 @@ -# app/routes/auth.py -""" -Маршруты API для аутентификации -""" -from fastapi import APIRouter, HTTPException, Depends -from fastapi.security import HTTPBasic, HTTPBasicCredentials -from app.database import db -import bcrypt - -router = APIRouter() -security = HTTPBasic() - -@router.post("/login") -async def login(credentials: HTTPBasicCredentials = Depends(security)): - """Аутентификация менеджера""" - try: - result = db.execute_query( - "SELECT manager_id, username, password_hash, full_name FROM managers WHERE username = %s AND is_active = TRUE", - (credentials.username,) - ) - - if not result: - raise HTTPException(status_code=401, detail="Invalid credentials") - - manager = dict(result[0]) - stored_hash = manager['password_hash'] - - # Проверка пароля - if bcrypt.checkpw(credentials.password.encode('utf-8'), stored_hash.encode('utf-8')): - return { - "manager_id": manager['manager_id'], - "username": manager['username'], - "full_name": manager['full_name'], - "authenticated": True - } - else: - raise HTTPException(status_code=401, detail="Invalid credentials") - - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.get("/verify") -async def verify_token(): - """Проверка валидности токена""" - return {"verified": True} diff --git a/ressult/app/routes/calculations.py b/ressult/app/routes/calculations.py deleted file mode 100644 index c41200b..0000000 --- a/ressult/app/routes/calculations.py +++ /dev/null @@ -1,43 +0,0 @@ -# app/routes/calculations.py -""" -Маршруты API для расчетов -Соответствует модулю 4 ТЗ по расчету материалов -""" -from fastapi import APIRouter, HTTPException -from app.models import MaterialCalculationRequest, MaterialCalculationResponse -import math - -router = APIRouter() - -@router.post("/calculate-material", response_model=MaterialCalculationResponse) -async def calculate_material(request: MaterialCalculationRequest): - """ - Расчет количества материала для производства продукции - Соответствует модулю 4 ТЗ - """ - try: - # Валидация входных параметров - if (request.param1 <= 0 or request.param2 <= 0 or - request.product_coeff <= 0 or request.defect_percent < 0): - return MaterialCalculationResponse( - material_quantity=-1, - status="error: invalid parameters" - ) - - # Расчет количества материала на одну единицу продукции - material_per_unit = request.param1 * request.param2 * request.product_coeff - - # Расчет общего количества материала с учетом брака - total_material = material_per_unit * request.quantity - total_material_with_defect = total_material * (1 + request.defect_percent / 100) - - # Округление до целого числа в большую сторону - material_quantity = math.ceil(total_material_with_defect) - - return MaterialCalculationResponse( - material_quantity=material_quantity, - status="success" - ) - - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) diff --git a/ressult/app/routes/config.py b/ressult/app/routes/config.py deleted file mode 100644 index 10e68ea..0000000 --- a/ressult/app/routes/config.py +++ /dev/null @@ -1,32 +0,0 @@ -# app/routes/config.py -""" -Маршруты API для управления конфигурацией -""" -from fastapi import APIRouter, HTTPException -from pathlib import Path -import json - -router = APIRouter() - -CONFIG_PATH = Path(__file__).parent.parent.parent / "config.json" - -@router.get("/") -async def get_config(): - """Получение текущей конфигурации""" - try: - if CONFIG_PATH.exists(): - with open(CONFIG_PATH, 'r', encoding='utf-8') as f: - return json.load(f) - return {"message": "Config file not found"} - except Exception as e: - raise HTTPException(status_code=500, detail=f"Error reading config: {str(e)}") - -@router.put("/") -async def update_config(config_data: dict): - """Обновление конфигурации""" - try: - with open(CONFIG_PATH, 'w', encoding='utf-8') as f: - json.dump(config_data, f, indent=4, ensure_ascii=False) - return {"message": "Configuration updated successfully"} - except Exception as e: - raise HTTPException(status_code=500, detail=f"Error saving config: {str(e)}") diff --git a/ressult/app/routes/partners.py b/ressult/app/routes/partners.py deleted file mode 100644 index 8c64889..0000000 --- a/ressult/app/routes/partners.py +++ /dev/null @@ -1,157 +0,0 @@ -# app/routes/partners.py -""" -Маршруты API для управления партнерами -Соответствует модулям 1-3 ТЗ -""" -from fastapi import APIRouter, HTTPException -from app.database import db -from app.models import Partner, PartnerCreate, PartnerUpdate, DiscountResponse -from decimal import Decimal - -router = APIRouter() - -@router.get("/") -async def get_partners(): - """ - Получение списка всех партнеров - Соответствует требованию просмотра списка партнеров - """ - try: - result = db.execute_query(""" - SELECT partner_id, partner_type, company_name, legal_address, - inn, director_name, phone, email, rating, sales_locations - FROM partners - ORDER BY company_name - """) - - partners_list = [] - for row in result: - partner_dict = dict(row) - # Преобразуем рейтинг к int если нужно - if isinstance(partner_dict.get('rating'), float): - partner_dict['rating'] = int(partner_dict['rating']) - partners_list.append(partner_dict) - - return partners_list - - except Exception as e: - if "relation \"partners\" does not exist" in str(e): - return [] - raise HTTPException(status_code=500, detail=str(e)) - -@router.get("/{partner_id}") -async def get_partner(partner_id: int): - """Получение информации о конкретном партнере""" - try: - result = db.execute_query( - "SELECT * FROM partners WHERE partner_id = %s", - (partner_id,) - ) - if not result: - raise HTTPException(status_code=404, detail="Partner not found") - - partner_data = dict(result[0]) - # Преобразуем рейтинг к int если нужно - if isinstance(partner_data.get('rating'), float): - partner_data['rating'] = int(partner_data['rating']) - - return partner_data - - except Exception as e: - if "relation \"partners\" does not exist" in str(e): - raise HTTPException(status_code=404, detail="Partner not found") - raise HTTPException(status_code=500, detail=str(e)) - -@router.post("/") -async def create_partner(partner: PartnerCreate): - """ - Создание нового партнера - Включает валидацию данных согласно ТЗ - """ - try: - result = db.execute_query(""" - INSERT INTO partners - (partner_type, company_name, legal_address, inn, director_name, - phone, email, rating, sales_locations) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) - RETURNING partner_id - """, ( - partner.partner_type, partner.company_name, partner.legal_address, - partner.inn, partner.director_name, partner.phone, partner.email, - partner.rating, partner.sales_locations - )) - return {"partner_id": result[0]["partner_id"]} - except Exception as e: - if "duplicate key value violates unique constraint" in str(e): - raise HTTPException(status_code=400, detail="Partner with this INN already exists") - raise HTTPException(status_code=500, detail=str(e)) - -@router.put("/{partner_id}") -async def update_partner(partner_id: int, partner: PartnerUpdate): - """ - Обновление данных партнера - Соответствует требованию редактирования данных партнера - """ - try: - db.execute_query(""" - UPDATE partners SET - partner_type = %s, company_name = %s, legal_address = %s, - inn = %s, director_name = %s, phone = %s, email = %s, - rating = %s, sales_locations = %s - WHERE partner_id = %s - """, ( - partner.partner_type, partner.company_name, partner.legal_address, - partner.inn, partner.director_name, partner.phone, partner.email, - partner.rating, partner.sales_locations, partner_id - )) - return {"message": "Partner updated successfully"} - except Exception as e: - if "duplicate key value violates unique constraint" in str(e): - raise HTTPException(status_code=400, detail="Partner with this INN already exists") - raise HTTPException(status_code=500, detail=str(e)) - -@router.delete("/{partner_id}") -async def delete_partner(partner_id: int): - """Удаление партнера""" - try: - db.execute_query( - "DELETE FROM partners WHERE partner_id = %s", - (partner_id,) - ) - return {"message": "Partner deleted successfully"} - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.get("/{partner_id}/discount", response_model=DiscountResponse) -async def calculate_partner_discount(partner_id: int): - """ - Расчет скидки для партнера на основе общего количества продаж - Соответствует модулю 2 ТЗ - """ - try: - # Получаем общее количество продаж партнера - result = db.execute_query(""" - SELECT COALESCE(SUM(quantity), 0) as total_sales - FROM sales WHERE partner_id = %s - """, (partner_id,)) - - total_sales = result[0]["total_sales"] if result else Decimal('0') - - # Расчет скидки согласно бизнес-правилам ТЗ - if total_sales < 10000: - discount = 0 - elif total_sales < 50000: - discount = 5 - elif total_sales < 300000: - discount = 10 - else: - discount = 15 - - return DiscountResponse( - partner_id=partner_id, - total_sales=total_sales, - discount_percent=discount - ) - - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) diff --git a/ressult/app/routes/sales.py b/ressult/app/routes/sales.py deleted file mode 100644 index fac35e0..0000000 --- a/ressult/app/routes/sales.py +++ /dev/null @@ -1,64 +0,0 @@ -# app/routes/sales.py -""" -Маршруты API для управления продажами -Соответствует требованиям ТЗ по истории реализации продукции -""" -from fastapi import APIRouter, HTTPException -from app.database import db -from app.models import Sale, SaleCreate - -router = APIRouter() - -@router.get("/partner/{partner_id}") -async def get_sales_by_partner(partner_id: int): - """ - Получение истории реализации продукции партнером - Соответствует модулю 4 ТЗ - """ - try: - result = db.execute_query(""" - SELECT sale_id, partner_id, product_name, quantity, sale_date - FROM sales - WHERE partner_id = %s - ORDER BY sale_date DESC - """, (partner_id,)) - return [dict(row) for row in result] - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.get("/") -async def get_all_sales(): - """Получение всех продаж с информацией о партнерах""" - try: - result = db.execute_query(""" - SELECT s.sale_id, s.partner_id, p.company_name, s.product_name, - s.quantity, s.sale_date - FROM sales s - JOIN partners p ON s.partner_id = p.partner_id - ORDER BY s.sale_date DESC - """) - return [dict(row) for row in result] - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.post("/") -async def create_sale(sale: SaleCreate): - """Создание новой записи о продаже""" - try: - result = db.execute_query(""" - INSERT INTO sales (partner_id, product_name, quantity, sale_date) - VALUES (%s, %s, %s, %s) - RETURNING sale_id - """, (sale.partner_id, sale.product_name, sale.quantity, sale.sale_date)) - return {"sale_id": result[0]["sale_id"]} - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.delete("/{sale_id}") -async def delete_sale(sale_id: int): - """Удаление записи о продаже""" - try: - db.execute_query("DELETE FROM sales WHERE sale_id = %s", (sale_id,)) - return {"message": "Sale deleted successfully"} - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) diff --git a/ressult/app/routes/upload.py b/ressult/app/routes/upload.py deleted file mode 100644 index 5e4339d..0000000 --- a/ressult/app/routes/upload.py +++ /dev/null @@ -1,103 +0,0 @@ -# app/routes/upload.py -""" -Маршруты API для загрузки и импорта данных -Соответствует требованиям ТЗ по импорту данных -""" -import pandas as pd -from fastapi import APIRouter, UploadFile, File, HTTPException -from app.database import db -from app.models import UploadResponse - -router = APIRouter() - -@router.post("/partners") -async def upload_partners(file: UploadFile = File(...)): - """ - Загрузка партнеров из файла - Подготовка данных для импорта согласно ТЗ - """ - try: - if file.filename.endswith('.xlsx'): - df = pd.read_excel(file.file) - elif file.filename.endswith('.csv'): - df = pd.read_csv(file.file) - else: - raise HTTPException(status_code=400, detail="Unsupported file format") - - processed = 0 - errors = [] - - for index, row in df.iterrows(): - try: - # Валидация и преобразование данных - rating = row.get('rating', 0) - if pd.isna(rating): - rating = 0 - - db.execute_query(""" - INSERT INTO partners - (partner_type, company_name, legal_address, inn, director_name, - phone, email, rating, sales_locations) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) - """, ( - row.get('partner_type'), - row.get('company_name'), - row.get('legal_address'), - row.get('inn'), - row.get('director_name'), - row.get('phone'), - row.get('email'), - int(rating), # Конвертация в целое число - row.get('sales_locations') - )) - processed += 1 - except Exception as e: - errors.append(f"Row {index}: {str(e)}") - - return UploadResponse( - message="File processed successfully", - processed_rows=processed, - errors=errors - ) - - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.post("/sales") -async def upload_sales(file: UploadFile = File(...)): - """Загрузка продаж из файла""" - try: - if file.filename.endswith('.xlsx'): - df = pd.read_excel(file.file) - elif file.filename.endswith('.csv'): - df = pd.read_csv(file.file) - else: - raise HTTPException(status_code=400, detail="Unsupported file format") - - processed = 0 - errors = [] - - for index, row in df.iterrows(): - try: - db.execute_query(""" - INSERT INTO sales - (partner_id, product_name, quantity, sale_date) - VALUES (%s, %s, %s, %s) - """, ( - int(row.get('partner_id')), - row.get('product_name'), - row.get('quantity'), - row.get('sale_date') - )) - processed += 1 - except Exception as e: - errors.append(f"Row {index}: {str(e)}") - - return UploadResponse( - message="File processed successfully", - processed_rows=processed, - errors=errors - ) - - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) diff --git a/ressult/config.json b/ressult/config.json deleted file mode 100644 index a73381d..0000000 --- a/ressult/config.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "application": { - "name": "MasterPol Partner Management System", - "version": "1.0.0", - "company_logo": "resources/logo.png", - "app_icon": "resources/icon.png" - }, - "api": { - "base_url": "http://localhost:8000", - "timeout": 30 - }, - "style": { - "primary_color": "#007acc", - "secondary_color": "#005a9e", - "accent_color": "#28a745", - "font_family": "Arial", - "font_size": "12px" - }, - "features": { - "enable_import": true, - "enable_export": true, - "enable_calculations": true - } -} diff --git a/ressult/database_init.py b/ressult/database_init.py deleted file mode 100644 index a0ac362..0000000 --- a/ressult/database_init.py +++ /dev/null @@ -1,196 +0,0 @@ -# database_init.py -""" -Скрипт инициализации базы данных с исправлением ошибки типа данных -""" -import argparse -import sys -import os -from app.database import db -import bcrypt - -def parse_arguments(): - """Парсинг аргументов командной строки""" - parser = argparse.ArgumentParser(description='Инициализация базы данных MasterPol') - parser.add_argument('--host', default='localhost', help='Хост PostgreSQL') - parser.add_argument('--port', default='5432', help='Порт PostgreSQL') - parser.add_argument('--database', default='masterpol', help='Имя базы данных') - parser.add_argument('--username', default='postgres', help='Имя пользователя PostgreSQL') - parser.add_argument('--password', required=True, help='Пароль пользователя PostgreSQL') - - return parser.parse_args() - -def initialize_database(db_url): - """Инициализация структуры базы данных с тестовыми данными""" - - # Устанавливаем URL базы данных - os.environ['DATABASE_URL'] = db_url - - # Удаляем существующие таблицы (для чистой инициализации) - drop_tables = """ - DROP TABLE IF EXISTS sales CASCADE; - DROP TABLE IF EXISTS partners CASCADE; - DROP TABLE IF EXISTS managers CASCADE; - """ - - # Создание таблицы партнеров с правильным типом для rating - partners_table = """ - CREATE TABLE IF NOT EXISTS partners ( - partner_id SERIAL PRIMARY KEY, - partner_type VARCHAR(50), - company_name VARCHAR(255) NOT NULL, - legal_address TEXT, - inn VARCHAR(20) UNIQUE NOT NULL, - director_name VARCHAR(255), - phone VARCHAR(50), - email VARCHAR(255), - rating INTEGER NOT NULL DEFAULT 0 CHECK (rating >= 0 AND rating <= 100), - sales_locations TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - """ - - # Создание таблицы продаж - sales_table = """ - CREATE TABLE IF NOT EXISTS sales ( - sale_id SERIAL PRIMARY KEY, - partner_id INTEGER NOT NULL REFERENCES partners(partner_id) ON DELETE CASCADE, - product_name VARCHAR(255) NOT NULL, - quantity DECIMAL(15,2) NOT NULL, - sale_date DATE NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - """ - - # Создание таблицы менеджеров - managers_table = """ - CREATE TABLE IF NOT EXISTS managers ( - manager_id SERIAL PRIMARY KEY, - username VARCHAR(100) UNIQUE NOT NULL, - password_hash VARCHAR(255) NOT NULL, - full_name VARCHAR(255) NOT NULL, - is_active BOOLEAN DEFAULT TRUE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - """ - - try: - # Удаляем существующие таблицы - try: - db.execute_query(drop_tables) - print("✅ Существующие таблицы удалены") - except Exception as e: - print(f"ℹ️ Таблицы для удаления не найдены: {e}") - - # Создание таблиц - db.execute_query(partners_table) - db.execute_query(sales_table) - db.execute_query(managers_table) - print("✅ База данных успешно инициализирована") - - # Создание тестового менеджера - password = "pass123" - hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') - - db.execute_query(""" - INSERT INTO managers (username, password_hash, full_name) - VALUES ('manager', %s, 'Тестовый Менеджер') - ON CONFLICT (username) DO NOTHING - """, (hashed_password,)) - print("✅ Тестовый пользователь создан (manager/pass123)") - - # Добавление тестовых партнеров - test_partners = [ - { - 'partner_type': 'distributor', - 'company_name': 'ООО "Ромашка"', - 'legal_address': 'г. Москва, ул. Ленина, д. 1', - 'inn': '1234567890', - 'director_name': 'Иванов Иван Иванович', - 'phone': '+79991234567', - 'email': 'info@romashka.ru', - 'rating': 85, # INTEGER значение от 0 до 100 - 'sales_locations': 'Москва, Санкт-Петербург' - }, - { - 'partner_type': 'retail', - 'company_name': 'ИП Петров', - 'legal_address': 'г. Санкт-Петербург, Невский пр., д. 100', - 'inn': '0987654321', - 'director_name': 'Петров Петр Петрович', - 'phone': '+79998765432', - 'email': 'petrov@mail.ru', - 'rating': 72, # INTEGER значение от 0 до 100 - 'sales_locations': 'Санкт-Петербург' - } - ] - - for partner in test_partners: - db.execute_query(""" - INSERT INTO partners - (partner_type, company_name, legal_address, inn, director_name, - phone, email, rating, sales_locations) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) - """, ( - partner['partner_type'], partner['company_name'], - partner['legal_address'], partner['inn'], - partner['director_name'], partner['phone'], - partner['email'], partner['rating'], - partner['sales_locations'] - )) - - print("✅ Тестовые партнеры добавлены") - - # Добавление тестовых продаж - test_sales = [ - (1, 'Продукт А', 150.50, '2024-01-15'), - (1, 'Продукт Б', 75.25, '2024-01-16'), - (2, 'Продукт В', 200.00, '2024-01-17'), - (1, 'Продукт А', 100.00, '2024-01-18') - ] - - for sale in test_sales: - db.execute_query(""" - INSERT INTO sales (partner_id, product_name, quantity, sale_date) - VALUES (%s, %s, %s, %s) - """, sale) - - print("✅ Тестовые продажи добавлены") - - # Проверяем, что данные корректно добавлены - partners_count = db.execute_query("SELECT COUNT(*) as count FROM partners")[0]['count'] - sales_count = db.execute_query("SELECT COUNT(*) as count FROM sales")[0]['count'] - managers_count = db.execute_query("SELECT COUNT(*) as count FROM managers")[0]['count'] - - print(f"📊 Статистика базы данных:") - print(f" - Партнеров: {partners_count}") - print(f" - Продаж: {sales_count}") - print(f" - Менеджеров: {managers_count}") - - return True - - except Exception as e: - print(f"❌ Ошибка инициализации базы данных: {e}") - import traceback - traceback.print_exc() - return False - -def main(): - """Основная функция""" - args = parse_arguments() - - # Формируем URL подключения - db_url = f"postgresql://{args.username}:{args.password}@{args.host}:{args.port}/{args.database}" - - print(f"🔄 Подключение к базе данных: {args.database} на {args.host}:{args.port}") - - success = initialize_database(db_url) - - if success: - print("🎉 Инициализация базы данных завершена успешно!") - sys.exit(0) - else: - print("💥 Инициализация базы данных завершена с ошибками!") - sys.exit(1) - -if __name__ == "__main__": - main() diff --git a/ressult/gui/__init__.py b/ressult/gui/__init__.py deleted file mode 100644 index 7496f80..0000000 --- a/ressult/gui/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# gui/__init__.py -""" -Пакет графического интерфейса с авторизацией -""" -from .login_window import LoginWindow -from .main_window import MainWindow -from .partner_form import PartnerForm -from .sales_history import SalesHistoryWindow -from .material_calculator import MaterialCalculatorWindow diff --git a/ressult/gui/__pycache__/__init__.cpython-314.pyc b/ressult/gui/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index 844ae5c..0000000 Binary files a/ressult/gui/__pycache__/__init__.cpython-314.pyc and /dev/null differ diff --git a/ressult/gui/__pycache__/login_window.cpython-314.pyc b/ressult/gui/__pycache__/login_window.cpython-314.pyc deleted file mode 100644 index 30f372b..0000000 Binary files a/ressult/gui/__pycache__/login_window.cpython-314.pyc and /dev/null differ diff --git a/ressult/gui/__pycache__/main_window.cpython-314.pyc b/ressult/gui/__pycache__/main_window.cpython-314.pyc deleted file mode 100644 index bd6114a..0000000 Binary files a/ressult/gui/__pycache__/main_window.cpython-314.pyc and /dev/null differ diff --git a/ressult/gui/__pycache__/material_calculator.cpython-314.pyc b/ressult/gui/__pycache__/material_calculator.cpython-314.pyc deleted file mode 100644 index b5a2627..0000000 Binary files a/ressult/gui/__pycache__/material_calculator.cpython-314.pyc and /dev/null differ diff --git a/ressult/gui/__pycache__/partner_form.cpython-314.pyc b/ressult/gui/__pycache__/partner_form.cpython-314.pyc deleted file mode 100644 index 63a8845..0000000 Binary files a/ressult/gui/__pycache__/partner_form.cpython-314.pyc and /dev/null differ diff --git a/ressult/gui/__pycache__/sales_history.cpython-314.pyc b/ressult/gui/__pycache__/sales_history.cpython-314.pyc deleted file mode 100644 index 8bb9689..0000000 Binary files a/ressult/gui/__pycache__/sales_history.cpython-314.pyc and /dev/null differ diff --git a/ressult/gui/login_window.py b/ressult/gui/login_window.py deleted file mode 100644 index c61b70b..0000000 --- a/ressult/gui/login_window.py +++ /dev/null @@ -1,253 +0,0 @@ -# gui/login_window.py -""" -Окно авторизации менеджера -Соответствует требованиям ТЗ по аутентификации -""" -import sys -from PyQt6.QtWidgets import (QApplication, QDialog, QVBoxLayout, QHBoxLayout, - QLabel, QLineEdit, QPushButton, QMessageBox, - QFrame, QCheckBox) -from PyQt6.QtCore import Qt, pyqtSignal -from PyQt6.QtGui import QFont, QPixmap, QIcon -import requests -from requests.auth import HTTPBasicAuth - -class LoginWindow(QDialog): - """Окно авторизации системы MasterPol""" - - login_success = pyqtSignal(dict) # Сигнал об успешной авторизации - - def __init__(self): - super().__init__() - self.setup_ui() - self.load_settings() - - def setup_ui(self): - """Настройка интерфейса окна авторизации""" - self.setWindowTitle("MasterPol - Авторизация") - self.setFixedSize(400, 500) - self.setModal(True) - - # Установка иконки приложения - try: - self.setWindowIcon(QIcon("resources/icon.png")) - except: - pass - - layout = QVBoxLayout() - layout.setContentsMargins(30, 30, 30, 30) - layout.setSpacing(0) - - # Заголовок - title_label = QLabel("MasterPol") - title_label.setFont(QFont("Arial", 24, QFont.Weight.Bold)) - title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - title_label.setStyleSheet("color: #007acc; margin-bottom: 20px;") - - subtitle_label = QLabel("Система управления партнерами") - subtitle_label.setFont(QFont("Arial", 12)) - subtitle_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - subtitle_label.setStyleSheet("color: #666; margin-bottom: 30px;") - - layout.addWidget(title_label) - layout.addWidget(subtitle_label) - - # Форма авторизаци - form_frame = QFrame() - form_frame.setStyleSheet(""" - QFrame { - background-color: white; - border: 0px solid #ddd; - border-radius: 4px; - padding: 20px; - } - """) - - form_layout = QVBoxLayout() - form_layout.setSpacing(15) - - # Поле логина - username_layout = QVBoxLayout() - username_label = QLabel("Имя пользователя:") - username_label.setStyleSheet("font-weight: bold; color: #333;") - - self.username_input = QLineEdit() - self.username_input.setPlaceholderText("Введите имя пользователя") - self.username_input.setStyleSheet(""" - QLineEdit { - padding: 8px 12px; - border: 2px solid #ccc; - border-radius: 6px; - font-size: 14px; - } - QLineEdit:focus { - border-color: #007acc; - } - """) - - username_layout.addWidget(username_label) - username_layout.addWidget(self.username_input) - - # Поле пароля - password_layout = QVBoxLayout() - password_label = QLabel("Пароль:") - password_label.setStyleSheet("font-weight: bold; color: #333;") - - self.password_input = QLineEdit() - self.password_input.setPlaceholderText("Введите пароль") - self.password_input.setEchoMode(QLineEdit.EchoMode.Password) - self.password_input.setStyleSheet(""" - QLineEdit { - padding: 10px; - border: 1px solid #ccc; - border-radius: 4px; - font-size: 14px; - } - QLineEdit:focus { - border-color: #007acc; - } - """) - - password_layout.addWidget(password_label) - password_layout.addWidget(self.password_input) - - # Запомнить меня - self.remember_checkbox = QCheckBox("Запомнить меня") - self.remember_checkbox.setStyleSheet("color: #333;") - - # Кнопка входа - self.login_button = QPushButton("Войти в систему") - self.login_button.clicked.connect(self.authenticate) - self.login_button.setStyleSheet(""" - QPushButton { - background-color: #007acc; - color: white; - border: none; - padding: 12px; - border-radius: 4px; - font-weight: bold; - font-size: 14px; - } - QPushButton:hover { - background-color: #005a9e; - } - QPushButton:disabled { - background-color: #ccc; - color: #666; - } - """) - - # Подсказка - hint_label = QLabel("Используйте логин: manager, пароль: pass123") - hint_label.setStyleSheet("color: #666; font-size: 12px; margin-top: 10px;") - hint_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - - form_layout.addLayout(username_layout) - form_layout.addLayout(password_layout) - form_layout.addWidget(self.remember_checkbox) - form_layout.addWidget(self.login_button) - form_layout.addWidget(hint_label) - - form_frame.setLayout(form_layout) - layout.addWidget(form_frame) - - # Информация о системе - info_label = QLabel("MasterPol v1.0.0\nСистема управления партнерами и продажами") - info_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - info_label.setStyleSheet("color: #999; font-size: 11px; margin-top: 20px;") - layout.addWidget(info_label) - - self.setLayout(layout) - - # Подключаем обработчики событий - self.username_input.returnPressed.connect(self.authenticate) - self.password_input.returnPressed.connect(self.authenticate) - - def load_settings(self): - """Загрузка сохраненных настроек авторизации""" - try: - # Здесь можно добавить загрузку из файла настроек - # Пока просто устанавливаем значения по умолчанию - self.username_input.setText("manager") - except: - pass - - def save_settings(self): - """Сохранение настроек авторизации""" - if self.remember_checkbox.isChecked(): - # Здесь можно добавить сохранение в файл настроек - pass - - def authenticate(self): - """Аутентификация пользователя""" - username = self.username_input.text().strip() - password = self.password_input.text().strip() - - if not username or not password: - QMessageBox.warning(self, "Ошибка", "Заполните все поля") - return - - # Блокируем кнопку во время аутентификации - self.login_button.setEnabled(False) - self.login_button.setText("Проверка...") - - try: - # Выполняем аутентификацию через API - response = requests.post( - "http://localhost:8000/api/v1/auth/login", - auth=HTTPBasicAuth(username, password), - timeout=10 - ) - - if response.status_code == 200: - user_data = response.json() - - # Сохраняем настройки - self.save_settings() - - # Сохраняем учетные данные для будущих запросов - user_data['auth'] = HTTPBasicAuth(username, password) - - # Отправляем сигнал об успешной авторизации - self.login_success.emit(user_data) - - else: - QMessageBox.warning( - self, - "Ошибка авторизации", - "Неверное имя пользователя или пароль" - ) - - except requests.exceptions.ConnectionError: - QMessageBox.critical( - self, - "Ошибка подключения", - "Не удалось подключиться к серверу.\n" - "Убедитесь, что сервер запущен на localhost:8000" - ) - except requests.exceptions.Timeout: - QMessageBox.critical( - self, - "Ошибка подключения", - "Превышено время ожидания ответа от сервера" - ) - except Exception as e: - QMessageBox.critical( - self, - "Ошибка", - f"Произошла непредвиденная ошибка:\n{str(e)}" - ) - finally: - # Разблокируем кнопку - self.login_button.setEnabled(True) - self.login_button.setText("Войти в систему") - -def main(): - """Точка входа для тестирования окна авторизации""" - app = QApplication(sys.argv) - window = LoginWindow() - window.show() - sys.exit(app.exec()) - -if __name__ == "__main__": - main() diff --git a/ressult/gui/main_window b/ressult/gui/main_window deleted file mode 100644 index 8af89e8..0000000 --- a/ressult/gui/main_window +++ /dev/null @@ -1,574 +0,0 @@ -# gui/main_window.py -""" -Главное окно приложения PyQt6 с поддержкой авторизации -""" -import sys -import requests -from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, - QHBoxLayout, QLabel, QPushButton, QListWidget, - QListWidgetItem, QMessageBox, QFrame, QStackedWidget, - QMenuBar, QMenu, QStatusBar, QToolBar) -from PyQt6.QtCore import Qt, pyqtSignal -from PyQt6.QtGui import QFont, QPixmap, QIcon, QAction -from .partner_form import PartnerForm -from .sales_history import SalesHistoryWindow -from .material_calculator import MaterialCalculatorWindow - -class PartnerCard(QFrame): - """Карточка партнера для отображения в списке""" - partner_clicked = pyqtSignal(dict) - - def __init__(self, partner_data): - super().__init__() - self.partner_data = partner_data - self.setup_ui() - - def setup_ui(self): - self.setFrameStyle(QFrame.Shape.StyledPanel) - self.setStyleSheet(""" - PartnerCard { - background-color: white; - border: 1px solid #ddd; - border-radius: 8px; - padding: 12px; - margin: 4px; - } - PartnerCard:hover { - background-color: #f5f5f5; - border-color: #007acc; - } - """) - - layout = QVBoxLayout() - layout.setContentsMargins(8, 8, 8, 8) - layout.setSpacing(4) - - # Заголовок с типом и названием - header_layout = QHBoxLayout() - header_layout.setSpacing(4) - - type_label = QLabel(f"{self.partner_data.get('partner_type', 'Тип не указан')} |") - type_label.setStyleSheet("color: #666; font-weight: bold;") - - name_label = QLabel(self.partner_data['company_name']) - name_label.setStyleSheet("font-weight: bold; font-size: 14px;") - name_label.setWordWrap(True) - - # Безопасное преобразование рейтинга - rating_value = self.partner_data.get('rating', 0) - if isinstance(rating_value, float): - rating_value = int(rating_value) - - rating_label = QLabel(f"{rating_value}%") - rating_label.setStyleSheet("color: #007acc; font-weight: bold;") - - header_layout.addWidget(type_label) - header_layout.addWidget(name_label) - header_layout.addStretch() - header_layout.addWidget(rating_label) - - # Информация о директоре - director_label = QLabel(self.partner_data.get('director_name', 'Директор не указан')) - director_label.setStyleSheet("color: #444;") - - # Контактная информация - phone_label = QLabel(self.partner_data.get('phone', 'Телефон не указан')) - phone_label.setStyleSheet("color: #666;") - - layout.addLayout(header_layout) - layout.addWidget(director_label) - layout.addWidget(phone_label) - - self.setLayout(layout) - - def mousePressEvent(self, event): - """Обработка клика на карточке""" - if event.button() == Qt.MouseButton.LeftButton: - self.partner_clicked.emit(self.partner_data) - -class MainWindow(QMainWindow): - """Главное окно приложения с поддержкой авторизации""" - - def __init__(self, user_data): - super().__init__() - self.user_data = user_data - self.current_partner = None - self.auth = user_data.get('auth') - self.setup_ui() - self.load_partners() - - def setup_ui(self): - """Настройка интерфейса главного окна""" - self.setWindowTitle(f"MasterPol - Система управления партнерами") - self.setGeometry(100, 100, 1200, 700) - - # Установка иконки приложения - try: - self.setWindowIcon(QIcon("resources/icon.png")) - except: - pass - - # Создание меню - self.create_menu() - - # Создание тулбара - self.create_toolbar() - - # Создание статусной строки - self.create_statusbar() - - # Центральный виджет - central_widget = QWidget() - self.setCentralWidget(central_widget) - - main_layout = QHBoxLayout() - main_layout.setContentsMargins(0, 0, 0, 0) - - # Левая панель - список партнеров - left_panel = self.create_partners_panel() - main_layout.addWidget(left_panel, 1) - - # Правая панель - детальная информация - self.right_panel = self.create_details_panel() - main_layout.addWidget(self.right_panel, 2) - - central_widget.setLayout(main_layout) - - def create_menu(self): - """Создание меню приложения""" - menubar = self.menuBar() - - # Меню Файл - file_menu = menubar.addMenu('Файл') - - refresh_action = QAction('Обновить', self) - refresh_action.setShortcut('F5') - refresh_action.triggered.connect(self.load_partners) - file_menu.addAction(refresh_action) - - file_menu.addSeparator() - - logout_action = QAction('Выход', self) - logout_action.setShortcut('Ctrl+Q') - logout_action.triggered.connect(self.logout) - file_menu.addAction(logout_action) - - # Меню Сервис - service_menu = menubar.addMenu('Сервис') - - calc_action = QAction('Калькулятор материалов', self) - calc_action.triggered.connect(self.show_material_calculator) - service_menu.addAction(calc_action) - - # Меню Справка - help_menu = menubar.addMenu('Справка') - - about_action = QAction('О программе', self) - about_action.triggered.connect(self.show_about) - help_menu.addAction(about_action) - - def create_toolbar(self): - """Создание панели инструментов""" - toolbar = QToolBar("Основные инструменты") - self.addToolBar(toolbar) - - refresh_action = QAction('Обновить', self) - refresh_action.triggered.connect(self.load_partners) - toolbar.addAction(refresh_action) - - toolbar.addSeparator() - - add_partner_action = QAction('Добавить партнера', self) - add_partner_action.triggered.connect(self.show_add_partner_form) - toolbar.addAction(add_partner_action) - - def create_statusbar(self): - """Создание статусной строки""" - statusbar = self.statusBar() - user_info = f"Пользователь: {self.user_data.get('full_name', 'Неизвестно')}" - statusbar.showMessage(user_info) - - def create_partners_panel(self): - """Создание панели списка партнеров""" - panel = QWidget() - panel.setMaximumWidth(400) - layout = QVBoxLayout() - layout.setContentsMargins(10, 10, 10, 10) - layout.setSpacing(10) - - # Заголовок - title = QLabel("Партнеры") - title.setFont(QFont("Arial", 16, QFont.Weight.Bold)) - title.setAlignment(Qt.AlignmentFlag.AlignCenter) - title.setStyleSheet("padding: 10px;") - layout.addWidget(title) - - # Панель управления - control_layout = QHBoxLayout() - control_layout.setSpacing(10) - - self.add_button = QPushButton("Добавить партнера") - self.add_button.clicked.connect(self.show_add_partner_form) - self.add_button.setStyleSheet(""" - QPushButton { - background-color: #007acc; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #005a9e; - } - """) - - self.refresh_button = QPushButton("Обновить") - self.refresh_button.clicked.connect(self.load_partners) - self.refresh_button.setStyleSheet(""" - QPushButton { - background-color: #6c757d; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #545b62; - } - """) - - control_layout.addWidget(self.add_button) - control_layout.addWidget(self.refresh_button) - control_layout.addStretch() - - layout.addLayout(control_layout) - - # Список партнеров - self.partners_list = QListWidget() - self.partners_list.setStyleSheet(""" - QListWidget { - border: 1px solid #ddd; - border-radius: 4px; - background-color: white; - outline: none; - } - QListWidget::item { - border: none; - padding: 0px; - } - QListWidget::item:selected { - background-color: transparent; - } - """) - layout.addWidget(self.partners_list) - - # Кнопка расчета материалов - self.calc_button = QPushButton("Калькулятор материалов") - self.calc_button.clicked.connect(self.show_material_calculator) - self.calc_button.setStyleSheet(""" - QPushButton { - background-color: #17a2b8; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #138496; - } - """) - layout.addWidget(self.calc_button) - - panel.setLayout(layout) - return panel - - def create_details_panel(self): - """Создание панели детальной информации""" - panel = QWidget() - layout = QVBoxLayout() - layout.setContentsMargins(10, 10, 10, 10) - layout.setSpacing(10) - - # Заголовок детальной информации - self.details_title = QLabel("Выберите партнера") - self.details_title.setFont(QFont("Arial", 14, QFont.Weight.Bold)) - self.details_title.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.details_title.setStyleSheet("padding: 10px;") - layout.addWidget(self.details_title) - - # Детальная информация о партнере - создаем пустой frame - self.details_frame = QFrame() - self.details_frame.setFrameStyle(QFrame.Shape.StyledPanel) - self.details_frame.setStyleSheet(""" - QFrame { - background-color: #f9f9f9; - border: 1px solid #ddd; - border-radius: 5px; - padding: 15px; - } - """) - self.details_layout = QVBoxLayout() - self.details_layout.setSpacing(8) - self.details_frame.setLayout(self.details_layout) - self.details_frame.hide() - - layout.addWidget(self.details_frame) - - # Кнопки управления выбранным партнером - self.control_buttons = QWidget() - buttons_layout = QHBoxLayout() - buttons_layout.setSpacing(10) - - self.edit_button = QPushButton("Редактировать") - self.edit_button.clicked.connect(self.edit_partner) - self.edit_button.setStyleSheet(""" - QPushButton { - background-color: #007acc; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #005a9e; - } - """) - self.edit_button.hide() - - self.sales_button = QPushButton("История продаж") - self.sales_button.clicked.connect(self.show_sales_history) - self.sales_button.setStyleSheet(""" - QPushButton { - background-color: #28a745; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #218838; - } - """) - self.sales_button.hide() - - self.discount_button = QPushButton("Расчет скидки") - self.discount_button.clicked.connect(self.calculate_discount) - self.discount_button.setStyleSheet(""" - QPushButton { - background-color: #ffc107; - color: black; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #e0a800; - } - """) - self.discount_button.hide() - - buttons_layout.addWidget(self.edit_button) - buttons_layout.addWidget(self.sales_button) - buttons_layout.addWidget(self.discount_button) - buttons_layout.addStretch() - - self.control_buttons.setLayout(buttons_layout) - layout.addWidget(self.control_buttons) - - # Добавляем растягивающийся элемент в конец - layout.addStretch() - - panel.setLayout(layout) - return panel - - def load_partners(self): - """Загрузка списка партнеров из API с авторизацией""" - try: - response = requests.get( - "http://localhost:8000/api/v1/partners", - auth=self.auth, - timeout=10 - ) - - if response.status_code == 200: - self.partners_list.clear() - partners = response.json() - - for partner in partners: - item = QListWidgetItem() - card = PartnerCard(partner) - card.partner_clicked.connect(self.show_partner_details) - - # Устанавливаем фиксированный размер для элемента - item.setSizeHint(card.sizeHint()) - self.partners_list.addItem(item) - self.partners_list.setItemWidget(item, card) - - # Сбрасываем выделение - self.partners_list.clearSelection() - self.current_partner = None - self.details_title.setText("Выберите партнера") - self.details_frame.hide() - self.edit_button.hide() - self.sales_button.hide() - self.discount_button.hide() - - elif response.status_code == 401: - QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла. Пожалуйста, войдите снова.") - self.logout() - else: - QMessageBox.warning(self, "Ошибка", "Не удалось загрузить партнеров") - - except requests.exceptions.ConnectionError: - QMessageBox.critical(self, "Ошибка", "Не удалось подключиться к серверу") - except Exception as e: - QMessageBox.warning(self, "Ошибка", f"Не удалось загрузить партнеров: {str(e)}") - - def show_partner_details(self, partner_data): - """Отображение детальной информации о партнере""" - self.current_partner = partner_data - self.details_title.setText(partner_data['company_name']) - - # Создаем новый виджет для деталей вместо очистки layout - new_details_frame = QFrame() - new_details_frame.setFrameStyle(QFrame.Shape.StyledPanel) - new_details_frame.setStyleSheet(""" - QFrame { - background-color: #f9f9f9; - border: 1px solid #ddd; - border-radius: 5px; - padding: 15px; - } - """) - new_details_layout = QVBoxLayout() - new_details_layout.setSpacing(8) - - # Добавляем новую информацию - details = [ - ("Тип:", partner_data.get('partner_type', 'Не указан')), - ("ИНН:", partner_data.get('inn', 'Не указан')), - ("Директор:", partner_data.get('director_name', 'Не указан')), - ("Телефон:", partner_data.get('phone', 'Не указан')), - ("Email:", partner_data.get('email', 'Не указан')), - ("Рейтинг:", str(partner_data.get('rating', 0))), - ("Адрес:", partner_data.get('legal_address', 'Не указан')), - ("Регионы:", partner_data.get('sales_locations', 'Не указан')) - ] - - for label, value in details: - row_widget = QWidget() - row_layout = QHBoxLayout(row_widget) - row_layout.setContentsMargins(0, 2, 0, 2) - - label_widget = QLabel(label) - label_widget.setStyleSheet("font-weight: bold; min-width: 100px;") - label_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) - - value_widget = QLabel(str(value)) - value_widget.setWordWrap(True) - value_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) - - row_layout.addWidget(label_widget) - row_layout.addWidget(value_widget) - row_layout.addStretch() - - new_details_layout.addWidget(row_widget) - - new_details_frame.setLayout(new_details_layout) - - # Заменяем старый details_frame на новый - old_frame = self.details_frame - layout = self.right_panel.layout() - layout.replaceWidget(old_frame, new_details_frame) - old_frame.deleteLater() - - self.details_frame = new_details_frame - self.details_layout = new_details_layout - - self.details_frame.show() - self.edit_button.show() - self.sales_button.show() - self.discount_button.show() - - def show_add_partner_form(self): - """Открытие формы добавления партнера""" - form = PartnerForm(self, auth=self.auth) - form.partner_saved.connect(self.load_partners) - form.exec() - - def edit_partner(self): - """Редактирование выбранного партнера""" - if self.current_partner: - form = PartnerForm(self, self.current_partner, auth=self.auth) - form.partner_saved.connect(self.load_partners) - form.exec() - - def show_sales_history(self): - """Открытие истории продаж партнера""" - if self.current_partner: - sales_window = SalesHistoryWindow(self.current_partner, self, auth=self.auth) - sales_window.exec() - - def calculate_discount(self): - """Расчет скидки для партнера с авторизацией""" - if self.current_partner: - try: - response = requests.get( - f"http://localhost:8000/api/v1/partners/{self.current_partner['partner_id']}/discount", - auth=self.auth, - timeout=10 - ) - - if response.status_code == 200: - discount_data = response.json() - QMessageBox.information( - self, - "Расчет скидки", - f"Партнер: {self.current_partner['company_name']}\n" - f"Общие продажи: {discount_data['total_sales']}\n" - f"Скидка: {discount_data['discount_percent']}%" - ) - elif response.status_code == 401: - QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла. Пожалуйста, войдите снова.") - self.logout() - - except Exception as e: - QMessageBox.warning(self, "Ошибка", f"Не удалось рассчитать скидку: {str(e)}") - - def show_material_calculator(self): - """Открытие калькулятора материалов""" - calculator = MaterialCalculatorWindow(self, auth=self.auth) - calculator.exec() - - def logout(self): - """Выход из системы""" - reply = QMessageBox.question( - self, - "Подтверждение выхода", - "Вы уверены, что хотите выйти из системы?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, - QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - self.close() - # Здесь можно добавить вызов окна авторизации - # или перезапуск приложения - - def show_about(self): - """Показать информацию о программе""" - QMessageBox.about( - self, - "О программе MasterPol", - "MasterPol - Система управления партнерами\n\n" - "Версия: 1.0.0\n" - "Разработчик: Команда MasterPol\n\n" - "Система предназначена для управления партнерами,\n" - "учета продаж и расчета бизнес-показателей." - ) diff --git a/ressult/gui/main_window.py b/ressult/gui/main_window.py deleted file mode 100644 index 8af89e8..0000000 --- a/ressult/gui/main_window.py +++ /dev/null @@ -1,574 +0,0 @@ -# gui/main_window.py -""" -Главное окно приложения PyQt6 с поддержкой авторизации -""" -import sys -import requests -from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, - QHBoxLayout, QLabel, QPushButton, QListWidget, - QListWidgetItem, QMessageBox, QFrame, QStackedWidget, - QMenuBar, QMenu, QStatusBar, QToolBar) -from PyQt6.QtCore import Qt, pyqtSignal -from PyQt6.QtGui import QFont, QPixmap, QIcon, QAction -from .partner_form import PartnerForm -from .sales_history import SalesHistoryWindow -from .material_calculator import MaterialCalculatorWindow - -class PartnerCard(QFrame): - """Карточка партнера для отображения в списке""" - partner_clicked = pyqtSignal(dict) - - def __init__(self, partner_data): - super().__init__() - self.partner_data = partner_data - self.setup_ui() - - def setup_ui(self): - self.setFrameStyle(QFrame.Shape.StyledPanel) - self.setStyleSheet(""" - PartnerCard { - background-color: white; - border: 1px solid #ddd; - border-radius: 8px; - padding: 12px; - margin: 4px; - } - PartnerCard:hover { - background-color: #f5f5f5; - border-color: #007acc; - } - """) - - layout = QVBoxLayout() - layout.setContentsMargins(8, 8, 8, 8) - layout.setSpacing(4) - - # Заголовок с типом и названием - header_layout = QHBoxLayout() - header_layout.setSpacing(4) - - type_label = QLabel(f"{self.partner_data.get('partner_type', 'Тип не указан')} |") - type_label.setStyleSheet("color: #666; font-weight: bold;") - - name_label = QLabel(self.partner_data['company_name']) - name_label.setStyleSheet("font-weight: bold; font-size: 14px;") - name_label.setWordWrap(True) - - # Безопасное преобразование рейтинга - rating_value = self.partner_data.get('rating', 0) - if isinstance(rating_value, float): - rating_value = int(rating_value) - - rating_label = QLabel(f"{rating_value}%") - rating_label.setStyleSheet("color: #007acc; font-weight: bold;") - - header_layout.addWidget(type_label) - header_layout.addWidget(name_label) - header_layout.addStretch() - header_layout.addWidget(rating_label) - - # Информация о директоре - director_label = QLabel(self.partner_data.get('director_name', 'Директор не указан')) - director_label.setStyleSheet("color: #444;") - - # Контактная информация - phone_label = QLabel(self.partner_data.get('phone', 'Телефон не указан')) - phone_label.setStyleSheet("color: #666;") - - layout.addLayout(header_layout) - layout.addWidget(director_label) - layout.addWidget(phone_label) - - self.setLayout(layout) - - def mousePressEvent(self, event): - """Обработка клика на карточке""" - if event.button() == Qt.MouseButton.LeftButton: - self.partner_clicked.emit(self.partner_data) - -class MainWindow(QMainWindow): - """Главное окно приложения с поддержкой авторизации""" - - def __init__(self, user_data): - super().__init__() - self.user_data = user_data - self.current_partner = None - self.auth = user_data.get('auth') - self.setup_ui() - self.load_partners() - - def setup_ui(self): - """Настройка интерфейса главного окна""" - self.setWindowTitle(f"MasterPol - Система управления партнерами") - self.setGeometry(100, 100, 1200, 700) - - # Установка иконки приложения - try: - self.setWindowIcon(QIcon("resources/icon.png")) - except: - pass - - # Создание меню - self.create_menu() - - # Создание тулбара - self.create_toolbar() - - # Создание статусной строки - self.create_statusbar() - - # Центральный виджет - central_widget = QWidget() - self.setCentralWidget(central_widget) - - main_layout = QHBoxLayout() - main_layout.setContentsMargins(0, 0, 0, 0) - - # Левая панель - список партнеров - left_panel = self.create_partners_panel() - main_layout.addWidget(left_panel, 1) - - # Правая панель - детальная информация - self.right_panel = self.create_details_panel() - main_layout.addWidget(self.right_panel, 2) - - central_widget.setLayout(main_layout) - - def create_menu(self): - """Создание меню приложения""" - menubar = self.menuBar() - - # Меню Файл - file_menu = menubar.addMenu('Файл') - - refresh_action = QAction('Обновить', self) - refresh_action.setShortcut('F5') - refresh_action.triggered.connect(self.load_partners) - file_menu.addAction(refresh_action) - - file_menu.addSeparator() - - logout_action = QAction('Выход', self) - logout_action.setShortcut('Ctrl+Q') - logout_action.triggered.connect(self.logout) - file_menu.addAction(logout_action) - - # Меню Сервис - service_menu = menubar.addMenu('Сервис') - - calc_action = QAction('Калькулятор материалов', self) - calc_action.triggered.connect(self.show_material_calculator) - service_menu.addAction(calc_action) - - # Меню Справка - help_menu = menubar.addMenu('Справка') - - about_action = QAction('О программе', self) - about_action.triggered.connect(self.show_about) - help_menu.addAction(about_action) - - def create_toolbar(self): - """Создание панели инструментов""" - toolbar = QToolBar("Основные инструменты") - self.addToolBar(toolbar) - - refresh_action = QAction('Обновить', self) - refresh_action.triggered.connect(self.load_partners) - toolbar.addAction(refresh_action) - - toolbar.addSeparator() - - add_partner_action = QAction('Добавить партнера', self) - add_partner_action.triggered.connect(self.show_add_partner_form) - toolbar.addAction(add_partner_action) - - def create_statusbar(self): - """Создание статусной строки""" - statusbar = self.statusBar() - user_info = f"Пользователь: {self.user_data.get('full_name', 'Неизвестно')}" - statusbar.showMessage(user_info) - - def create_partners_panel(self): - """Создание панели списка партнеров""" - panel = QWidget() - panel.setMaximumWidth(400) - layout = QVBoxLayout() - layout.setContentsMargins(10, 10, 10, 10) - layout.setSpacing(10) - - # Заголовок - title = QLabel("Партнеры") - title.setFont(QFont("Arial", 16, QFont.Weight.Bold)) - title.setAlignment(Qt.AlignmentFlag.AlignCenter) - title.setStyleSheet("padding: 10px;") - layout.addWidget(title) - - # Панель управления - control_layout = QHBoxLayout() - control_layout.setSpacing(10) - - self.add_button = QPushButton("Добавить партнера") - self.add_button.clicked.connect(self.show_add_partner_form) - self.add_button.setStyleSheet(""" - QPushButton { - background-color: #007acc; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #005a9e; - } - """) - - self.refresh_button = QPushButton("Обновить") - self.refresh_button.clicked.connect(self.load_partners) - self.refresh_button.setStyleSheet(""" - QPushButton { - background-color: #6c757d; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #545b62; - } - """) - - control_layout.addWidget(self.add_button) - control_layout.addWidget(self.refresh_button) - control_layout.addStretch() - - layout.addLayout(control_layout) - - # Список партнеров - self.partners_list = QListWidget() - self.partners_list.setStyleSheet(""" - QListWidget { - border: 1px solid #ddd; - border-radius: 4px; - background-color: white; - outline: none; - } - QListWidget::item { - border: none; - padding: 0px; - } - QListWidget::item:selected { - background-color: transparent; - } - """) - layout.addWidget(self.partners_list) - - # Кнопка расчета материалов - self.calc_button = QPushButton("Калькулятор материалов") - self.calc_button.clicked.connect(self.show_material_calculator) - self.calc_button.setStyleSheet(""" - QPushButton { - background-color: #17a2b8; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #138496; - } - """) - layout.addWidget(self.calc_button) - - panel.setLayout(layout) - return panel - - def create_details_panel(self): - """Создание панели детальной информации""" - panel = QWidget() - layout = QVBoxLayout() - layout.setContentsMargins(10, 10, 10, 10) - layout.setSpacing(10) - - # Заголовок детальной информации - self.details_title = QLabel("Выберите партнера") - self.details_title.setFont(QFont("Arial", 14, QFont.Weight.Bold)) - self.details_title.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.details_title.setStyleSheet("padding: 10px;") - layout.addWidget(self.details_title) - - # Детальная информация о партнере - создаем пустой frame - self.details_frame = QFrame() - self.details_frame.setFrameStyle(QFrame.Shape.StyledPanel) - self.details_frame.setStyleSheet(""" - QFrame { - background-color: #f9f9f9; - border: 1px solid #ddd; - border-radius: 5px; - padding: 15px; - } - """) - self.details_layout = QVBoxLayout() - self.details_layout.setSpacing(8) - self.details_frame.setLayout(self.details_layout) - self.details_frame.hide() - - layout.addWidget(self.details_frame) - - # Кнопки управления выбранным партнером - self.control_buttons = QWidget() - buttons_layout = QHBoxLayout() - buttons_layout.setSpacing(10) - - self.edit_button = QPushButton("Редактировать") - self.edit_button.clicked.connect(self.edit_partner) - self.edit_button.setStyleSheet(""" - QPushButton { - background-color: #007acc; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #005a9e; - } - """) - self.edit_button.hide() - - self.sales_button = QPushButton("История продаж") - self.sales_button.clicked.connect(self.show_sales_history) - self.sales_button.setStyleSheet(""" - QPushButton { - background-color: #28a745; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #218838; - } - """) - self.sales_button.hide() - - self.discount_button = QPushButton("Расчет скидки") - self.discount_button.clicked.connect(self.calculate_discount) - self.discount_button.setStyleSheet(""" - QPushButton { - background-color: #ffc107; - color: black; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #e0a800; - } - """) - self.discount_button.hide() - - buttons_layout.addWidget(self.edit_button) - buttons_layout.addWidget(self.sales_button) - buttons_layout.addWidget(self.discount_button) - buttons_layout.addStretch() - - self.control_buttons.setLayout(buttons_layout) - layout.addWidget(self.control_buttons) - - # Добавляем растягивающийся элемент в конец - layout.addStretch() - - panel.setLayout(layout) - return panel - - def load_partners(self): - """Загрузка списка партнеров из API с авторизацией""" - try: - response = requests.get( - "http://localhost:8000/api/v1/partners", - auth=self.auth, - timeout=10 - ) - - if response.status_code == 200: - self.partners_list.clear() - partners = response.json() - - for partner in partners: - item = QListWidgetItem() - card = PartnerCard(partner) - card.partner_clicked.connect(self.show_partner_details) - - # Устанавливаем фиксированный размер для элемента - item.setSizeHint(card.sizeHint()) - self.partners_list.addItem(item) - self.partners_list.setItemWidget(item, card) - - # Сбрасываем выделение - self.partners_list.clearSelection() - self.current_partner = None - self.details_title.setText("Выберите партнера") - self.details_frame.hide() - self.edit_button.hide() - self.sales_button.hide() - self.discount_button.hide() - - elif response.status_code == 401: - QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла. Пожалуйста, войдите снова.") - self.logout() - else: - QMessageBox.warning(self, "Ошибка", "Не удалось загрузить партнеров") - - except requests.exceptions.ConnectionError: - QMessageBox.critical(self, "Ошибка", "Не удалось подключиться к серверу") - except Exception as e: - QMessageBox.warning(self, "Ошибка", f"Не удалось загрузить партнеров: {str(e)}") - - def show_partner_details(self, partner_data): - """Отображение детальной информации о партнере""" - self.current_partner = partner_data - self.details_title.setText(partner_data['company_name']) - - # Создаем новый виджет для деталей вместо очистки layout - new_details_frame = QFrame() - new_details_frame.setFrameStyle(QFrame.Shape.StyledPanel) - new_details_frame.setStyleSheet(""" - QFrame { - background-color: #f9f9f9; - border: 1px solid #ddd; - border-radius: 5px; - padding: 15px; - } - """) - new_details_layout = QVBoxLayout() - new_details_layout.setSpacing(8) - - # Добавляем новую информацию - details = [ - ("Тип:", partner_data.get('partner_type', 'Не указан')), - ("ИНН:", partner_data.get('inn', 'Не указан')), - ("Директор:", partner_data.get('director_name', 'Не указан')), - ("Телефон:", partner_data.get('phone', 'Не указан')), - ("Email:", partner_data.get('email', 'Не указан')), - ("Рейтинг:", str(partner_data.get('rating', 0))), - ("Адрес:", partner_data.get('legal_address', 'Не указан')), - ("Регионы:", partner_data.get('sales_locations', 'Не указан')) - ] - - for label, value in details: - row_widget = QWidget() - row_layout = QHBoxLayout(row_widget) - row_layout.setContentsMargins(0, 2, 0, 2) - - label_widget = QLabel(label) - label_widget.setStyleSheet("font-weight: bold; min-width: 100px;") - label_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) - - value_widget = QLabel(str(value)) - value_widget.setWordWrap(True) - value_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) - - row_layout.addWidget(label_widget) - row_layout.addWidget(value_widget) - row_layout.addStretch() - - new_details_layout.addWidget(row_widget) - - new_details_frame.setLayout(new_details_layout) - - # Заменяем старый details_frame на новый - old_frame = self.details_frame - layout = self.right_panel.layout() - layout.replaceWidget(old_frame, new_details_frame) - old_frame.deleteLater() - - self.details_frame = new_details_frame - self.details_layout = new_details_layout - - self.details_frame.show() - self.edit_button.show() - self.sales_button.show() - self.discount_button.show() - - def show_add_partner_form(self): - """Открытие формы добавления партнера""" - form = PartnerForm(self, auth=self.auth) - form.partner_saved.connect(self.load_partners) - form.exec() - - def edit_partner(self): - """Редактирование выбранного партнера""" - if self.current_partner: - form = PartnerForm(self, self.current_partner, auth=self.auth) - form.partner_saved.connect(self.load_partners) - form.exec() - - def show_sales_history(self): - """Открытие истории продаж партнера""" - if self.current_partner: - sales_window = SalesHistoryWindow(self.current_partner, self, auth=self.auth) - sales_window.exec() - - def calculate_discount(self): - """Расчет скидки для партнера с авторизацией""" - if self.current_partner: - try: - response = requests.get( - f"http://localhost:8000/api/v1/partners/{self.current_partner['partner_id']}/discount", - auth=self.auth, - timeout=10 - ) - - if response.status_code == 200: - discount_data = response.json() - QMessageBox.information( - self, - "Расчет скидки", - f"Партнер: {self.current_partner['company_name']}\n" - f"Общие продажи: {discount_data['total_sales']}\n" - f"Скидка: {discount_data['discount_percent']}%" - ) - elif response.status_code == 401: - QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла. Пожалуйста, войдите снова.") - self.logout() - - except Exception as e: - QMessageBox.warning(self, "Ошибка", f"Не удалось рассчитать скидку: {str(e)}") - - def show_material_calculator(self): - """Открытие калькулятора материалов""" - calculator = MaterialCalculatorWindow(self, auth=self.auth) - calculator.exec() - - def logout(self): - """Выход из системы""" - reply = QMessageBox.question( - self, - "Подтверждение выхода", - "Вы уверены, что хотите выйти из системы?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, - QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - self.close() - # Здесь можно добавить вызов окна авторизации - # или перезапуск приложения - - def show_about(self): - """Показать информацию о программе""" - QMessageBox.about( - self, - "О программе MasterPol", - "MasterPol - Система управления партнерами\n\n" - "Версия: 1.0.0\n" - "Разработчик: Команда MasterPol\n\n" - "Система предназначена для управления партнерами,\n" - "учета продаж и расчета бизнес-показателей." - ) diff --git a/ressult/gui/main_window.py.bak b/ressult/gui/main_window.py.bak deleted file mode 100644 index 2051605..0000000 --- a/ressult/gui/main_window.py.bak +++ /dev/null @@ -1,616 +0,0 @@ -# gui/main_wind/w.py -""" -Главное окно приложения PyQt6 с поддержкой авторизации -""" -import sys -import requests -from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, - QHBoxLayout, QLabel, QPushButton, QListWidget, - QListWidgetItem, QMessageBox, QFrame, QStackedWidget, - QMenuBar, QMenu, QStatusBar, QToolBar) -from PyQt6.QtCore import Qt, pyqtSignal -from PyQt6.QtGui import QFont, QPixmap, QIcon, QAction -from .partner_form import PartnerForm -from .sales_history import SalesHistoryWindow -from .material_calculator import MaterialCalculatorWindow - -class PartnerCard(QFrame): - """Карточка партнера для отображения в списке""" - partner_clicked = pyqtSignal(dict) - - def __init__(self, partner_data): - super().__init__() - self.partner_data = partner_data - self.setup_ui() - - def setup_ui(self): - self.setFrameStyle(QFrame.Shape.StyledPanel) - self.setStyleSheet(""" - PartnerCard { - background-color: white; - border: 1px solid #ddd; - border-radius: 8px; - padding: 12px; - margin: 4px; - } - PartnerCard:hover { - background-color: #f5f5f5; - border-color: #007acc; - } - """) - - layout = QVBoxLayout() - layout.setContentsMargins(8, 8, 8, 8) - layout.setSpacing(4) - - # Заголовок с типом и названием - header_layout = QHBoxLayout() - header_layout.setSpacing(4) - - type_label = QLabel(f"{self.partner_data.get('partner_type', 'Тип не указан')} |") - type_label.setStyleSheet("color: #666; font-weight: bold;") - - name_label = QLabel(self.partner_data['company_name']) - name_label.setStyleSheet("font-weight: bold; font-size: 14px;") - name_label.setWordWrap(True) - - # Безопасное преобразование рейтинга - rating_value = self.partner_data.get('rating', 0) - if isinstance(rating_value, float): - rating_value = int(rating_value) - - rating_label = QLabel(f"{rating_value}%") - rating_label.setStyleSheet("color: #007acc; font-weight: bold;") - - header_layout.addWidget(type_label) - header_layout.addWidget(name_label) - header_layout.addStretch() - header_layout.addWidget(rating_label) - - # Информация о директоре - QLabel(self.partner_data.get('director_name', 'Директор не указан')) - director_label.setStyleSheet("color: #444;") - - # Контактная информация - phone_label = QLabel(self.partner_data.get('phone', 'Телефон не указан')) - phone_label.setStyleSheet("color: #666;") - - layout.addLayout(header_layout) - layout.addWidget(director_label) - layout.addWidget(phone_label) - - self.setLayout(layout) - - def mousePressEvent(self, event): - """Обработка клика на карточке""" - if event.button() == Qt.MouseButton.LeftButton: - self.partner_clicked.emit(self.partner_data) - -class MainWindow(QMainWindow): - """Главное окно приложения с поддержкой авторизации""" - - def __init__(self, user_data): - super().__init__() - self.user_data = user_data - self.current_partner = None - self.orders_panel = None - self.auth = user_data.get('auth') - self.setup_ui() - self.load_partners() - - def setup_ui(self): - """Настройка интерфейса главного окна""" - self.setWindowTitle(f"MasterPol - Система управления партнерами") - self.setGeometry(100, 100, 1200, 700) - - # Установка иконки приложения - try: - self.setWindowIcon(QIcon("resources/icon.png")) - except: - pass - - # Создание меню - self.create_menu() - - # Создание тулбара - self.create_toolbar() - - # Создание статусной строки - self.create_statusbar() - - # Центральный виджет - central_widget = QWidget() - self.setCentralWidget(central_widget) - - main_layout = QHBoxLayout() - main_layout.setContentsMargins(0, 0, 0, 0) - - # Левая панель - список партнеров - left_panel = self.create_partners_panel() - main_layout.addWidget(left_panel, 1) - - # Правая панель - детальная информация - self.right_panel = self.create_details_panel() - main_layout.addWidget(self.right_panel, 2) - - central_widget.setLayout(main_layout) - - def create_menu(self): - """Создание меню приложения""" - menubar = self.menuBar() - - # Меню Файл - file_menu = menubar.addMenu('Файл') - - refresh_action = QAction('Обновить', self) - refresh_action.setShortcut('F5') - refresh_action.triggered.connect(self.load_partners) - file_menu.addAction(refresh_action) - - file_menu.addSeparator() - - logout_action = QAction('Выход', self) - logout_action.setShortcut('Ctrl+Q') - logout_action.triggered.connect(self.logout) - file_menu.addAction(logout_action) - - # Меню Сервис - service_menu = menubar.addMenu('Сервис') - - calc_action = QAction('Калькулятор материалов', self) - calc_action.triggered.connect(self.show_material_calculator) - service_menu.addAction(calc_action) - - # Меню Справка - help_menu = menubar.addMenu('Справка') - - about_action = QAction('О программе', self) - about_action.triggered.connect(self.show_about) - help_menu.addAction(about_action) - - def create_toolbar(self): - """Создание панели инструментов""" - toolbar = QToolBar("Основные инструменты") - self.addToolBar(toolbar) - - refresh_action = QAction('Обновить', self) - refresh_action.triggered.connect(self.load_partners) - toolbar.addAction(refresh_action) - - toolbar.addSeparator() - - add_partner_action = QAction('Добавить партнера', self) - add_partner_action.triggered.connect(self.show_add_partner_form) - toolbar.addAction(add_partner_action) - - def create_statusbar(self): - """Создание статусной строки""" - statusbar = self.statusBar() - user_info = f"Пользователь: {self.user_data.get('full_name', 'Неизвестно')}" - statusbar.showMessage(user_info) - - def create_partners_panel(self): - """Создание панели списка партнеров""" - panel = QWidget() - panel.setMaximumWidth(400) - layout = QVBoxLayout() - layout.setContentsMargins(10, 10, 10, 10) - layout.setSpacing(10) - - # Заголовок - title = QLabel("Партнеры") - title.setFont(QFont("Arial", 16, QFont.Weight.Bold)) - title.setAlignment(Qt.AlignmentFlag.AlignCenter) - title.setStyleSheet("padding: 10px;") - layout.addWidget(title) - - # Панель управления - control_layout = QHBoxLayout() - control_layout.setSpacing(10) - - self.add_button = QPushButton("Добавить партнера") - self.add_button.clicked.connect(self.show_add_partner_form) - self.add_button.setStyleSheet(""" - QPushButton { - background-color: #007acc; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #005a9e; - } - """) - - self.refresh_button = QPushButton("Обновить") - self.refresh_button.clicked.connect(self.load_partners) - self.refresh_button.setStyleSheet(""" - QPushButton { - background-color: #6c757d; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #545b62; - } - """) - - control_layout.addWidget(self.add_button) - control_layout.addWidget(self.refresh_button) - control_layout.addStretch() - - layout.addLayout(control_layout) - - # Список партнеров - self.partners_list = QListWidget() - self.partners_list.setStyleSheet(""" - QListWidget { - border: 1px solid #ddd; - border-radius: 4px; - background-color: white; - outline: none; - } - QListWidget::item { - border: none; - padding: 0px; - } - QListWidget::item:selected { - background-color: transparent; - } - """) - layout.addWidget(self.partners_list) - - # Кнопка расчета материалов - self.calc_button = QPushButton("Калькулятор материалов") - self.calc_button.clicked.connect(self.show_material_calculator) - self.calc_button.setStyleSheet(""" - QPushButton { - background-color: #17a2b8; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #138496; - } - """) - layout.addWidget(self.calc_button) - - panel.setLayout(layout) - return panel - - def create_details_panel(self): - """Создание панели детальной информации""" - panel = QWidget() - layout = QVBoxLayout() - layout.setContentsMargins(10, 10, 10, 10) - layout.setSpacing(10) - - # Заголовок детальной информации - self.details_title = QLabel("Выберите партнера") - self.details_title.setFont(QFont("Arial", 14, QFont.Weight.Bold)) - self.details_title.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.details_title.setStyleSheet("padding: 10px;") - layout.addWidget(self.details_title) - - # Детальная информация о партнере - создаем пустой frame - self.details_frame = QFrame() - self.details_frame.setFrameStyle(QFrame.Shape.StyledPanel) - self.details_frame.setStyleSheet(""" - QFrame { - background-color: #f9f9f9; - border: 1px solid #ddd; - border-radius: 5px; - padding: 15px; - } - """) - self.details_layout = QVBoxLayout() - self.details_layout.setSpacing(8) - self.details_frame.setLayout(self.details_layout) - self.details_frame.hide() - - layout.addWidget(self.details_frame) - - # Кнопки управления выбранным партнером - self.control_buttons = QWidget() - buttons_layout = QHBoxLayout() - buttons_layout.setSpacing(10) - - self.edit_button = QPushButton("Редактировать") - self.edit_button.clicked.connect(self.edit_partner) - self.edit_button.setStyleSheet(""" - QPushButton { - background-color: #007acc; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #005a9e; - } - """) - self.edit_button.hide() - - self.sales_button = QPushButton("История продаж") - self.sales_button.clicked.connect(self.show_sales_history) - self.sales_button.setStyleSheet(""" - QPushButton { - background-color: #28a745; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #218838; - } - """) - self.sales_button.hide() - - self.discount_button = QPushButton("Расчет скидки") - self.discount_button.clicked.connect(self.calculate_discount) - self.discount_button.setStyleSheet(""" - QPushButton { - background-color: #ffc107; - color: black; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #e0a800; - } - """) - self.discount_button.hide() - - buttons_layout.addWidget(self.edit_button) - buttons_layout.addWidget(self.sales_button) - buttons_layout.addWidget(self.discount_button) - buttons_layout.addStretch() - - self.control_buttons.setLayout(buttons_layout) - layout.addWidget(self.control_buttons) - - # Добавляем растягивающийся элемент в конец - layout.addStretch() - - panel.setLayout(layout) - return panel - - def load_partners(self): - """Загрузка списка партнеров из API с авторизацией""" - try: - response = requests.get( - "http://localhost:8000/api/v1/partners", - auth=self.auth, - timeout=10 - ) - - if response.status_code == 200: - self.partners_list.clear() - partners = response.json() - - for partner in partners: - item = QListWidgetItem() - card = PartnerCard(partner) - card.partner_clicked.connect(self.show_partner_details) - - # Устанавливаем фиксированный размер для элемента - item.setSizeHint(card.sizeHint()) - self.partners_list.addItem(item) - self.partners_list.setItemWidget(item, card) - - # Сбрасываем выделение - self.partners_list.clearSelection() - self.current_partner = None - self.details_title.setText("Выберите партнера") - self.details_frame.hide() - self.edit_button.hide() - self.sales_button.hide() - self.discount_button.hide() - - elif response.status_code == 401: - QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла. Пожалуйста, войдите снова.") - self.logout() - else: - QMessageBox.warning(self, "Ошибка", "Не удалось загрузить партнеров") - - except requests.exceptions.ConnectionError: - QMessageBox.critical(self, "Ошибка", "Не удалось подключиться к серверу") - except Exception as e: - QMessageBox.warning(self, "Ошибка", f"Не удалось загрузить партнеров: {str(e)}") - - def show_partner_details(self, partner_data): - """Отображение детальной информации о партнере""" - self.current_partner = partner_data - self.details_title.setText(partner_data['company_name']) - - # Создаем новый виджет для деталей вместо очистки layout - new_details_frame = QFrame() - new_details_frame.setFrameStyle(QFrame.Shape.StyledPanel) - new_details_frame.setStyleSheet(""" - QFrame { - background-color: #f9f9f9; - border: 1px solid #ddd; - border-radius: 5px; - padding: 15px; - } - """) - new_details_layout = QVBoxLayout() - new_details_layout.setSpacing(8) - - # Добавляем новую информацию - details = [ - ("Тип:", partner_data.get('partner_type', 'Не указан')), - ("ИНН:", partner_data.get('inn', 'Не указан')), - ("Директор:", partner_data.get('director_name', 'Не указан')), - ("Телефон:", partner_data.get('phone', 'Не указан')), - ("Email:", partner_data.get('email', 'Не указан')), - ("Рейтинг:", str(partner_data.get('rating', 0))), - ("Адрес:", partner_data.get('legal_address', 'Не указан')), - ("Регионы:", partner_data.get('sales_locations', 'Не указан')) - ] - - # ЗАМЕНИТЕ этот блок кода в методе show_partner_details: -for label, value in details: - row_widget = QWidget() - row_layout = QHBoxLayout(row_widget) - row_layout.setContentsMargins(0, 2, 0, 2) - - label_widget = QLabel(label) - label_widget.setStyleSheet("font-weight: bold; min-width: 100px;") - label_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) - - value_widget = QLabel(str(value)) - value_widget.setWordWrap(True) - value_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) - - row_layout.addWidget(label_widget) - row_layout.addWidget(value_widget) - row_layout.addStretch() - - new_details_layout.addWidget(row_widget) - - # НА этот исправленный вариант: - for label, value in details: - # Создаем контейнер для строки - row_container = QWidget() - row_container.setFixedHeight(30) # Фиксированная высота для каждой строки - row_layout = QHBoxLayout(row_container) - row_layout.setContentsMargins(5, 0, 5, 0) - row_layout.setSpacing(10) - - # Лейбл (название поля) - label_widget = QLabel(label) - label_widget.setStyleSheet(""" - QLabel { - font-weight: bold; - color: #333; - min-width: 120px; - max-width: 120px; - background-color: transparent; - } - """) - label_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) - - # Значение - value_widget = QLabel(str(value)) - value_widget.setStyleSheet(""" - QLabel { - color: #555; - background-color: transparent; - border: none; - } - """) - value_widget.setWordWrap(True) - value_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) - - row_layout.addWidget(label_widget) - row_layout.addWidget(value_widget) - row_layout.addStretch() - - new_details_layout.addWidget(row_container) - - new_details_frame.setLayout(new_details_layout) - - # Заменяем старый details_frame на новый - old_frame = self.details_frame - layout = self.right_panel.layout() - layout.replaceWidget(old_frame, new_details_frame) - old_frame.deleteLater() - - self.details_frame = new_details_frame - self.details_layout = new_details_layout - - self.details_frame.show() - self.edit_button.show() - self.sales_button.show() - self.discount_button.show() - - def show_add_partner_form(self): - """Открытие формы добавления партнера""" - form = PartnerForm(self, auth=self.auth) - form.partner_saved.connect(self.load_partners) - form.exec() - - def edit_partner(self): - """Редактирование выбранного партнера""" - if self.current_partner: - form = PartnerForm(self, self.current_partner, auth=self.auth) - form.partner_saved.connect(self.load_partners) - form.exec() - - def show_sales_history(self): - """Открытие истории продаж партнера""" - if self.current_partner: - sales_window = SalesHistoryWindow(self.current_partner, self, auth=self.auth) - sales_window.exec() - - def calculate_discount(self): - """Расчет скидки для партнера с авторизацией""" - if self.current_partner: - try: - response = requests.get( - f"http://localhost:8000/api/v1/partners/{self.current_partner['partner_id']}/discount", - auth=self.auth, - timeout=10 - ) - - if response.status_code == 200: - discount_data = response.json() - QMessageBox.information( - self, - "Расчет скидки", - f"Партнер: {self.current_partner['company_name']}\n" - f"Общие продажи: {discount_data['total_sales']}\n" - f"Скидка: {discount_data['discount_percent']}%" - ) - elif response.status_code == 401: - QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла. Пожалуйста, войдите снова.") - self.logout() - - except Exception as e: - QMessageBox.warning(self, "Ошибка", f"Не удалось рассчитать скидку: {str(e)}") - - def show_material_calculator(self): - """Открытие калькулятора материалов""" - calculator = MaterialCalculatorWindow(self, auth=self.auth) - calculator.exec() - - def logout(self): - """Выход из системы""" - reply = QMessageBox.question( - self, - "Подтверждение выхода", - "Вы уверены, что хотите выйти из системы?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, - QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - self.close() - # Здесь можно добавить вызов окна авторизации - # или перезапуск приложения - - def show_about(self): - """Показать информацию о программе""" - QMessageBox.about( - self, - "О программе MasterPol", - "MasterPol - Система управления партнерами\n\n" - "Версия: 1.0.0\n" - "Разработчик: Команда MasterPol\n\n" - "Система предназначена для управления партнерами,\n" - "учета продаж и расчета бизнес-показателей." - ) diff --git a/ressult/gui/material_calculator.py b/ressult/gui/material_calculator.py deleted file mode 100644 index 6903972..0000000 --- a/ressult/gui/material_calculator.py +++ /dev/null @@ -1,160 +0,0 @@ -# gui/material_calculator.py -""" -Калькулятор материалов для производства -Соответствует модулю 4 ТЗ -""" -from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel, - QLineEdit, QPushButton, QMessageBox, QFormLayout, - QDoubleSpinBox, QSpinBox) -from PyQt6.QtCore import Qt -import requests -import math - -class MaterialCalculatorWindow(QDialog): - def __init__(self, parent=None, auth=None): - super().__init__(parent) - self.auth = auth # Сохраняем auth, даже если не используется - self.setup_ui() - - def setup_ui(self): - self.setWindowTitle("Калькулятор материалов для производства") - self.setModal(True) - self.resize(400, 300) - - layout = QVBoxLayout() - - # Заголовок - title = QLabel("Расчет количества материала") - title.setAlignment(Qt.AlignmentFlag.AlignCenter) - title.setStyleSheet("font-size: 16px; font-weight: bold; margin: 10px;") - layout.addWidget(title) - - # Форма ввода параметров - form_layout = QFormLayout() - - self.product_type_id = QSpinBox() - self.product_type_id.setRange(1, 100) - form_layout.addRow("ID типа продукции:", self.product_type_id) - - self.material_type_id = QSpinBox() - self.material_type_id.setRange(1, 100) - form_layout.addRow("ID типа материала:", self.material_type_id) - - self.quantity = QSpinBox() - self.quantity.setRange(1, 1000000) - form_layout.addRow("Количество продукции:", self.quantity) - - self.param1 = QDoubleSpinBox() - self.param1.setRange(0.1, 1000.0) - self.param1.setDecimals(2) - form_layout.addRow("Параметр продукции 1:", self.param1) - - self.param2 = QDoubleSpinBox() - self.param2.setRange(0.1, 1000.0) - self.param2.setDecimals(2) - form_layout.addRow("Параметр продукции 2:", self.param2) - - self.product_coeff = QDoubleSpinBox() - self.product_coeff.setRange(0.1, 10.0) - self.product_coeff.setDecimals(3) - self.product_coeff.setValue(1.0) - form_layout.addRow("Коэффициент типа продукции:", self.product_coeff) - - self.defect_percent = QDoubleSpinBox() - self.defect_percent.setRange(0.0, 50.0) - self.defect_percent.setDecimals(1) - self.defect_percent.setSuffix("%") - form_layout.addRow("Процент брака материала:", self.defect_percent) - - layout.addLayout(form_layout) - - # Результат - self.result_label = QLabel() - self.result_label.setStyleSheet("font-weight: bold; color: #007acc; margin: 10px;") - self.result_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(self.result_label) - - # Кнопки - buttons_layout = QHBoxLayout() - - self.calculate_button = QPushButton("Рассчитать") - self.calculate_button.clicked.connect(self.calculate_material) - self.calculate_button.setStyleSheet(""" - QPushButton { - background-color: #007acc; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #005a9e; - } - """) - - self.close_button = QPushButton("Закрыть") - self.close_button.clicked.connect(self.accept) - - buttons_layout.addWidget(self.calculate_button) - buttons_layout.addStretch() - buttons_layout.addWidget(self.close_button) - - layout.addLayout(buttons_layout) - - self.setLayout(layout) - - def calculate_material(self): - """Расчет количества материала с обработкой ошибок""" - try: - # Проверяем валидность входных данных - if (self.param1.value() <= 0 or self.param2.value() <= 0 or - self.product_coeff.value() <= 0 or self.defect_percent.value() < 0): - self.result_label.setText("Ошибка: неверные параметры") - self.result_label.setStyleSheet("color: red; font-weight: bold;") - return - - # Создаем данные для расчета - calculation_data = { - 'product_type_id': self.product_type_id.value(), - 'material_type_id': self.material_type_id.value(), - 'quantity': self.quantity.value(), - 'param1': self.param1.value(), - 'param2': self.param2.value(), - 'product_coeff': self.product_coeff.value(), - 'defect_percent': self.defect_percent.value() - } - - # Локальный расчет (без API) - material_quantity = self.calculate_locally(calculation_data) - - if material_quantity >= 0: - self.result_label.setText( - f"Необходимое количество материала: {material_quantity} единиц" - ) - self.result_label.setStyleSheet("color: #007acc; font-weight: bold;") - else: - self.result_label.setText("Ошибка: неверные параметры расчета") - self.result_label.setStyleSheet("color: red; font-weight: bold;") - - except Exception as e: - self.result_label.setText(f"Ошибка расчета: {str(e)}") - self.result_label.setStyleSheet("color: red; font-weight: bold;") - - def calculate_locally(self, data): - """Локальный расчет материалов""" - try: - import math - - # Расчет количества материала на одну единицу продукции - material_per_unit = data['param1'] * data['param2'] * data['product_coeff'] - - # Расчет общего количества материала с учетом брака - total_material = material_per_unit * data['quantity'] - total_material_with_defect = total_material * (1 + data['defect_percent'] / 100) - - # Округление до целого числа в большую сторону - return math.ceil(total_material_with_defect) - - except: - return -1 diff --git a/ressult/gui/orders_panel.py b/ressult/gui/orders_panel.py deleted file mode 100644 index 64576a3..0000000 --- a/ressult/gui/orders_panel.py +++ /dev/null @@ -1,344 +0,0 @@ -# gui/orders_panel.py -""" -Панель управления заказами и продажами -""" -from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QTableWidget, QTableWidgetItem, QPushButton, - QHeaderView, QMessageBox, QDateEdit, QComboBox, - QLineEdit, QFormLayout, QDialog, QDoubleSpinBox) -from PyQt6.QtCore import Qt, QDate -from PyQt6.QtGui import QFont -import requests - -class OrderForm(QDialog): - """Форма для добавления/редактирования заказа""" - - order_saved = pyqtSignal() - - def __init__(self, parent=None, order_data=None, auth=None, partners=None): - super().__init__(parent) - self.order_data = order_data - self.auth = auth - self.partners = partners or [] - self.setup_ui() - - def setup_ui(self): - self.setWindowTitle("Добавить заказ" if not self.order_data else "Редактировать заказ") - self.setModal(True) - self.resize(400, 300) - - layout = QVBoxLayout() - - # Форма ввода данных - form_layout = QFormLayout() - - # Выбор партнера - self.partner_combo = QComboBox() - self.partner_combo.addItem("Выберите партнера", None) - for partner in self.partners: - self.partner_combo.addItem(partner['company_name'], partner['partner_id']) - form_layout.addRow("Партнер*:", self.partner_combo) - - # Название продукта - self.product_name = QLineEdit() - self.product_name.setPlaceholderText("Введите название продукта") - form_layout.addRow("Продукт*:", self.product_name) - - # Количество - self.quantity = QDoubleSpinBox() - self.quantity.setRange(0.01, 100000.0) - self.quantity.setDecimals(2) - form_layout.addRow("Количество*:", self.quantity) - - # Дата продажи - self.sale_date = QDateEdit() - self.sale_date.setDate(QDate.currentDate()) - self.sale_date.setCalendarPopup(True) - form_layout.addRow("Дата продажи*:", self.sale_date) - - layout.addLayout(form_layout) - - # Кнопки - buttons_layout = QHBoxLayout() - - self.save_button = QPushButton("Сохранить") - self.save_button.clicked.connect(self.save_order) - self.save_button.setStyleSheet(""" - QPushButton { - background-color: #28a745; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #218838; - } - """) - - self.cancel_button = QPushButton("Отмена") - self.cancel_button.clicked.connect(self.reject) - - buttons_layout.addWidget(self.save_button) - buttons_layout.addWidget(self.cancel_button) - buttons_layout.addStretch() - - layout.addLayout(buttons_layout) - - self.setLayout(layout) - - # Если редактирование, заполняем форму - if self.order_data: - self.fill_form() - - def fill_form(self): - """Заполнение формы данными заказа""" - data = self.order_data - - # Устанавливаем партнера - partner_index = self.partner_combo.findData(data.get('partner_id')) - if partner_index >= 0: - self.partner_combo.setCurrentIndex(partner_index) - - self.product_name.setText(data.get('product_name', '')) - self.quantity.setValue(float(data.get('quantity', 0))) - - # Устанавливаем дату - sale_date = data.get('sale_date') - if sale_date: - date = QDate.fromString(sale_date, 'yyyy-MM-dd') - if date.isValid(): - self.sale_date.setDate(date) - - def validate_form(self): - """Валидация данных формы""" - errors = [] - - if not self.partner_combo.currentData(): - errors.append("Выберите партнера") - - if not self.product_name.text().strip(): - errors.append("Введите название продукта") - - if self.quantity.value() <= 0: - errors.append("Количество должно быть больше 0") - - return errors - - def save_order(self): - """Сохранение заказа""" - errors = self.validate_form() - if errors: - QMessageBox.warning(self, "Ошибка валидации", "\n".join(errors)) - return - - order_data = { - 'partner_id': self.partner_combo.currentData(), - 'product_name': self.product_name.text().strip(), - 'quantity': self.quantity.value(), - 'sale_date': self.sale_date.date().toString('yyyy-MM-dd') - } - - try: - if self.order_data: - # Обновление существующего заказа - response = requests.put( - f"http://localhost:8000/api/v1/sales/{self.order_data['sale_id']}", - json=order_data, - auth=self.auth, - timeout=10 - ) - else: - # Создание нового заказа - response = requests.post( - "http://localhost:8000/api/v1/sales", - json=order_data, - auth=self.auth, - timeout=10 - ) - - if response.status_code == 200: - self.order_saved.emit() - QMessageBox.information(self, "Успех", "Заказ успешно сохранен") - self.accept() - elif response.status_code == 401: - QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла. Пожалуйста, войдите снова.") - else: - error_msg = response.json().get('detail', 'Неизвестная ошибка') - QMessageBox.warning(self, "Ошибка", f"Не удалось сохранить заказ: {error_msg}") - - except requests.exceptions.ConnectionError: - QMessageBox.critical(self, "Ошибка", "Не удалось подключиться к серверу") - except Exception as e: - QMessageBox.critical(self, "Ошибка", f"Ошибка подключения: {str(e)}") - -class OrdersPanel(QWidget): - """Панель управления заказами""" - - def __init__(self, auth=None): - super().__init__() - self.auth = auth - self.partners = [] - self.setup_ui() - self.load_partners() - self.load_orders() - - def setup_ui(self): - """Настройка интерфейса панели заказов""" - layout = QVBoxLayout() - layout.setContentsMargins(10, 10, 10, 10) - layout.setSpacing(10) - - # Заголовок - title = QLabel("Управление заказами") - title.setFont(QFont("Arial", 16, QFont.Weight.Bold)) - title.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(title) - - # Панель управления - control_layout = QHBoxLayout() - - self.add_button = QPushButton("Добавить заказ") - self.add_button.clicked.connect(self.show_add_order_form) - self.add_button.setStyleSheet(""" - QPushButton { - background-color: #007acc; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #005a9e; - } - """) - - self.refresh_button = QPushButton("Обновить") - self.refresh_button.clicked.connect(self.load_orders) - - control_layout.addWidget(self.add_button) - control_layout.addWidget(self.refresh_button) - control_layout.addStretch() - - layout.addLayout(control_layout) - - # Таблица заказов - self.orders_table = QTableWidget() - self.orders_table.setColumnCount(6) - self.orders_table.setHorizontalHeaderLabels([ - "ID", "Партнер", "Продукт", "Количество", "Дата", "Действия" - ]) - self.orders_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) - self.orders_table.setStyleSheet(""" - QTableWidget { - border: 1px solid #ddd; - border-radius: 4px; - background-color: white; - } - QTableWidget::item { - padding: 8px; - } - """) - - layout.addWidget(self.orders_table) - - self.setLayout(layout) - - def load_partners(self): - """Загрузка списка партнеров""" - try: - response = requests.get( - "http://localhost:8000/api/v1/partners", - auth=self.auth, - timeout=10 - ) - if response.status_code == 200: - self.partners = response.json() - except: - self.partners = [] - - def load_orders(self): - """Загрузка списка заказов""" - try: - response = requests.get( - "http://localhost:8000/api/v1/sales", - auth=self.auth, - timeout=10 - ) - if response.status_code == 200: - orders = response.json() - self.display_orders(orders) - elif response.status_code == 401: - QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла") - except Exception as e: - QMessageBox.warning(self, "Ошибка", f"Не удалось загрузить заказы: {str(e)}") - - def display_orders(self, orders): - """Отображение заказов в таблице""" - self.orders_table.setRowCount(len(orders)) - - for row, order in enumerate(orders): - self.orders_table.setItem(row, 0, QTableWidgetItem(str(order.get('sale_id', '')))) - self.orders_table.setItem(row, 1, QTableWidgetItem(order.get('company_name', 'Неизвестно'))) - self.orders_table.setItem(row, 2, QTableWidgetItem(order.get('product_name', ''))) - self.orders_table.setItem(row, 3, QTableWidgetItem(str(order.get('quantity', '')))) - self.orders_table.setItem(row, 4, QTableWidgetItem(order.get('sale_date', ''))) - - # Кнопки действий - actions_widget = QWidget() - actions_layout = QHBoxLayout(actions_widget) - actions_layout.setContentsMargins(4, 4, 4, 4) - actions_layout.setSpacing(4) - - delete_button = QPushButton("Удалить") - delete_button.setStyleSheet(""" - QPushButton { - background-color: #dc3545; - color: white; - border: none; - padding: 4px 8px; - border-radius: 3px; - font-size: 11px; - } - QPushButton:hover { - background-color: #c82333; - } - """) - delete_button.clicked.connect(lambda checked, o=order: self.delete_order(o)) - - actions_layout.addWidget(delete_button) - actions_layout.addStretch() - - self.orders_table.setCellWidget(row, 5, actions_widget) - - def show_add_order_form(self): - """Открытие формы добавления заказа""" - form = OrderForm(self, auth=self.auth, partners=self.partners) - form.order_saved.connect(self.load_orders) - form.exec() - - def delete_order(self, order): - """Удаление заказа""" - reply = QMessageBox.question( - self, - "Подтверждение удаления", - f"Вы уверены, что хотите удалить заказ #{order.get('sale_id')}?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, - QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - try: - response = requests.delete( - f"http://localhost:8000/api/v1/sales/{order['sale_id']}", - auth=self.auth, - timeout=10 - ) - if response.status_code == 200: - self.load_orders() - elif response.status_code == 401: - QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла") - except Exception as e: - QMessageBox.warning(self, "Ошибка", f"Не удалось удалить заказ: {str(e)}") diff --git a/ressult/gui/partner_form.py b/ressult/gui/partner_form.py deleted file mode 100644 index 9d2310b..0000000 --- a/ressult/gui/partner_form.py +++ /dev/null @@ -1,193 +0,0 @@ -# gui/partner_form.py (обновленный) -""" -Форма для добавления/редактирования партнера с поддержкой авторизации -""" -from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel, - QLineEdit, QComboBox, QPushButton, QMessageBox, - QFormLayout, QSpinBox) -from PyQt6.QtCore import pyqtSignal -import requests - -class PartnerForm(QDialog): - partner_saved = pyqtSignal() - - def __init__(self, parent=None, partner_data=None, auth=None): - super().__init__(parent) - self.partner_data = partner_data - self.auth = auth - self.setup_ui() - - def setup_ui(self): - self.setWindowTitle("Добавить партнера" if not self.partner_data else "Редактировать партнера") - self.setModal(True) - self.resize(500, 400) - - layout = QVBoxLayout() - - # Форма ввода данных - form_layout = QFormLayout() - - self.company_name = QLineEdit() - self.company_name.setPlaceholderText("Введите наименование компании") - form_layout.addRow("Наименование компании*:", self.company_name) - - self.inn = QLineEdit() - self.inn.setPlaceholderText("Введите ИНН") - form_layout.addRow("ИНН*:", self.inn) - - self.partner_type = QComboBox() - self.partner_type.addItems(["", "distributor", "retail", "wholesale", "dealer"]) - self.partner_type.setPlaceholderText("Выберите тип партнера") - form_layout.addRow("Тип партнера:", self.partner_type) - - self.rating = QSpinBox() - self.rating.setRange(0, 100) - self.rating.setSuffix("%") - form_layout.addRow("Рейтинг:", self.rating) - - self.legal_address = QLineEdit() - self.legal_address.setPlaceholderText("Введите юридический адрес") - form_layout.addRow("Юридический адрес:", self.legal_address) - - self.director_name = QLineEdit() - self.director_name.setPlaceholderText("Введите ФИО директора") - form_layout.addRow("ФИО директора:", self.director_name) - - self.phone = QLineEdit() - self.phone.setPlaceholderText("+7XXXXXXXXXX") - form_layout.addRow("Телефон:", self.phone) - - self.email = QLineEdit() - self.email.setPlaceholderText("email@example.com") - form_layout.addRow("Email:", self.email) - - self.sales_locations = QLineEdit() - self.sales_locations.setPlaceholderText("Москва, Санкт-Петербург...") - form_layout.addRow("Регионы продаж:", self.sales_locations) - - layout.addLayout(form_layout) - - # Кнопки - buttons_layout = QHBoxLayout() - - self.save_button = QPushButton("Сохранить") - self.save_button.clicked.connect(self.save_partner) - self.save_button.setStyleSheet(""" - QPushButton { - background-color: #28a745; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #218838; - } - """) - - self.cancel_button = QPushButton("Отмена") - self.cancel_button.clicked.connect(self.reject) - - buttons_layout.addWidget(self.save_button) - buttons_layout.addWidget(self.cancel_button) - buttons_layout.addStretch() - - layout.addLayout(buttons_layout) - - self.setLayout(layout) - - # Если редактирование, заполняем форму - if self.partner_data: - self.fill_form() - - def fill_form(self): - """Заполнение формы данными партнера""" - data = self.partner_data - self.company_name.setText(data.get('company_name', '')) - self.inn.setText(data.get('inn', '')) - - partner_type = data.get('partner_type', '') - if partner_type: - index = self.partner_type.findText(partner_type) - if index >= 0: - self.partner_type.setCurrentIndex(index) - - # Безопасное преобразование рейтинга - rating = data.get('rating', 0) - if isinstance(rating, float): - rating = int(rating) - self.rating.setValue(rating) - - self.legal_address.setText(data.get('legal_address', '')) - self.director_name.setText(data.get('director_name', '')) - self.phone.setText(data.get('phone', '')) - self.email.setText(data.get('email', '')) - self.sales_locations.setText(data.get('sales_locations', '')) - - def validate_form(self): - """Валидация данных формы""" - errors = [] - - if not self.company_name.text().strip(): - errors.append("Наименование компании обязательно") - - if not self.inn.text().strip(): - errors.append("ИНН обязателен") - - if self.phone.text() and not self.phone.text().startswith('+'): - errors.append("Телефон должен начинаться с '+'") - - return errors - - def save_partner(self): - """Сохранение партнера с авторизацией""" - errors = self.validate_form() - if errors: - QMessageBox.warning(self, "Ошибка валидации", "\n".join(errors)) - return - - partner_data = { - 'company_name': self.company_name.text().strip(), - 'inn': self.inn.text().strip(), - 'partner_type': self.partner_type.currentText() or None, - 'rating': self.rating.value(), - 'legal_address': self.legal_address.text().strip() or None, - 'director_name': self.director_name.text().strip() or None, - 'phone': self.phone.text().strip() or None, - 'email': self.email.text().strip() or None, - 'sales_locations': self.sales_locations.text().strip() or None - } - - try: - if self.partner_data: - # Обновление существующего партнера - response = requests.put( - f"http://localhost:8000/api/v1/partners/{self.partner_data['partner_id']}", - json=partner_data, - auth=self.auth, - timeout=10 - ) - else: - # Создание нового партнера - response = requests.post( - "http://localhost:8000/api/v1/partners", - json=partner_data, - auth=self.auth, - timeout=10 - ) - - if response.status_code == 200: - self.partner_saved.emit() - QMessageBox.information(self, "Успех", "Партнер успешно сохранен") - self.accept() - elif response.status_code == 401: - QMessageBox.warning(self, "Ошибка авторизации", "Сессия истекла. Пожалуйста, войдите снова.") - else: - error_msg = response.json().get('detail', 'Неизвестная ошибка') - QMessageBox.warning(self, "Ошибка", f"Не удалось сохранить партнера: {error_msg}") - - except requests.exceptions.ConnectionError: - QMessageBox.critical(self, "Ошибка", "Не удалось подключиться к серверу") - except Exception as e: - QMessageBox.critical(self, "Ошибка", f"Ошибка подключения: {str(e)}") diff --git a/ressult/gui/partner_form.py.bak b/ressult/gui/partner_form.py.bak deleted file mode 100644 index da98b84..0000000 --- a/ressult/gui/partner_form.py.bak +++ /dev/null @@ -1,186 +0,0 @@ -# gui/partner_form.py -""" -Форма для добавления/редактирования партнера -Соответствует модулю 3 ТЗ -""" -from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel, - QLineEdit, QComboBox, QPushButton, QMessageBox, - QFormLayout, QSpinBox) -from PyQt6.QtCore import pyqtSignal -import requests - -class PartnerForm(QDialog): - partner_saved = pyqtSignal() - - def __init__(self, parent=None, partner_data=None): - super().__init__(parent) - self.partner_data = partner_data - self.setup_ui() - - def setup_ui(self): - self.setWindowTitle("Добавить партнера" if not self.partner_data else "Редактировать партнера") - self.setModal(True) - self.resize(500, 400) - - layout = QVBoxLayout() - - # Форма ввода данных - form_layout = QFormLayout() - - self.company_name = QLineEdit() - self.company_name.setPlaceholderText("Введите наименование компании") - form_layout.addRow("Наименование компании*:", self.company_name) - - self.inn = QLineEdit() - self.inn.setPlaceholderText("Введите ИНН") - form_layout.addRow("ИНН*:", self.inn) - - self.partner_type = QComboBox() - self.partner_type.addItems(["", "distributor", "retail", "wholesale", "dealer"]) - self.partner_type.setPlaceholderText("Выберите тип партнера") - form_layout.addRow("Тип партнера:", self.partner_type) - - self.rating = QSpinBox() - self.rating.setRange(0, 100) - self.rating.setSuffix("%") - form_layout.addRow("Рейтинг:", self.rating) - - self.legal_address = QLineEdit() - self.legal_address.setPlaceholderText("Введите юридический адрес") - form_layout.addRow("Юридический адрес:", self.legal_address) - - self.director_name = QLineEdit() - self.director_name.setPlaceholderText("Введите ФИО директора") - form_layout.addRow("ФИО директора:", self.director_name) - - self.phone = QLineEdit() - self.phone.setPlaceholderText("+7XXXXXXXXXX") - form_layout.addRow("Телефон:", self.phone) - - self.email = QLineEdit() - self.email.setPlaceholderText("email@example.com") - form_layout.addRow("Email:", self.email) - - self.sales_locations = QLineEdit() - self.sales_locations.setPlaceholderText("Москва, Санкт-Петербург...") - form_layout.addRow("Регионы продаж:", self.sales_locations) - - layout.addLayout(form_layout) - - # Кнопки - buttons_layout = QHBoxLayout() - - self.save_button = QPushButton("Сохранить") - self.save_button.clicked.connect(self.save_partner) - self.save_button.setStyleSheet(""" - QPushButton { - background-color: #28a745; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: bold; - } - QPushButton:hover { - background-color: #218838; - } - """) - - self.cancel_button = QPushButton("Отмена") - self.cancel_button.clicked.connect(self.reject) - - buttons_layout.addWidget(self.save_button) - buttons_layout.addWidget(self.cancel_button) - buttons_layout.addStretch() - - layout.addLayout(buttons_layout) - - self.setLayout(layout) - - # Если редактирование, заполняем форму - if self.partner_data: - self.fill_form() - - # gui/partner_form.py (исправленный метод fill_form) - def fill_form(self): - """Заполнение формы данными партнера""" - data = self.partner_data - self.company_name.setText(data.get('company_name', '')) - self.inn.setText(data.get('inn', '')) - - partner_type = data.get('partner_type', '') - if partner_type: - index = self.partner_type.findText(partner_type) - if index >= 0: - self.partner_type.setCurrentIndex(index) - - # Безопасное преобразование рейтинга к int - rating = data.get('rating', 0) - if isinstance(rating, float): - rating = int(rating) - self.rating.setValue(rating) - - self.legal_address.setText(data.get('legal_address', '')) - self.director_name.setText(data.get('director_name', '')) - self.phone.setText(data.get('phone', '')) - self.email.setText(data.get('email', '')) - self.sales_locations.setText(data.get('sales_locations', '')) - - def validate_form(self): - """Валидация данных формы""" - errors = [] - - if not self.company_name.text().strip(): - errors.append("Наименование компании обязательно") - - if not self.inn.text().strip(): - errors.append("ИНН обязателен") - - if self.phone.text() and not self.phone.text().startswith('+'): - errors.append("Телефон должен начинаться с '+'") - - return errors - - def save_partner(self): - """Сохранение партнера""" - errors = self.validate_form() - if errors: - QMessageBox.warning(self, "Ошибка валидации", "\n".join(errors)) - return - - partner_data = { - 'company_name': self.company_name.text().strip(), - 'inn': self.inn.text().strip(), - 'partner_type': self.partner_type.currentText() or None, - 'rating': self.rating.value(), - 'legal_address': self.legal_address.text().strip() or None, - 'director_name': self.director_name.text().strip() or None, - 'phone': self.phone.text().strip() or None, - 'email': self.email.text().strip() or None, - 'sales_locations': self.sales_locations.text().strip() or None - } - - try: - if self.partner_data: - # Обновление существующего партнера - response = requests.put( - f"http://localhost:8000/api/v1/partners/{self.partner_data['partner_id']}", - json=partner_data - ) - else: - # Создание нового партнера - response = requests.post( - "http://localhost:8000/api/v1/partners", - json=partner_data - ) - - if response.status_code == 200: - self.partner_saved.emit() - QMessageBox.information(self, "Успех", "Партнер успешно сохранен") - self.accept() - else: - error_msg = response.json().get('detail', 'Неизвестная ошибка') - QMessageBox.warning(self, "Ошибка", f"Не удалось сохранить партнера: {error_msg}") - - except Exception as e: - QMessageBox.critical(self, "Ошибка", f"Ошибка подключения: {str(e)}") diff --git a/ressult/gui/sales_history.py b/ressult/gui/sales_history.py deleted file mode 100644 index 1c4c571..0000000 --- a/ressult/gui/sales_history.py +++ /dev/null @@ -1,91 +0,0 @@ -# gui/sales_history.py -""" -Окно истории продаж партнера -Соответствует модулю 4 ТЗ -""" -from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel, - QTableWidget, QTableWidgetItem, QPushButton, - QHeaderView, QMessageBox) -from PyQt6.QtCore import Qt -import requests - -class SalesHistoryWindow(QDialog): - def __init__(self, partner_data, parent=None): - super().__init__(parent) - self.partner_data = partner_data - self.setup_ui() - self.load_sales_history() - - def setup_ui(self): - self.setWindowTitle(f"История продаж - {self.partner_data['company_name']}") - self.setModal(True) - self.resize(800, 400) - - layout = QVBoxLayout() - - # Заголовок - title = QLabel(f"История реализации продукции\n{self.partner_data['company_name']}") - title.setAlignment(Qt.AlignmentFlag.AlignCenter) - title.setStyleSheet("font-size: 16px; font-weight: bold; margin: 10px;") - layout.addWidget(title) - - # Таблица продаж - self.sales_table = QTableWidget() - self.sales_table.setColumnCount(4) - self.sales_table.setHorizontalHeaderLabels([ - "ID", "Наименование продукции", "Количество", "Дата продажи" - ]) - self.sales_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) - layout.addWidget(self.sales_table) - - # Статистика - self.stats_label = QLabel() - self.stats_label.setStyleSheet("font-weight: bold; margin: 10px;") - layout.addWidget(self.stats_label) - - # Кнопки - buttons_layout = QHBoxLayout() - - self.close_button = QPushButton("Закрыть") - self.close_button.clicked.connect(self.accept) - - buttons_layout.addStretch() - buttons_layout.addWidget(self.close_button) - - layout.addLayout(buttons_layout) - - self.setLayout(layout) - - def load_sales_history(self): - """Загрузка истории продаж партнера""" - try: - response = requests.get( - f"http://localhost:8000/api/v1/sales/partner/{self.partner_data['partner_id']}" - ) - if response.status_code == 200: - sales_data = response.json() - self.display_sales_data(sales_data) - else: - QMessageBox.warning(self, "Ошибка", "Не удалось загрузить историю продаж") - - except Exception as e: - QMessageBox.critical(self, "Ошибка", f"Ошибка подключения: {str(e)}") - - def display_sales_data(self, sales_data): - """Отображение данных о продажах в таблице""" - self.sales_table.setRowCount(len(sales_data)) - - total_quantity = 0 - for row, sale in enumerate(sales_data): - self.sales_table.setItem(row, 0, QTableWidgetItem(str(sale['sale_id']))) - self.sales_table.setItem(row, 1, QTableWidgetItem(sale['product_name'])) - self.sales_table.setItem(row, 2, QTableWidgetItem(str(sale['quantity']))) - self.sales_table.setItem(row, 3, QTableWidgetItem(sale['sale_date'])) - - total_quantity += float(sale['quantity']) - - # Обновление статистики - self.stats_label.setText( - f"Общее количество проданной продукции: {total_quantity}\n" - f"Всего продаж: {len(sales_data)}" - ) diff --git a/ressult/requirements.txt b/ressult/requirements.txt deleted file mode 100644 index ace2609..0000000 --- a/ressult/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -# requirements.txt -fastapi==0.104.1 -uvicorn==0.24.0 -psycopg2-binary==2.9.9 -python-dotenv==1.0.0 -python-multipart==0.0.6 -pandas==2.1.3 -openpyxl==3.1.2 -aiofiles==23.2.1 -pydantic[email]==2.5.0 -bcrypt==4.1.1 diff --git a/ressult/run.py b/ressult/run.py deleted file mode 100644 index 8bddcfa..0000000 --- a/ressult/run.py +++ /dev/null @@ -1,17 +0,0 @@ -# run.py -""" -Точка входа для запуска сервера -""" -import uvicorn -import os -from dotenv import load_dotenv - -load_dotenv() - -if __name__ == "__main__": - uvicorn.run( - "app.main:app", - host=os.getenv('HOST', '0.0.0.0'), - port=int(os.getenv('PORT', 8000)), - reload=os.getenv('DEBUG', 'False').lower() == 'true' - ) diff --git a/ressult/run_gui.py b/ressult/run_gui.py deleted file mode 100644 index aacd95d..0000000 --- a/ressult/run_gui.py +++ /dev/null @@ -1,51 +0,0 @@ -# run_gui.py -""" -Главный модуль запуска GUI приложения с авторизацией -""" -import sys -import os -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from gui.login_window import LoginWindow -from gui.main_window import MainWindow -from PyQt6.QtWidgets import QApplication -from PyQt6.QtCore import QTimer - -class ApplicationController: - """Контроллер приложения, управляющий авторизацией и главным окном""" - - def __init__(self): - self.app = QApplication(sys.argv) - self.login_window = None - self.main_window = None - self.current_user = None - - def show_login(self): - """Показать окно авторизации""" - self.login_window = LoginWindow() - self.login_window.login_success.connect(self.on_login_success) - self.login_window.show() - - def on_login_success(self, user_data): - """Обработка успешной авторизации""" - self.current_user = user_data - self.login_window.close() - self.show_main_window() - - def show_main_window(self): - """Показать главное окно приложения""" - self.main_window = MainWindow(self.current_user) - self.main_window.show() - - def run(self): - """Запуск приложения""" - self.show_login() - return self.app.exec() - -def main(): - """Точка входа приложения""" - controller = ApplicationController() - sys.exit(controller.run()) - -if __name__ == "__main__": - main() diff --git a/robbery/master_pol-module_1_2/.gitignore b/robbery/master_pol-module_1_2/.gitignore deleted file mode 100644 index eb7063d..0000000 --- a/robbery/master_pol-module_1_2/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -**/__pycache__/ -.venv/ \ No newline at end of file diff --git a/robbery/master_pol-module_1_2/.idea/.gitignore b/robbery/master_pol-module_1_2/.idea/.gitignore deleted file mode 100644 index eaf91e2..0000000 --- a/robbery/master_pol-module_1_2/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/robbery/master_pol-module_1_2/.idea/.name b/robbery/master_pol-module_1_2/.idea/.name deleted file mode 100644 index 11a5d8e..0000000 --- a/robbery/master_pol-module_1_2/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -main.py \ No newline at end of file diff --git a/robbery/master_pol-module_1_2/.idea/inspectionProfiles/profiles_settings.xml b/robbery/master_pol-module_1_2/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/robbery/master_pol-module_1_2/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/robbery/master_pol-module_1_2/.idea/master_pol-module_1_2.iml b/robbery/master_pol-module_1_2/.idea/master_pol-module_1_2.iml deleted file mode 100644 index 9bd607d..0000000 --- a/robbery/master_pol-module_1_2/.idea/master_pol-module_1_2.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/robbery/master_pol-module_1_2/.idea/misc.xml b/robbery/master_pol-module_1_2/.idea/misc.xml deleted file mode 100644 index 953f9db..0000000 --- a/robbery/master_pol-module_1_2/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/robbery/master_pol-module_1_2/.idea/modules.xml b/robbery/master_pol-module_1_2/.idea/modules.xml deleted file mode 100644 index f8a1763..0000000 --- a/robbery/master_pol-module_1_2/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/robbery/master_pol-module_1_2/README.md b/robbery/master_pol-module_1_2/README.md deleted file mode 100644 index 1c67087..0000000 --- a/robbery/master_pol-module_1_2/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# MasterPol - -Графическое приложение на PyQt6 для работы с базой данных MySQL. - -## Подготовка проекта - -1. **Клонируйте репозиторий и перейдите в папку проекта:** - - ```sh - git clone <адрес-репозитория> - cd master_pol - ``` - -2. **Создайте и активируйте виртуальное окружение:** - - ```sh - python -m venv .venv - .venv\Scripts\activate # Windows - # source .venv/bin/activate # Linux/MacOS - ``` - -3. **Установите зависимости:** - - ```sh - pip install -r requirements.txt - ``` - -4. **Создайте базу данных и выполните SQL-скрипт:** - - - Запустите MySQL и выполните скрипт `app/database/script.sql` для создания необходимых таблиц и данных: - - ```sh - mysql -u -p < app/database/script.sql - ``` - - - Замените `` и `` на свои значения. - -5. **Проверьте параметры подключения к базе данных:** - - Откройте файл `app/database/db.py` и убедитесь, что значения для подключения (host, user, password, database) указаны верно. - -## Запуск приложения - -```sh -python app/main.py -``` - -## Структура проекта - -- `app/main.py` — точка входа, запуск приложения -- `app/components/` — компоненты интерфейса -- `app/database/` — работа с БД, скрипты и настройки -- `app/pages/` — страницы приложения -- `app/res/` — ресурсы (цвета, шрифты) - ---- diff --git a/robbery/master_pol-module_1_2/app/components/edit_partner_dialog.py b/robbery/master_pol-module_1_2/app/components/edit_partner_dialog.py deleted file mode 100644 index f596812..0000000 --- a/robbery/master_pol-module_1_2/app/components/edit_partner_dialog.py +++ /dev/null @@ -1,108 +0,0 @@ -from PyQt6.QtWidgets import ( - QDialog, - QVBoxLayout, - QFormLayout, - QLineEdit, - QPushButton, - QComboBox, - QSpinBox, - QMessageBox, -) -from PyQt6.QtCore import Qt -from res.colors import ACCENT_COLOR -from dto.partners_dto import PartnerUpdateDto, PartnersInfo - - -class EditPartnerDialog(QDialog): - def __init__(self, partner_data: PartnersInfo, parent=None): - super().__init__(parent) - self.partner_data = partner_data - self.setup_ui() - self.load_partner_types() - self.fill_form() - self.result = None - - def setup_ui(self): - self.setWindowTitle("Редактирование партнера") - self.setFixedSize(500, 400) - - layout = QVBoxLayout() - form_layout = QFormLayout() - - # Создаем поля формы - self.partner_type = QComboBox() - self.partner_name = QLineEdit() - self.first_name = QLineEdit() - self.last_name = QLineEdit() - self.middle_name = QLineEdit() - self.email = QLineEdit() - self.phone = QLineEdit() - self.address = QLineEdit() - self.inn = QLineEdit() - self.rating = QSpinBox() - self.rating.setRange(0, 10) - - # Добавляем поля в форму - form_layout.addRow("Тип партнера:", self.partner_type) - form_layout.addRow("Название:", self.partner_name) - form_layout.addRow("Имя директора:", self.first_name) - form_layout.addRow("Фамилия директора:", self.last_name) - form_layout.addRow("Отчество директора:", self.middle_name) - form_layout.addRow("Email:", self.email) - form_layout.addRow("Телефон:", self.phone) - form_layout.addRow("Адрес:", self.address) - form_layout.addRow("ИНН:", self.inn) - form_layout.addRow("Рейтинг:", self.rating) - - # Кнопки - self.save_button = QPushButton("Сохранить") - self.cancel_button = QPushButton("Отмена") - - self.save_button.clicked.connect(self.save_changes) - self.cancel_button.clicked.connect(self.reject) - - layout.addLayout(form_layout) - layout.addWidget(self.save_button) - layout.addWidget(self.cancel_button) - - self.setLayout(layout) - - # Стили - self.setStyleSheet( - f""" - QPushButton {{ - background-color: {ACCENT_COLOR}; - padding: 8px; - border-radius: 4px; - }} - """ - ) - - def load_partner_types(self): - types = ['ООО', "ЗАО"] - for i, val in enumerate(types): - self.partner_type.addItem(val, i + 1) - - def fill_form(self): - pass - def save_changes(self): - try: - partner_data = PartnerUpdateDto( - id=self.partner_data.id, - partner_type_id=self.partner_type.currentData(), - partner_name=self.partner_name.text(), - first_name=self.first_name.text(), - last_name=self.last_name.text(), - middle_name=self.middle_name.text(), - email=self.email.text(), - phone=self.phone.text(), - address=self.address.text(), - inn=self.inn.text(), - rating=self.rating.value(), - ) - db.update_partner(partner_data) - self.accept() - except Exception as e: - QMessageBox.critical( - self, "Ошибка", f"Не удалось сохранить изменения: {str(e)}" - ) diff --git a/robbery/master_pol-module_1_2/app/components/partner_card.py b/robbery/master_pol-module_1_2/app/components/partner_card.py deleted file mode 100644 index 8b462a3..0000000 --- a/robbery/master_pol-module_1_2/app/components/partner_card.py +++ /dev/null @@ -1,94 +0,0 @@ -from dataclasses import dataclass -from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QHBoxLayout, QFrame -from PyQt6.QtCore import Qt, pyqtSignal -from res.colors import ACCENT_COLOR, SECONDARY_COLOR -from res.fonts import MAIN_FONT -from dto.partners_dto import PartnersInfo - - - - -class PartnerCard(QFrame): - doubleClicked = pyqtSignal(PartnersInfo) - - def __init__(self, info: PartnersInfo): - super().__init__() - self.info = info - - self.init_ui() - self.set_styles() - - def mouseDoubleClickEvent(self, a0): - self.doubleClicked.emit(self.info) - return super().mouseDoubleClickEvent(a0) - - def init_ui(self): - main_layout = QVBoxLayout() - self.setLayout(main_layout) - - # Верхняя строка: Тип | Наименование и скидка - header_layout = QHBoxLayout() - header_text = QLabel(f"{self.info.type_name} | {self.info.partner_name}") - header_text.setObjectName("partnerHeader") - discount_text = QLabel(f"{self.info.discount}%") - discount_text.setObjectName("partnerDiscount") - - header_layout.addWidget(header_text) - header_layout.addWidget(discount_text, alignment=Qt.AlignmentFlag.AlignRight) - - # Информация о директоре - director_text = QLabel(f"Директор") - director_text.setObjectName("fieldLabel") - director_name = QLabel( - f"{self.info.last_name_director} {self.info.first_name_director} {self.info.middle_name_director}" - ) - - # Контактная информация - phone_text = QLabel(f"+{self.info.phone_partner}") - - # Рейтинг - rating_layout = QHBoxLayout() - rating_label = QLabel("Рейтинг:") - rating_label.setObjectName("fieldLabel") - rating_value = QLabel(str(self.info.rating)) - rating_layout.addWidget(rating_label) - rating_layout.addWidget(rating_value) - rating_layout.addStretch() - - # Добавляем все элементы в главный layout - main_layout.addLayout(header_layout) - main_layout.addWidget(director_text) - main_layout.addWidget(director_name) - main_layout.addWidget(phone_text) - main_layout.addLayout(rating_layout) - - def set_styles(self): - self.setStyleSheet( - """ - PartnerCard { - border: 1px solid #ccc; - border-radius: 4px; - padding: 10px; - margin: 5px; - background-color: white; - } - QLabel { - font-family: %s; - } - #partnerHeader { - font-size: 18px; - font-weight: bold; - color: %s; - } - #partnerDiscount { - font-size: 18px; - font-weight: bold; - color: %s; - } - #fieldLabel { - color: gray; - font-size: 14px; - } - """ - % (MAIN_FONT, ACCENT_COLOR, SECONDARY_COLOR) - ) diff --git a/robbery/master_pol-module_1_2/app/database/db.py b/robbery/master_pol-module_1_2/app/database/db.py deleted file mode 100644 index 54590f0..0000000 --- a/robbery/master_pol-module_1_2/app/database/db.py +++ /dev/null @@ -1,84 +0,0 @@ -import pymysql as psql -from dto.partners_dto import PartnerUpdateDto - - -class Database: - def __init__(self, host, user, password, db): - self.connection = psql.connect( - host=host, - user=user, - password=password, - database=db, - cursorclass=psql.cursors.DictCursor, - ) - - def authorize_user(self, username, password): - query = "SELECT * FROM users WHERE username=%s AND password=%s" - with self.connection.cursor() as cur: - cur.execute(query, (username, password)) - result = cur.fetchone() - return result is not None - - def execute_select(self, query, params=None): - """Выполняет SELECT запрос и возвращает результаты""" - with self.connection.cursor() as cur: - if params: - cur.execute(query, params) - else: - cur.execute(query) - return cur.fetchall() - - def get_partner_types(self): - """Получает все типы партнеров из таблицы partner_types""" - query = "SELECT * FROM partners_type" - with self.connection.cursor() as cur: - cur.execute(query) - return cur.fetchall() - - def update_partner(self, partners_info: PartnerUpdateDto): - with self.connection.cursor() as cur: - cur.callproc( - "upd_partner", - ( - partners_info.partner_type_id, - partners_info.id, - partners_info.partner_name, - partners_info.first_name, - partners_info.last_name, - partners_info.middle_name, - partners_info.email, - partners_info.phone, - partners_info.address, - partners_info.inn, - partners_info.rating, - ), - ) - self.connection.commit() - - def get_disc(self, partner_name): - """ - Получает скидку для партнера, вызывая функцию get_disc из БД - """ - # Сначала получим ID партнера по его имени - query = "SELECT id FROM partners WHERE partner_name = %s" - with self.connection.cursor() as cur: - cur.execute(query, (partner_name,)) - result = cur.fetchone() - - if not result: - return 0 - - # Вызываем функцию get_disc из БД - query = "SELECT get_disc(%s) as discount" - cur.execute(query, (result["id"],)) - discount_result = cur.fetchone() - - return discount_result["discount"] if discount_result else 0 - - -db = None -try: - db = Database(host="localhost", user="root", password="", db="master_pol") - print("Database connection established.") -except psql.MySQLError as e: - print(f"Error connecting to database: {e}") diff --git a/robbery/master_pol-module_1_2/app/database/script.sql b/robbery/master_pol-module_1_2/app/database/script.sql deleted file mode 100644 index 7d1b571..0000000 --- a/robbery/master_pol-module_1_2/app/database/script.sql +++ /dev/null @@ -1,460 +0,0 @@ -CREATE DATABASE master_pol; -use master_pol; - -CREATE TABLE `partners` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `partner_type_id` INTEGER NOT NULL, - `partner_name` VARCHAR(255) NOT NULL, - `first_name_director` VARCHAR(50) NOT NULL, - `last_name_director` VARCHAR(50) NOT NULL, - `middle_name_director` VARCHAR(255), - `email_partner` VARCHAR(100) NOT NULL, - `phone_partner` VARCHAR(15) NOT NULL, - `address` VARCHAR(255) NOT NULL, - `INN` VARCHAR(10) NOT NULL, - `rating` INTEGER NOT NULL, - `logo` LONGBLOB, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `partners_type` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `name` VARCHAR(255), - PRIMARY KEY(`id`) -); - - -CREATE TABLE `products` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `article` VARCHAR(10) NOT NULL, - `name` VARCHAR(100) NOT NULL, - `product_type_id` INTEGER NOT NULL, - `description` VARCHAR(255), - `picture` LONGBLOB, - `min_price_partners` DECIMAL(10,2) NOT NULL, - `cert_quality` LONGBLOB, - `standard_number` VARCHAR(255), - `selfcost` DECIMAL(10,2), - `length` DECIMAL(10,2), - `width` DECIMAL(10,2), - `height` DECIMAL(10,2), - `weight_no_package` DECIMAL(10,2), - `weight_with_package` DECIMAL(10,2), - `time_to_create_min` INTEGER, - `workshop_number` INTEGER, - `people_count_production` INTEGER, - `product_current_stock` INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `products_types` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `name` VARCHAR(70) NOT NULL, - `coefficent` DECIMAL(3,2) NOT NULL, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `product_partners` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `product_id` INTEGER NOT NULL, - `partner_id` INTEGER NOT NULL, - `amount` INTEGER NOT NULL, - `sale_date` DATE NOT NULL, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `employees` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `employee_type_id` INTEGER NOT NULL, - `first_name` VARCHAR(50) NOT NULL, - `last_name` VARCHAR(50) NOT NULL, - `middle_name` VARCHAR(60) NULL, - `birth_date` DATE NOT NULL, - `passport_data` VARCHAR(11) NOT NULL, - `bank_details` VARCHAR(100) NOT NULL, - `has_family` BOOLEAN, - `health_status` VARCHAR(25), - PRIMARY KEY(`id`) -); - - -CREATE TABLE `employees_types` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `name` VARCHAR(50) NOT NULL, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `users` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `username` VARCHAR(30) NOT NULL, - `password` VARCHAR(80) NOT NULL, - `employee_id` INTEGER NOT NULL, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `materials` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `material_type_id` INTEGER NOT NULL, - `supplier_id` INTEGER NOT NULL, - `name` VARCHAR(60) NOT NULL, - `package_quantity` INTEGER NOT NULL, - `unit` VARCHAR(20) NOT NULL, - `cost` DECIMAL(8,2) NOT NULL, - `image` LONGBLOB, - `min_stock` INTEGER, - `material_current_stock` INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `materials_type` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `name` VARCHAR(50) NOT NULL, - `defect_percent` DECIMAL(10,2) NOT NULL, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `products_recipes` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `product_id` INTEGER NOT NULL, - `material_id` INTEGER NOT NULL, - `material_count` INTEGER NOT NULL, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `partners_rating_history` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `partner_id` INTEGER NOT NULL, - `new_rating` INTEGER NOT NULL, - `changed` DATETIME NOT NULL, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `orders` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `partner_id` INTEGER NOT NULL, - `manager_id` INTEGER NOT NULL, - `total_price` DECIMAL(10,2) NOT NULL, - `order_payment` DECIMAL(10,2) NOT NULL DEFAULT 0, - `created` DATETIME NOT NULL, - `status` ENUM('created', 'waiting prepayment', 'prepayment received', 'completed', 'canceled', 'ready for shipment', 'pending', 'in production') NOT NULL, - `prepayment_date` DATETIME, - `payment_date` DATETIME, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `products_orders` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `order_id` INTEGER NOT NULL, - `product_id` INTEGER NOT NULL, - `quantity` INTEGER NOT NULL, - `agreed_price_per` DECIMAL(8,2), - `production_date` DATE, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `suppliers` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `name` VARCHAR(50) NOT NULL, - `INN` VARCHAR(10) NOT NULL, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `materials_supply_history` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `material_id` INTEGER NOT NULL, - `supplier_id` INTEGER NOT NULL, - `quantity` INTEGER NOT NULL, - `delivery_date` DATE NOT NULL, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `materials_movement` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `material_id` INTEGER NOT NULL, - `amount` INTEGER NOT NULL, - `movement_type` ENUM('incoming', 'reserve', 'write off') NOT NULL DEFAULT 'incoming', - `movement_date` DATETIME NOT NULL, - PRIMARY KEY(`id`) -); - - -CREATE TABLE `employees_access` ( - `id` INTEGER NOT NULL AUTO_INCREMENT UNIQUE, - `employee_id` INTEGER NOT NULL, - `door_id` INTEGER NOT NULL, - `access_date` DATETIME NOT NULL, - PRIMARY KEY(`id`) -); - - -ALTER TABLE `partners` -ADD FOREIGN KEY(`partner_type_id`) REFERENCES `partners_type`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `products` -ADD FOREIGN KEY(`product_type_id`) REFERENCES `products_types`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `product_partners` -ADD FOREIGN KEY(`product_id`) REFERENCES `products`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `product_partners` -ADD FOREIGN KEY(`partner_id`) REFERENCES `partners`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `employees` -ADD FOREIGN KEY(`employee_type_id`) REFERENCES `employees_types`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `users` -ADD FOREIGN KEY(`employee_id`) REFERENCES `employees`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `materials` -ADD FOREIGN KEY(`material_type_id`) REFERENCES `materials_type`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `products_recipes` -ADD FOREIGN KEY(`product_id`) REFERENCES `products`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `products_recipes` -ADD FOREIGN KEY(`material_id`) REFERENCES `materials`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `partners_rating_history` -ADD FOREIGN KEY(`partner_id`) REFERENCES `partners`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `orders` -ADD FOREIGN KEY(`partner_id`) REFERENCES `partners`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `orders` -ADD FOREIGN KEY(`manager_id`) REFERENCES `employees`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `products_orders` -ADD FOREIGN KEY(`order_id`) REFERENCES `orders`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `products_orders` -ADD FOREIGN KEY(`product_id`) REFERENCES `products`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `materials` -ADD FOREIGN KEY(`supplier_id`) REFERENCES `suppliers`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `materials_supply_history` -ADD FOREIGN KEY(`material_id`) REFERENCES `materials`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `materials_supply_history` -ADD FOREIGN KEY(`supplier_id`) REFERENCES `suppliers`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `materials_movement` -ADD FOREIGN KEY(`material_id`) REFERENCES `materials`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; -ALTER TABLE `employees_access` -ADD FOREIGN KEY(`employee_id`) REFERENCES `employees`(`id`) -ON UPDATE NO ACTION ON DELETE NO ACTION; - -INSERT INTO materials_type (name, defect_percent) VALUES -('Тип материала 1', 0.001), -('Тип материала 2', 0.0095), -('Тип материала 3', 0.0028), -('Тип материала 4', 0.0055), -('Тип материала 5', 0.0034); - -INSERT INTO products_types (name, coefficent) VALUES -('Ламинат', 2.35), -('Массивная доска', 5.15), -('Паркетная доска', 4.34), -('Пробковое покрытие', 1.5); - -INSERT INTO partners_type (name) VALUES -('ЗАО'), -('ООО'), -('ПАО'), -('ОАО'); - - -INSERT INTO partners (partner_type_id, partner_name, first_name_director, last_name_director, middle_name_director, email_partner, phone_partner, address, INN, rating) VALUES -(1, 'База Строитель', 'Александра', 'Иванова', 'Ивановна', 'aleksandraivanova@ml.ru', '4931234567', '652050, Кемеровская область, город Юрга, ул. Лесная, 15', '2222455179', 7), -(2, 'Паркет 29', 'Василий', 'Петров', 'Петрович', 'vppetrov@vl.ru', '9871235678', '164500, Архангельская область, город Северодвинск, ул. Строителей, 18', '3333888520', 7), -(3, 'Стройсервис', 'Андрей', 'Соловьев', 'Николаевич', 'ansolovev@st.ru', '8122233200', '188910, Ленинградская область, город Приморск, ул. Парковая, 21', '4440391035', 7), -(4, 'Ремонт и отделка', 'Екатерина', 'Воробьева', 'Валерьевна', 'ekaterina.vorobeva@ml.ru', '4442223311', '143960, Московская область, город Реутов, ул. Свободы, 51', '1111520857', 5), -(1, 'МонтажПро', 'Степан', 'Степанов', 'Сергеевич', 'stepanov@stepan.ru', '9128883333', '309500, Белгородская область, город Старый Оскол, ул. Рабочая, 122', '5552431140', 10); - -INSERT INTO products (article, name, product_type_id, min_price_partners) VALUES -('8758385', 'Паркетная доска Ясень темный однополосная 14 мм', 3, 4456.90), -('8858958', 'Инженерная доска Дуб Французская елка однополосная 12 мм', 3, 7330.99), -('7750282', 'Ламинат Дуб дымчато-белый 33 класс 12 мм', 1, 1799.33), -('7028748', 'Ламинат Дуб серый 32 класс 8 мм с фаской', 1, 3890.41), -('5012543', 'Пробковое напольное клеевое покрытие 32 класс 4 мм', 4, 5450.59); - -INSERT INTO product_partners (product_id, partner_id, amount, sale_date) VALUES -(1, 1, 15500, '2023-03-23'), -(3, 1, 12350, '2023-12-18'), -(4, 1, 37400, '2024-06-07'), -(2, 2, 35000, '2022-12-02'), -(5, 2, 1250, '2023-05-17'), -(3, 2, 1000, '2024-06-07'), -(1, 2, 7550, '2024-07-01'), -(1, 3, 7250, '2023-01-22'), -(2, 3, 2500, '2024-07-05'), -(4, 4, 59050, '2023-03-20'), -(3, 4, 37200, '2024-03-12'), -(5, 4, 4500, '2024-05-14'), -(3, 5, 50000, '2023-09-19'), -(4, 5, 670000, '2023-11-10'), -(1, 5, 35000, '2024-04-15'), -(2, 5, 25000, '2024-06-12'); - --- === 1. Типы сотрудников === -INSERT INTO employees_types (name) -VALUES -('Менеджер'), -('Бухгалтер'), -('Программист'), -('Охранник'), -('Уборщик'); - --- === 2. Сотрудники === -INSERT INTO employees ( - employee_type_id, first_name, last_name, middle_name, birth_date, - passport_data, bank_details, has_family, health_status -) -VALUES --- Менеджеры -(1, 'Иван', 'Петров', 'Сергеевич', '1988-03-15', '40051234567', '123456789', TRUE, 'Хорошее'), -(1, 'Мария', 'Сидорова', 'Игоревна', '1990-11-02', '40057891234', '987654321', FALSE, 'Отличное'), - --- Программист -(3, 'Андрей', 'Кузнецов', 'Алексеевич', '1995-07-21', '40101234567', '111122223333', TRUE, 'Хорошее'), - --- Бухгалтер -(2, 'Елена', 'Морозова', 'Павловна', '1982-05-08', '40104561234', '444455556666', TRUE, 'Удовлетворительное'), - --- Охранник -(4, 'Сергей', 'Волков', 'Владимирович', '1979-09-10', '40205678901', '555566667777', FALSE, 'Хорошее'), - --- Уборщик -(5, 'Наталья', 'Орлова', 'Геннадьевна', '1975-12-25', '40307891234', '888899990000', TRUE, 'Хорошее'); - --- === 3. Пользователи === --- Пользователи, связанные с менеджерами -INSERT INTO users (username, password, employee_id) -VALUES -('ivan', 'test', 1), -('manager_maria', 'hashed_password_456', 2); - - -CREATE VIEW show_partners -AS -SELECT p.id, pt.name AS type_name, p.partner_name, p.first_name_director, p.last_name_director, p.middle_name_director, p.phone_partner, p.rating -FROM partners p JOIN partners_type pt -ON -p.partner_type_id = pt.id; - - -DELIMITER // -CREATE PROCEDURE add_parther (IN p_partner_type_id INT, IN p_partner_name VARCHAR(255), -IN p_first_name_director VARCHAR(50), IN p_last_name_director VARCHAR(50), IN p_middle_name_director VARCHAR(255), -IN p_email_partner VARCHAR(100), IN p_phone_partner VARCHAR(15), IN p_address VARCHAR(255), IN p_INN VARCHAR(10), IN p_rating INT) - -BEGIN - INSERT INTO partners ( - partner_type_id, - partner_name, - first_name_director, - last_name_director, - middle_name_director, - email_partner, - phone_partner, - address, - INN, - rating - ) VALUES ( - p_partner_type_id, - p_partner_name, - p_first_name_director, - p_last_name_director, - p_middle_name_director, - p_email_partner, - p_phone_partner, - p_address, - p_INN, - p_rating - ); -END // - -DELIMITER ; - - -DELIMITER // - -CREATE PROCEDURE upd_partner (IN p_partner_type_id INT, IN p_id INT, IN p_partner_name VARCHAR(255), -IN p_first_name_director VARCHAR(50), IN p_last_name_director VARCHAR(50), IN p_middle_name_director VARCHAR(255), -IN p_email_partner VARCHAR(100), IN p_phone_partner VARCHAR(15), IN p_address VARCHAR(255), IN p_INN VARCHAR(10), IN p_rating INT) - -BEGIN - UPDATE partners - SET - partner_type_id = p_partner_type_id, - partner_name = p_partner_name, - first_name_director = p_first_name_director, - last_name_director = p_last_name_director, - middle_name_director = p_middle_name_director, - email_partner = p_email_partner, - phone_partner = p_phone_partner, - address = p_address, - INN = p_INN, - rating = p_rating - WHERE id = p_id; - -END // - -DELIMITER ; - - -DELIMITER // - -CREATE FUNCTION get_disc(partner_id INT) -RETURNS INT -BEGIN - - DECLARE total_amount INT; - - SELECT SUM(amount) INTO total_amount - FROM product_partners - WHERE partner_id = partner_id; - - IF total_amount >= 300000 THEN RETURN 15; - ELSEIF total_amount >= 50000 THEN RETURN 10; - ELSEIF total_amount >= 10000 THEN RETURN 5; - ELSE RETURN 0; - END IF; - -END // - -DELIMITER ; - - - -DELIMITER // - -CREATE PROCEDURE partner_history(IN p_partner_id INT) -BEGIN - SELECT - pr.name AS product_name, - pp.amount AS quantity, - pp.sale_date AS sale_date - FROM product_partners pp JOIN products pr - ON - pp.product_id = pr.id - WHERE pp.partner_id = p_partner_id - ORDER BY pp.sale_date DESC; -END// - -DELIMITER ; diff --git a/robbery/master_pol-module_1_2/app/dto/partners_dto.py b/robbery/master_pol-module_1_2/app/dto/partners_dto.py deleted file mode 100644 index 63b1107..0000000 --- a/robbery/master_pol-module_1_2/app/dto/partners_dto.py +++ /dev/null @@ -1,29 +0,0 @@ -from dataclasses import dataclass - - -@dataclass -class PartnersInfo: - id: int - type_name: str - partner_name: str - first_name_director: str - last_name_director: str - middle_name_director: str - phone_partner: str - rating: int - discount: float - - -@dataclass -class PartnerUpdateDto: - id: int - partner_type_id: int - partner_name: str - first_name: str - last_name: str - middle_name: str - email: str - phone: str - address: str - inn: str - rating: int diff --git a/robbery/master_pol-module_1_2/app/main.py b/robbery/master_pol-module_1_2/app/main.py deleted file mode 100644 index 2f48f74..0000000 --- a/robbery/master_pol-module_1_2/app/main.py +++ /dev/null @@ -1,11 +0,0 @@ -from PyQt6.QtWidgets import QApplication -from PyQt6.QtGui import QIcon -from pages.auth_page import AuthPage - -app = QApplication([]) - -app.setWindowIcon(QIcon("app/res/imgs/master_pol.ico")) -start_page = AuthPage() -start_page.show() - -app.exec() diff --git a/robbery/master_pol-module_1_2/app/pages/auth_page.py b/robbery/master_pol-module_1_2/app/pages/auth_page.py deleted file mode 100644 index 2881fa0..0000000 --- a/robbery/master_pol-module_1_2/app/pages/auth_page.py +++ /dev/null @@ -1,94 +0,0 @@ -from PyQt6.QtWidgets import ( - QWidget, - QLabel, - QFormLayout, - QPushButton, - QMessageBox, - QLineEdit, - QVBoxLayout, -) -from PyQt6.QtCore import Qt -from res.colors import ACCENT_COLOR, SECONDARY_COLOR, ACCENT_COLOR_HOVER -from res.fonts import MAIN_FONT - - -class AuthPage(QWidget): - def __init__(self): - super().__init__() - self.setup_window() - self.init_ui() - self.set_styles() - - def setup_window(self): - self.setWindowTitle("Авторизация") - self.setFixedSize(400, 250) - - def init_ui(self): - self.main_layout = QVBoxLayout() - self.form_layout: QFormLayout = QFormLayout() - - self.title = QLabel("Авторизация") - self.title.setObjectName("title") - - self.username_label = QLabel("Логин:") - self.password_label = QLabel("Пароль:") - - self.username_input = QLineEdit() - self.password_input = QLineEdit() - self.password_input.setEchoMode(QLineEdit.EchoMode.Password) - - self.login_button = QPushButton("Войти") - - self.form_layout.addRow(self.username_label, self.username_input) - self.form_layout.addRow(self.password_label, self.password_input) - self.form_layout.addRow(self.login_button) - - self.setLayout(self.main_layout) - self.main_layout.addWidget(self.title, alignment=Qt.AlignmentFlag.AlignHCenter) - - self.main_layout.addStretch() - self.main_layout.addLayout(self.form_layout) - self.main_layout.addStretch() - - self.login_button.clicked.connect(self.handle_login) - - def handle_login(self): - username = self.username_input.text() - password = self.password_input.text() - - if not username or not password: - QMessageBox.warning(self, "Ошибка", "Пожалуйста, заполните все поля.") - return - - from pages.partners_page import PartnersPage - - self.partners_page = PartnersPage() - self.partners_page.show() - self.close() - - def set_styles(self): - self.setStyleSheet( - """QLabel { font-size: 16px; font-family: %(MAIN_FONT)s} - #title { - font-size: 24px; - font-weight: bold; - color: %(ACCENT_COLOR)s; - } - QPushButton { - background-color: %(ACCENT_COLOR)s; - border: 1px solid black; - color: %(SECONDARY_COLOR)s; - font-weight: bold; - padding: 5px; - } - QPushButton:hover { - background-color: %(ACCENT_COLOR_HOVER)s; - } - """ - % { - "ACCENT_COLOR": ACCENT_COLOR, - "SECONDARY_COLOR": SECONDARY_COLOR, - "MAIN_FONT": MAIN_FONT, - "ACCENT_COLOR_HOVER": ACCENT_COLOR_HOVER, - } - ) diff --git a/robbery/master_pol-module_1_2/app/pages/partners_page.py b/robbery/master_pol-module_1_2/app/pages/partners_page.py deleted file mode 100644 index 9b2e804..0000000 --- a/robbery/master_pol-module_1_2/app/pages/partners_page.py +++ /dev/null @@ -1,130 +0,0 @@ -from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QScrollArea, QVBoxLayout -from PyQt6.QtCore import Qt -from components.partner_card import PartnerCard, PartnersInfo -from res.colors import ACCENT_COLOR - - -class PartnersPage(QWidget): - def __init__(self): - super().__init__() - self.setup_window() - self.init_ui() - self.load_partners() - - def setup_window(self): - self.setWindowTitle("Партнеры") - self.resize(800, 600) - - def init_ui(self): - main_layout = QVBoxLayout() - self.setLayout(main_layout) - - # Заголовок - title = QLabel("Партнеры") - title.setObjectName("title") - title.setStyleSheet( - f""" - #title {{ - font-size: 24px; - font-weight: bold; - color: {ACCENT_COLOR}; - margin-bottom: 20px; - }} - """ - ) - main_layout.addWidget(title, alignment=Qt.AlignmentFlag.AlignHCenter) - - # Создаем область прокрутки - scroll_area = QScrollArea() - scroll_area.setWidgetResizable(True) - scroll_content = QWidget() - self.partners_layout = QVBoxLayout(scroll_content) - scroll_area.setWidget(scroll_content) - main_layout.addWidget(scroll_area) - - def handle_partner_double_click(self, partner_info: PartnersInfo): - from components.edit_partner_dialog import EditPartnerDialog - - dialog = EditPartnerDialog(partner_info, self) - dialog.exec() - - def load_partners(self): - # Тестовые данные партнеров - test_partners = [ - { - "id": 1, - "type_name": "Золотой партнер", - "partner_name": "ООО 'ТехноПрофи'", - "first_name_director": "Иван", - "last_name_director": "Петров", - "middle_name_director": "Сергеевич", - "phone_partner": "+7 (495) 123-45-67", - "rating": 4.8, - "discount": 15.0 - }, - { - "id": 2, - "type_name": "Серебряный партнер", - "partner_name": "ИП Сидоров А.В.", - "first_name_director": "Алексей", - "last_name_director": "Сидоров", - "middle_name_director": "Викторович", - "phone_partner": "+7 (495) 234-56-78", - "rating": 4.2, - "discount": 10.0 - }, - { - "id": 3, - "type_name": "Бронзовый партнер", - "partner_name": "ООО 'СтройМастер'", - "first_name_director": "Мария", - "last_name_director": "Иванова", - "middle_name_director": "Олеговна", - "phone_partner": "+7 (495) 345-67-89", - "rating": 3.9, - "discount": 7.5 - }, - { - "id": 4, - "type_name": "Золотой партнер", - "partner_name": "АО 'ПромИнвест'", - "first_name_director": "Сергей", - "last_name_director": "Козлов", - "middle_name_director": "Анатольевич", - "phone_partner": "+7 (495) 456-78-90", - "rating": 4.9, - "discount": 18.0 - }, - { - "id": 5, - "type_name": "Стандартный партнер", - "partner_name": "ООО 'ТоргСервис'", - "first_name_director": "Ольга", - "last_name_director": "Смирнова", - "middle_name_director": "Дмитриевна", - "phone_partner": "+7 (495) 567-89-01", - "rating": 3.5, - "discount": 5.0 - } - ] - - # Создаем карточки партнеров на основе тестовых данных - for partner in test_partners: - partner_info = PartnersInfo( - id=partner["id"], - type_name=partner["type_name"], - partner_name=partner["partner_name"], - first_name_director=partner["first_name_director"], - last_name_director=partner["last_name_director"], - middle_name_director=partner["middle_name_director"], - phone_partner=partner["phone_partner"], - rating=partner["rating"], - discount=partner["discount"], - ) - - # Создаем и добавляем карточку партнера - partner_card = PartnerCard(partner_info) - partner_card.doubleClicked.connect(self.handle_partner_double_click) - self.partners_layout.addWidget(partner_card) - - self.partners_layout.addStretch() \ No newline at end of file diff --git a/robbery/master_pol-module_1_2/app/res/colors.py b/robbery/master_pol-module_1_2/app/res/colors.py deleted file mode 100644 index b4165d4..0000000 --- a/robbery/master_pol-module_1_2/app/res/colors.py +++ /dev/null @@ -1,4 +0,0 @@ -MAIN_COLOR = "#FFFFFF" -SECONDARY_COLOR = "#F4E8D3" -ACCENT_COLOR = "#67BA80" -ACCENT_COLOR_HOVER = "#529265" diff --git a/robbery/master_pol-module_1_2/app/res/fonts.py b/robbery/master_pol-module_1_2/app/res/fonts.py deleted file mode 100644 index 207a164..0000000 --- a/robbery/master_pol-module_1_2/app/res/fonts.py +++ /dev/null @@ -1 +0,0 @@ -MAIN_FONT = "Segoe UI" \ No newline at end of file diff --git a/robbery/master_pol-module_1_2/app/res/imgs/master_pol.ico b/robbery/master_pol-module_1_2/app/res/imgs/master_pol.ico deleted file mode 100644 index 9744b0a..0000000 Binary files a/robbery/master_pol-module_1_2/app/res/imgs/master_pol.ico and /dev/null differ diff --git a/robbery/master_pol-module_1_2/app/res/imgs/master_pol.png b/robbery/master_pol-module_1_2/app/res/imgs/master_pol.png deleted file mode 100644 index c192a72..0000000 Binary files a/robbery/master_pol-module_1_2/app/res/imgs/master_pol.png and /dev/null differ diff --git a/robbery/master_pol-module_1_2/app/res/styles.py b/robbery/master_pol-module_1_2/app/res/styles.py deleted file mode 100644 index e76ca02..0000000 --- a/robbery/master_pol-module_1_2/app/res/styles.py +++ /dev/null @@ -1,35 +0,0 @@ -from string import Template -from res.colors import MAIN_COLOR, SECONDARY_COLOR, ACCENT_COLOR -from res.fonts import MAIN_FONT - -styles_template = Template( - """ - QWidget { - font-family: {MAIN_FONT}; - background-color: {MAIN_COLOR} - color: {SECONDARY_COLOR}; - } - QPushButton { - background-color: {ACCENT_COLOR}; - border: none; - padding: 8px 16px; - border-radius: 4px; - } - QPushButton:hover { - background-color: {SECONDARY_COLOR}; - } - QLineEdit { - padding: 6px; - border: 1px solid {ACCENT_COLOR}; - border-radius: 4px; - background-color: white; - } -""" -) - -styles = styles_template.substitute( - MAIN_FONT=MAIN_FONT, - MAIN_COLOR=MAIN_COLOR, - SECONDARY_COLOR=SECONDARY_COLOR, - ACCENT_COLOR=ACCENT_COLOR, -) diff --git a/robbery/master_pol-module_1_2/requirements.txt b/robbery/master_pol-module_1_2/requirements.txt deleted file mode 100644 index bbd2d4f..0000000 Binary files a/robbery/master_pol-module_1_2/requirements.txt and /dev/null differ diff --git a/service_requests.db b/service_requests.db index 569cec3..65c3422 100644 Binary files a/service_requests.db and b/service_requests.db differ diff --git a/service_requests_v2.db b/service_requests_v2.db deleted file mode 100644 index 7c9edb6..0000000 Binary files a/service_requests_v2.db and /dev/null differ