After Graduate Update

This commit is contained in:
Daniel 2025-11-26 19:31:33 +03:00
parent b92a91ab37
commit c6917dd85e
69 changed files with 7540 additions and 0 deletions

574
ressult/gui/main_window Normal file
View file

@ -0,0 +1,574 @@
# 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"
"учета продаж и расчета бизнес-показателей."
)