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