1822 lines
76 KiB
Python
1822 lines
76 KiB
Python
import sys
|
||
import sqlite3
|
||
import os
|
||
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'
|
||
}
|
||
|
||
|
||
# === Инициализация базы данных 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 (full_name, position, hire_date, salary, is_active)
|
||
VALUES ('Иванов Алексей Петрович', 'Менеджер по продажам', '2023-01-15', 50000.00, 1)
|
||
""")
|
||
|
||
# Добавляем партнеров
|
||
partners_data = [
|
||
("ООО", "ООО «СтройГрад»", "г. Москва, ул. Ленина, 10", "770123456789", "Иван Петров", "+79001112233", "buildgrad@example.com", 4.5, "Москва, СПб", 1500000.00, 5.0),
|
||
("ИП", "ИП Сидоров А.В.", "г. Казань, пр. Победы, 5", "165432109876", "Андрей Сидоров", "+79054445566", "sidorov@example.com", 4.2, "Казань", 800000.00, 3.0),
|
||
("ТОО", "Торговый дом «Полимер+»", "г. Екатеринбург, ул. Мира, 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_type, company_name, legal_address, inn, director_name, phone, email, rating, sales_locations, total_sales, discount_rate)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""", p)
|
||
|
||
# Добавляем поставщиков
|
||
suppliers_data = [
|
||
("ООО", "ООО «Сырье-Про»", "770987654321", "Петр Васильев", "+79012345678", "syrie@example.com", 4.7, "Древесина, клей, ламинация"),
|
||
("ИП", "ИП Колесников С.И.", "163218765432", "Сергей Колесников", "+79087654321", "kolesnikov@example.com", 4.3, "ПВХ, пластификаторы"),
|
||
]
|
||
|
||
for s in suppliers_data:
|
||
cursor.execute("""
|
||
INSERT OR IGNORE INTO suppliers
|
||
(supplier_type, company_name, inn, contact_person, phone, email, rating, supplied_materials)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||
""", s)
|
||
|
||
# Добавляем материалы
|
||
materials_data = [
|
||
("Древесина", "Дубовая доска", 1, 1.0, "м³", "Дубовая доска высшего сорта", 15000.00, 100.5, 20.0),
|
||
("Клей", "Клей для ламината", 1, 25.0, "кг", "Водостойкий клей", 450.00, 500.0, 50.0),
|
||
("ПВХ", "ПВХ пленка", 2, 50.0, "м²", "Декоративная ПВХ пленка", 320.00, 800.0, 100.0),
|
||
]
|
||
|
||
for m in materials_data:
|
||
cursor.execute("""
|
||
INSERT OR IGNORE INTO materials
|
||
(material_type, material_name, supplier_id, package_quantity, unit_of_measure, description, cost_per_unit, current_stock, min_stock_level)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""", m)
|
||
|
||
# Добавляем продукцию
|
||
products_data = [
|
||
("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),
|
||
("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),
|
||
("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
|
||
(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, 0.05), # Ламинат Quick-Step -> Дубовая доска
|
||
(1, 2, 0.8), # Ламинат Quick-Step -> Клей
|
||
(1, 3, 1.2), # Ламинат Quick-Step -> ПВХ пленка
|
||
(2, 1, 0.06), # Ламинат Classen -> Дубовая доска
|
||
(2, 2, 1.0), # Ламинат Classen -> Клей
|
||
(2, 3, 1.5), # Ламинат Classen -> ПВХ пленка
|
||
(3, 3, 0.3), # Плинтус -> ПВХ пленка
|
||
]
|
||
|
||
for pm in product_materials_data:
|
||
cursor.execute("""
|
||
INSERT OR IGNORE INTO product_materials (product_id, material_id, material_quantity)
|
||
VALUES (?, ?, ?)
|
||
""", pm)
|
||
|
||
# Добавляем запасы готовой продукции
|
||
finished_goods_data = [
|
||
(1, 500.0, 0, 50.0),
|
||
(2, 300.0, 0, 30.0),
|
||
(3, 1000.0, 0, 100.0),
|
||
]
|
||
|
||
for fg in finished_goods_data:
|
||
cursor.execute("""
|
||
INSERT OR IGNORE INTO finished_goods_stock (product_id, current_stock, reserved_stock, min_stock_level)
|
||
VALUES (?, ?, ?, ?)
|
||
""", fg)
|
||
|
||
|
||
# === Диалог авторизации ===
|
||
class AuthDialog(QDialog):
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
self.setWindowTitle("Авторизация - Мастер пол")
|
||
self.setFixedSize(350, 200)
|
||
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 {{
|
||
padding: 8px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
}}
|
||
""")
|
||
|
||
self.authenticated = False
|
||
self.user_id = None
|
||
self.user_name = 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.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")
|
||
hint.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
hint.setStyleSheet("color: #666; font-size: 12px; margin-top: 10px;")
|
||
layout.addWidget(hint)
|
||
|
||
self.setLayout(layout)
|
||
|
||
def login(self):
|
||
login = self.login_edit.text().strip()
|
||
password = self.pass_edit.text()
|
||
|
||
# Простая проверка (в реальной системе - проверка в БД)
|
||
if login == "manager" and password == "pass123":
|
||
self.authenticated = True
|
||
self.user_id = 1 # ID менеджера из тестовых данных
|
||
self.user_name = "Иванов Алексей Петрович"
|
||
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):
|
||
super().__init__()
|
||
self.user_id = user_id
|
||
self.user_name = user_name
|
||
|
||
self.setWindowTitle(f"Мастер пол - Система управления (Пользователь: {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"Пользователь: {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)
|
||
|
||
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.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.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.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("🔄 Обновить")
|
||
|
||
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("🔄 Обновить")
|
||
|
||
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("🔄 Обновить")
|
||
|
||
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}")
|
||
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 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 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
|
||
|
||
# Здесь должна быть реализация загрузки и редактирования заявки
|
||
QMessageBox.information(self, "Информация", "Редактирование заявки будет реализовано в полной версии.")
|
||
|
||
def view_order(self):
|
||
"""Просмотр деталей заявки"""
|
||
order_id = self.get_selected_order_id()
|
||
if not order_id:
|
||
return
|
||
|
||
# Здесь должна быть реализация просмотра деталей заявки
|
||
QMessageBox.information(self, "Информация", "Просмотр заявки будет реализовано в полной версии.")
|
||
|
||
def update_order_status(self):
|
||
"""Обновление статуса заявки"""
|
||
order_id = self.get_selected_order_id()
|
||
if not order_id:
|
||
return
|
||
|
||
# Здесь должна быть реализация обновления статуса
|
||
QMessageBox.information(self, "Информация", "Обновление статуса будет реализовано в полной версии.")
|
||
|
||
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()
|
||
|
||
|
||
# === Точка входа ===
|
||
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)
|
||
main_window.show()
|
||
|
||
sys.exit(app.exec())
|