master-floor/main3.py
2025-11-26 19:31:33 +03:00

2461 lines
105 KiB
Python
Raw 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.

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())