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