From bce57e79f2662c8fb0275940970e71825db9ff2f Mon Sep 17 00:00:00 2001 From: helldh Date: Tue, 25 Nov 2025 20:41:29 +0300 Subject: [PATCH] Update: Version 2.0 --- main2.py | 2267 ++++++++++++++++++++++++++++++++++++++++++++++++++ masterpol.db | Bin 94208 -> 94208 bytes 2 files changed, 2267 insertions(+) create mode 100644 main2.py diff --git a/main2.py b/main2.py new file mode 100644 index 0000000..0d30b0e --- /dev/null +++ b/main2.py @@ -0,0 +1,2267 @@ +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 (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) + + +# === Диалог авторизации === +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.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.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("🔄 Обновить") + + 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 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()) diff --git a/masterpol.db b/masterpol.db index a696f890b346a336df3319e06ea0cc03dfa1498e..12f3f3db2f90c1f4082a9d2031f5759a1aab43e6 100644 GIT binary patch delta 1728 zcmZ`(ZBSHI7(O3++Yk1hgJ|X^yX-<_CR_L3WnHyqR)keaWeIVTL=a`im_V2XCUY9$ zV&;#iXtuGmnUbkyLMLM(1x(PWarl#SwvU;9;9%Mhsu_lr&@+sb@KMy`5c*UCBMYoFjxR&OYSqV~6C!d`(h6;T-JCev3S=M;r{fHto(2 z<%g_6ihbgPJ3sIE8PS{SN_C1cF_O5Jomcg~I44HMF#P)qys8okDXJDI46Y8XSu5TX z1EODydc}w0++5PYs?b_RQB_sb0)f5l4UP5t<8}4xV+ZS+S{h?d>}zgXp9R!lC92)I znGO&vd=&91ewEMi(ZNOD&-kKDnvd@WawM)k8F29zkezR#{2>2@|H!ZKVcyG+@oK&$ zsjIw*+i4zdj@QTHwGC~trdFH7MqOz`tA+o8GELCAkBn&!jsc_Qmt(P3X_=J<@w@tF zyae+^Z+PjhJf}q6xFz0P-@dP{mCF_gZ>npH#T)7xL8YL!wzsr2HpJqs4ks~U>*8$( zKr_xg;|`Pzq9jitp5TwN5w=yHO6uRrk~q!K&^v z`)Hm#ld#UN%4UP?BbH!C*df-ycCu2omaSwimMPzoXOj9Uxr66P2xBXy;qL?H;<=`Q z(D2{sjaYq#!A=PCf?&6j@Y&&Ke?RIOW;qhFLu&ZXbSCqdItl-J!dNF|H|Tb2xG$~p z?1f0nCA0&u?}B=G9?G=nuLUb2MI}|W8>1Dq+c%e%Z;O;w;u>6&_G^Ac6TQ&x7hyC8 zQm;enkBK3@m@YOeQbNs&y3`ax6jBQPO2DtE&lQzcmX>X*Ew6}_7j3UHYEsctQcb%H z=Yupwyd%a^da4Vm4~Pq*-)jyNOjb%4m%!Btu2Ewmz2F@K2A7PngP}YJLweb*bcmA> za|FzzKn$YB#HCc1zLJ=`T>;#P4|W^f6*hMZ)HJ`Ul$HUjXvvP^%F<{V{^2sq#8>*; zl{75~Q0FBr_&>~~r=`<) z`%jPyU1&E!928AJ;BR}-W->3TSyxlZ<~p>^t-bxO#?=KKT~c<$kM%T{DJ^6_8VJ=`AW{mU1w Z3u<9c#{KIX0dB{A>l^N&n>E3-IL&xB*np~p`jpxrUF}9EVrCW(+5h=e7vVn4dDx-B}jWi zLzAP_(BnboRuW;0TVtdcjV1j7(Y3boIp@nc-}5bZUFEK;GJ&OPa0N?NvIZh7B0}qk z*4cr0Xc?E(t_RN?PJ~dqYCN)0UJ&RYc!LfMY;XJ?%+Bn5fVTz664--xc!4LlgBqN` zH)Jhi4JMgDB0@pS$l6w}DCcz%lQ5R^s#4gOWu1(B$O67FR0{vH+2lPDXqB6Nf1Uz;Qy4)J&8lRtck#cDb}=% zE9TfCW-q4L)9bHuet{+s9P?3|#l%msB1-g@CM;u#+KJQaG?7@#^bBS!qr&XgPZYb+ z7(#Hv-~NJX+;g6Ex-ksUUl1SA7=*EpFJ*lMJ$T>=9Kkk3Ap(T;E#sLr`kOf9{RPiy BZS?>E