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