master-floor/ressult/gui/main_window
2025-11-26 19:31:33 +03:00

574 lines
23 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

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