SQL-скрипт для создания и заполнения базы данных


  1 задание:
-- Создание базы данных
CREATE DATABASE IF NOT EXISTS master_pol_partners;
USE master_pol_partners;

-- Таблица типов партнеров
CREATE TABLE partner_types (
    id INT AUTO_INCREMENT PRIMARY KEY,
    type_name VARCHAR(50) NOT NULL UNIQUE
);

-- Таблица партнеров
CREATE TABLE partners (
    id INT AUTO_INCREMENT PRIMARY KEY,
    partner_name VARCHAR(100) NOT NULL UNIQUE,
    type_id INT NOT NULL,
    director_name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL,
    phone VARCHAR(20) NOT NULL,
    legal_address TEXT NOT NULL,
    inn VARCHAR(12) NOT NULL UNIQUE,
    rating INT NOT NULL CHECK (rating BETWEEN 1 AND 10),
    FOREIGN KEY (type_id) REFERENCES partner_types(id)
);

-- Таблица типов продукции
CREATE TABLE product_types (
    id INT AUTO_INCREMENT PRIMARY KEY,
    type_name VARCHAR(50) NOT NULL UNIQUE,
    type_coefficient DECIMAL(10,2) NOT NULL
);

-- Таблица продукции
CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    product_name VARCHAR(150) NOT NULL UNIQUE,
    article VARCHAR(20) NOT NULL UNIQUE,
    type_id INT NOT NULL,
    min_partner_price DECIMAL(15,2) NOT NULL,
    FOREIGN KEY (type_id) REFERENCES product_types(id)
);

-- Таблица истории продаж
CREATE TABLE sales_history (
    id INT AUTO_INCREMENT PRIMARY KEY,
    product_id INT NOT NULL,
    partner_id INT NOT NULL,
    quantity INT NOT NULL,
    sale_date DATETIME NOT NULL,
    FOREIGN KEY (product_id) REFERENCES products(id),
    FOREIGN KEY (partner_id) REFERENCES partners(id)
);

-- Таблица типов материалов
CREATE TABLE material_types (
    id INT AUTO_INCREMENT PRIMARY KEY,
    type_name VARCHAR(50) NOT NULL UNIQUE,
    defect_percentage DECIMAL(10,4) NOT NULL
);

-- Вставка данных из предоставленных файлов

-- Заполнение таблицы типов партнеров
INSERT INTO partner_types (type_name) VALUES 
('ЗАО'), ('ООО'), ('ПАО'), ('ОАО');

-- Заполнение таблицы партнеров
INSERT INTO partners (partner_name, type_id, director_name, email, phone, legal_address, inn, rating)
SELECT 
    p.`Наименование партнера`,
    pt.id,
    p.`Директор`,
    p.`Электронная почта партнера`,
    p.`Телефон партнера`,
    p.`Юридический адрес партнера`,
    p.`ИНН`,
    p.`Рейтинг`
FROM (
    SELECT 'ЗАО' as type_name, 'База Строитель' as `Наименование партнера`, 'Иванова Александра Ивановна' as `Директор`, 'aleksandraivanova@ml.ru' as `Электронная почта партнера`, '493 123 45 67' as `Телефон партнера`, '652050, Кемеровская область, город Юрга, ул. Лесная, 15' as `Юридический адрес партнера`, '2222455179' as `ИНН`, 7 as `Рейтинг`
    UNION ALL SELECT 'ООО', 'Паркет 29', 'Петров Василий Петрович', 'vppetrov@vl.ru', '987 123 56 78', '164500, Архангельская область, город Северодвинск, ул. Строителей, 18', '3333888520', 7
    UNION ALL SELECT 'ПАО', 'Стройсервис', 'Соловьев Андрей Николаевич', 'ansolovev@st.ru', '812 223 32 00', '188910, Ленинградская область, город Приморск, ул. Парковая, 21', '4440391035', 7
    UNION ALL SELECT 'ОАО', 'Ремонт и отделка', 'Воробьева Екатерина Валерьевна', 'ekaterina.vorobeva@ml.ru', '444 222 33 11', '143960, Московская область, город Реутов, ул. Свободы, 51', '1111520857', 5
    UNION ALL SELECT 'ЗАО', 'МонтажПро', 'Степанов Степан Сергеевич', 'stepanov@stepan.ru', '912 888 33 33', '309500, Белгородская область, город Старый Оскол, ул. Рабочая, 122', '5552431140', 10
) p
JOIN partner_types pt ON p.type_name = pt.type_name;

-- Заполнение таблицы типов продукции
INSERT INTO product_types (type_name, type_coefficient) VALUES 
('Ламинат', 2.35),
('Массивная доска', 5.15),
('Паркетная доска', 4.34),
('Пробковое покрытие', 1.5);

-- Заполнение таблицы продукции
INSERT INTO products (product_name, article, type_id, min_partner_price)
SELECT 
    p.`Наименование продукции`,
    p.`Артикул`,
    pt.id,
    p.`Минимальная стоимость для партнера`
FROM (
    SELECT 'Паркетная доска Ясень темный однополосная 14 мм' as `Наименование продукции`, '8758385' as `Артикул`, 'Паркетная доска' as `Тип продукции`, 4456.9 as `Минимальная стоимость для партнера`
    UNION ALL SELECT 'Инженерная доска Дуб Французская елка однополосная 12 мм', '8858958', 'Паркетная доска', 7330.99
    UNION ALL SELECT 'Ламинат Дуб дымчато-белый 33 класс 12 мм', '7750282', 'Ламинат', 1799.33
    UNION ALL SELECT 'Ламинат Дуб серый 32 класс 8 мм с фаской', '7028748', 'Ламинат', 3890.41
    UNION ALL SELECT 'Пробковое напольное клеевое покрытие 32 класс 4 мм', '5012543', 'Пробковое покрытие', 5450.59
) p
JOIN product_types pt ON p.`Тип продукции` = pt.type_name;

-- Заполнение таблицы истории продаж
INSERT INTO sales_history (product_id, partner_id, quantity, sale_date)
SELECT 
    pr.id,
    pa.id,
    s.`Количество продукции`,
    STR_TO_DATE(s.`Дата продажи`, '%Y-%m-%d %H:%i:%s')
FROM (
    SELECT 'Паркетная доска Ясень темный однополосная 14 мм' as `Продукция`, 'База Строитель' as `Наименование партнера`, 15500 as `Количество продукции`, '2023-03-23 00:00:00' as `Дата продажи`
    UNION ALL SELECT 'Ламинат Дуб дымчато-белый 33 класс 12 мм', 'База Строитель', 12350, '2023-12-18 00:00:00'
    UNION ALL SELECT 'Ламинат Дуб серый 32 класс 8 мм с фаской', 'База Строитель', 37400, '2024-06-07 00:00:00'
    UNION ALL SELECT 'Инженерная доска Дуб Французская елка однополосная 12 мм', 'Паркет 29', 35000, '2022-12-02 00:00:00'
    UNION ALL SELECT 'Пробковое напольное клеевое покрытие 32 класс 4 мм', 'Паркет 29', 1250, '2023-05-17 00:00:00'
    UNION ALL SELECT 'Ламинат Дуб дымчато-белый 33 класс 12 мм', 'Паркет 29', 1000, '2024-06-07 00:00:00'
    UNION ALL SELECT 'Паркетная доска Ясень темный однополосная 14 мм', 'Паркет 29', 7550, '2024-07-01 00:00:00'
    UNION ALL SELECT 'Паркетная доска Ясень темный однополосная 14 мм', 'Стройсервис', 7250, '2023-01-22 00:00:00'
    UNION ALL SELECT 'Инженерная доска Дуб Французская елка однополосная 12 мм', 'Стройсервис', 2500, '2024-07-05 00:00:00'
    UNION ALL SELECT 'Ламинат Дуб серый 32 класс 8 мм с фаской', 'Ремонт и отделка', 59050, '2023-03-20 00:00:00'
    UNION ALL SELECT 'Ламинат Дуб дымчато-белый 33 класс 12 мм', 'Ремонт и отделка', 37200, '2024-03-12 00:00:00'
    UNION ALL SELECT 'Пробковое напольное клеевое покрытие 32 класс 4 мм', 'Ремонт и отделка', 4500, '2024-05-14 00:00:00'
    UNION ALL SELECT 'Ламинат Дуб дымчато-белый 33 класс 12 мм', 'МонтажПро', 50000, '2023-09-19 00:00:00'
    UNION ALL SELECT 'Ламинат Дуб серый 32 класс 8 мм с фаской', 'МонтажПро', 670000, '2023-11-10 00:00:00'
    UNION ALL SELECT 'Паркетная доска Ясень темный однополосная 14 мм', 'МонтажПро', 35000, '2024-04-15 00:00:00'
    UNION ALL SELECT 'Инженерная доска Дуб Французская елка однополосная 12 мм', 'МонтажПро', 25000, '2024-06-12 00:00:00'
) s
JOIN products pr ON s.`Продукция` = pr.product_name
JOIN partners pa ON s.`Наименование партнера` = pa.partner_name;

-- Заполнение таблицы типов материалов
INSERT INTO material_types (type_name, defect_percentage) VALUES 
('Тип материала 1', 0.001),
('Тип материала 2', 0.0095),
('Тип материала 3', 0.0028),
('Тип материала 4', 0.0055),
('Тип материала 5', 0.0034);

ER-диаграмма будет содержать следующие таблицы и связи:
1.	partner_types - типы партнеров (ЗАО, ООО и т.д.)
o	id (PK)
o	type_name
2.	partners - информация о партнерах
o	id (PK)
o	partner_name
o	type_id (FK → partner_types)
o	director_name
o	email
o	phone
o	legal_address
o	inn
o	rating
3.	product_types - типы продукции (ламинат, паркетная доска и т.д.)
o	id (PK)
o	type_name
o	type_coefficient
4.	products - информация о продукции
o	id (PK)
o	product_name
o	article
o	type_id (FK → product_types)
o	min_partner_price
5.	sales_history - история продаж продукции партнерам
o	id (PK)
o	product_id (FK → products)
o	partner_id (FK → partners)
o	quantity
o	sale_date
6.	material_types - типы материалов (для производства)
o	id (PK)
o	type_name
o	defect_percentage
Связи:
•	Один ко многим между partner_types и partners
•	Один ко многим между product_types и products
•	Один ко многим между partners и sales_history
•	Один ко многим между products и sales_history
Подготовка данных для импорта
Данные из предоставленных файлов уже подготовлены и включены в SQL-скрипт выше в виде INSERT-запросов. Все данные нормализованы и соответствуют третьей нормальной форме:
1.	Устранены повторяющиеся группы (например, типы партнеров вынесены в отдельную таблицу)
2.	Все неключевые атрибуты зависят от первичного ключа
3.	Все неключевые атрибуты не зависят друг от друга
Дополнительные примечания
1.	Для генерации ER-диаграммы в MySQL Workbench можно использовать функцию "Database" → "Reverse Engineer" после выполнения скрипта.
2.	Все таблицы имеют первичные ключи и соответствующие внешние ключи для обеспечения ссылочной целостности.
3.	Для поля rating в таблице partners добавлено ограничение CHECK для контроля допустимых значений.
4.	Даты продаж преобразованы из строкового формата в DATETIME с помощью функции STR_TO_DATE.
5.	Все уникальные идентификаторы (ИНН, артикулы) помечены как UNIQUE.

2 задание:
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                            QLabel, QScrollArea, QFrame, QPushButton, QMessageBox, QGridLayout)
from PyQt5.QtGui import QFont, QPixmap, QIcon, QPalette, QColor
from PyQt5.QtCore import Qt
import sqlite3

class PartnerCard(QFrame):
    def __init__(self, partner_data, parent=None):
        super().__init__(parent)
        self.partner_data = partner_data
        self.setup_ui()
        
    def setup_ui(self):
        self.setFrameShape(QFrame.StyledPanel)
        self.setLineWidth(1)
        self.setFixedSize(300, 180)
        
        layout = QVBoxLayout()
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(10)
        
        # Верхняя строка с названием и скидкой
        top_layout = QHBoxLayout()
        
        name_label = QLabel(self.partner_data['name'])
        name_label.setStyleSheet("font-weight: bold; font-size: 14px;")
        top_layout.addWidget(name_label)
        
        discount_label = QLabel(f"{self.partner_data['discount']}%")
        discount_label.setStyleSheet("color: #67BA80; font-weight: bold; font-size: 14px;")
        top_layout.addStretch()
        top_layout.addWidget(discount_label)
        
        layout.addLayout(top_layout)
        
        # Информация о партнере
        director_label = QLabel(self.partner_data['director'])
        director_label.setStyleSheet("font-size: 12px; color: #555555;")
        layout.addWidget(director_label)
        
        phone_label = QLabel(self.partner_data['phone'])
        phone_label.setStyleSheet("font-size: 12px; color: #555555;")
        layout.addWidget(phone_label)
        
        rating_label = QLabel(f"Рейтинг: {self.partner_data['rating']}")
        rating_label.setStyleSheet("font-size: 12px; color: #555555;")
        layout.addWidget(rating_label)
        
        # Кнопка для просмотра деталей
        details_btn = QPushButton("Подробнее")
        details_btn.setStyleSheet("""
            QPushButton {
                background-color: #67BA80;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 3px;
                font-size: 12px;
            }
            QPushButton:hover {
                background-color: #5AA770;
            }
        """)
        details_btn.setFixedWidth(100)
        layout.addWidget(details_btn, alignment=Qt.AlignRight)
        
        layout.addStretch()
        self.setLayout(layout)

class PartnerSystem(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Система управления партнерами - Мастер пол")
        self.setGeometry(100, 100, 1000, 700)
        
        # Установка стилей согласно Приложению 2
        self.setStyleSheet("""
            QMainWindow {
                background-color: #F4E8D3;
                font-family: 'Segoe UI';
            }
            QFrame {
                background-color: #FFFFFF;
                border-radius: 5px;
            }
        """)
        
        # Создание центрального виджета
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # Основной layout
        main_layout = QVBoxLayout(central_widget)
        main_layout.setContentsMargins(20, 20, 20, 20)
        main_layout.setSpacing(20)
        
        # Заголовок и логотип
        title_layout = QHBoxLayout()
        self.logo_label = QLabel()
        # В реальном приложении здесь должен быть путь к логотипу из ресурсов
        # self.logo_label.setPixmap(QPixmap("logo.png").scaled(100, 100, Qt.KeepAspectRatio))
        title_layout.addWidget(self.logo_label)
        
        title_label = QLabel("Список партнеров компании")
        title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #333333;")
        title_layout.addWidget(title_label)
        title_layout.addStretch()
        
        main_layout.addLayout(title_layout)
        
        # Область с карточками партнеров
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setFrameShape(QFrame.NoFrame)
        
        cards_container = QWidget()
        self.cards_layout = QGridLayout(cards_container)
        self.cards_layout.setContentsMargins(10, 10, 10, 10)
        self.cards_layout.setSpacing(20)
        self.cards_layout.setAlignment(Qt.AlignTop)
        
        scroll_area.setWidget(cards_container)
        main_layout.addWidget(scroll_area)
        
        # Кнопки управления
        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch()
        
        self.refresh_btn = QPushButton("Обновить")
        self.refresh_btn.clicked.connect(self.load_partners)
        buttons_layout.addWidget(self.refresh_btn)
        
        self.add_btn = QPushButton("Добавить партнера")
        self.add_btn.clicked.connect(self.show_add_partner_dialog)
        buttons_layout.addWidget(self.add_btn)
        
        main_layout.addLayout(buttons_layout)
        
        # Подключение к базе данных
        self.db_connection = sqlite3.connect('master_pol_partners.db')
        
        # Загрузка данных
        self.load_partners()
    
    def load_partners(self):
        """Загрузка списка партнеров из базы данных"""
        try:
            # Очищаем текущие карточки
            for i in reversed(range(self.cards_layout.count())): 
                self.cards_layout.itemAt(i).widget().setParent(None)
            
            cursor = self.db_connection.cursor()
            
            # Получаем список партнеров с информацией об объеме продаж
            query = """
                SELECT p.id, p.partner_name, p.director_name, p.phone, p.rating,
                       COALESCE(SUM(sh.quantity), 0) as total_sales
                FROM partners p
                LEFT JOIN sales_history sh ON p.id = sh.partner_id
                GROUP BY p.id, p.partner_name, p.director_name, p.phone, p.rating
                ORDER BY p.partner_name
            """
            cursor.execute(query)
            partners = cursor.fetchall()
            
            # Создаем карточки для каждого партнера
            row, col = 0, 0
            max_cols = 3
            
            for partner in partners:
                partner_id, name, director, phone, rating, total_sales = partner
                
                # Рассчитываем скидку
                discount = self.calculate_discount(total_sales)
                
                # Создаем карточку
                partner_data = {
                    'id': partner_id,
                    'name': name,
                    'director': director,
                    'phone': phone,
                    'rating': rating,
                    'discount': discount
                }
                
                card = PartnerCard(partner_data)
                self.cards_layout.addWidget(card, row, col)
                
                col += 1
                if col >= max_cols:
                    col = 0
                    row += 1
            
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить данные: {str(e)}")
    
    def calculate_discount(self, total_sales):
        """Расчет скидки для партнера на основе общего объема продаж"""
        if total_sales < 10000:
            return 0
        elif 10000 <= total_sales < 50000:
            return 5
        elif 50000 <= total_sales < 300000:
            return 10
        else:
            return 15
    
    def show_add_partner_dialog(self):
        """Показ диалога добавления нового партнера"""
        QMessageBox.information(self, "Добавление партнера", "Функция добавления партнера будет реализована здесь")
    
    def closeEvent(self, event):
        """Обработчик закрытия окна"""
        self.db_connection.close()
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    # Установка иконки приложения (если есть в ресурсах)
    # app.setWindowIcon(QIcon("app_icon.ico"))
    
    window = PartnerSystem()
    window.show()
    sys.exit(app.exec_())
    
  3 задание:
  import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                            QLabel, QScrollArea, QFrame, QPushButton, QMessageBox, QGridLayout,
                            QLineEdit, QComboBox, QFormLayout, QSpinBox)
from PyQt5.QtGui import QFont, QPixmap, QIcon, QPalette, QColor
from PyQt5.QtCore import Qt
import sqlite3

class PartnerCard(QFrame):
    def __init__(self, partner_data, parent=None):
        super().__init__(parent)
        self.partner_data = partner_data
        self.parent = parent
        self.setup_ui()
        
    def setup_ui(self):
        self.setFrameShape(QFrame.StyledPanel)
        self.setLineWidth(1)
        self.setFixedSize(300, 180)
        
        layout = QVBoxLayout()
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(10)
        
        # Верхняя строка с названием и скидкой
        top_layout = QHBoxLayout()
        
        name_label = QLabel(self.partner_data['name'])
        name_label.setStyleSheet("font-weight: bold; font-size: 14px;")
        top_layout.addWidget(name_label)
        
        discount_label = QLabel(f"{self.partner_data['discount']}%")
        discount_label.setStyleSheet("color: #67BA80; font-weight: bold; font-size: 14px;")
        top_layout.addStretch()
        top_layout.addWidget(discount_label)
        
        layout.addLayout(top_layout)
        
        # Информация о партнере
        director_label = QLabel(self.partner_data['director'])
        director_label.setStyleSheet("font-size: 12px; color: #555555;")
        layout.addWidget(director_label)
        
        phone_label = QLabel(self.partner_data['phone'])
        phone_label.setStyleSheet("font-size: 12px; color: #555555;")
        layout.addWidget(phone_label)
        
        rating_label = QLabel(f"Рейтинг: {self.partner_data['rating']}")
        rating_label.setStyleSheet("font-size: 12px; color: #555555;")
        layout.addWidget(rating_label)
        
        # Кнопка для просмотра/редактирования
        edit_btn = QPushButton("Редактировать")
        edit_btn.setStyleSheet("""
            QPushButton {
                background-color: #67BA80;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 3px;
                font-size: 12px;
            }
            QPushButton:hover {
                background-color: #5AA770;
            }
        """)
        edit_btn.setFixedWidth(100)
        edit_btn.clicked.connect(self.edit_partner)
        layout.addWidget(edit_btn, alignment=Qt.AlignRight)
        
        layout.addStretch()
        self.setLayout(layout)
    
    def edit_partner(self):
        """Открытие формы редактирования партнера"""
        self.parent.open_edit_partner_form(self.partner_data['id'])

class PartnerForm(QWidget):
    def __init__(self, parent=None, partner_id=None):
        super().__init__(parent)
        self.parent = parent
        self.partner_id = partner_id
        self.setup_ui()
        
        if partner_id:
            self.load_partner_data()
        
    def setup_ui(self):
        self.setWindowTitle("Добавление/редактирование партнера" if not self.partner_id 
                          else f"Редактирование партнера ID: {self.partner_id}")
        self.setFixedSize(500, 400)
        
        layout = QVBoxLayout()
        layout.setContentsMargins(20, 20, 20, 20)
        layout.setSpacing(20)
        
        # Форма с полями ввода
        form_layout = QFormLayout()
        form_layout.setSpacing(15)
        
        # Наименование партнера
        self.name_edit = QLineEdit()
        self.name_edit.setPlaceholderText("Введите наименование партнера")
        form_layout.addRow("Наименование:", self.name_edit)
        
        # Тип партнера
        self.type_combo = QComboBox()
        self.type_combo.addItems(["ЗАО", "ООО", "ПАО", "ОАО"])
        form_layout.addRow("Тип партнера:", self.type_combo)
        
        # Рейтинг
        self.rating_spin = QSpinBox()
        self.rating_spin.setRange(1, 10)
        form_layout.addRow("Рейтинг (1-10):", self.rating_spin)
        
        # Юридический адрес
        self.address_edit = QLineEdit()
        self.address_edit.setPlaceholderText("Введите юридический адрес")
        form_layout.addRow("Юридический адрес:", self.address_edit)
        
        # ФИО директора
        self.director_edit = QLineEdit()
        self.director_edit.setPlaceholderText("Введите ФИО директора")
        form_layout.addRow("ФИО директора:", self.director_edit)
        
        # Телефон
        self.phone_edit = QLineEdit()
        self.phone_edit.setPlaceholderText("+7 XXX XXX XX XX")
        form_layout.addRow("Телефон:", self.phone_edit)
        
        # Email
        self.email_edit = QLineEdit()
        self.email_edit.setPlaceholderText("example@domain.com")
        form_layout.addRow("Email:", self.email_edit)
        
        layout.addLayout(form_layout)
        
        # Кнопки управления
        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch()
        
        cancel_btn = QPushButton("Отмена")
        cancel_btn.clicked.connect(self.close)
        buttons_layout.addWidget(cancel_btn)
        
        save_btn = QPushButton("Сохранить")
        save_btn.setStyleSheet("background-color: #67BA80; color: white;")
        save_btn.clicked.connect(self.save_partner)
        buttons_layout.addWidget(save_btn)
        
        layout.addLayout(buttons_layout)
        
        self.setLayout(layout)
    
    def load_partner_data(self):
        """Загрузка данных партнера для редактирования"""
        try:
            cursor = self.parent.db_connection.cursor()
            cursor.execute("""
                SELECT p.partner_name, pt.type_name, p.rating, p.legal_address, 
                       p.director_name, p.phone, p.email
                FROM partners p
                JOIN partner_types pt ON p.type_id = pt.id
                WHERE p.id = ?
            """, (self.partner_id,))
            
            partner = cursor.fetchone()
            if partner:
                name, p_type, rating, address, director, phone, email = partner
                self.name_edit.setText(name)
                self.type_combo.setCurrentText(p_type)
                self.rating_spin.setValue(rating)
                self.address_edit.setText(address)
                self.director_edit.setText(director)
                self.phone_edit.setText(phone)
                self.email_edit.setText(email)
        
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", 
                               f"Не удалось загрузить данные партнера:\n{str(e)}")
    
    def save_partner(self):
        """Сохранение данных партнера"""
        # Валидация данных
        if not self.name_edit.text().strip():
            QMessageBox.warning(self, "Ошибка ввода", 
                              "Поле 'Наименование' обязательно для заполнения!")
            self.name_edit.setFocus()
            return
        
        if not self.director_edit.text().strip():
            QMessageBox.warning(self, "Ошибка ввода",
                              "Поле 'ФИО директора' обязательно для заполнения!")
            self.director_edit.setFocus()
            return
        
        try:
            cursor = self.parent.db_connection.cursor()
            
            # Получаем ID типа партнера
            cursor.execute("SELECT id FROM partner_types WHERE type_name = ?", 
                         (self.type_combo.currentText(),))
            type_id = cursor.fetchone()[0]
            
            partner_data = (
                self.name_edit.text().strip(),
                type_id,
                self.director_edit.text().strip(),
                self.email_edit.text().strip(),
                self.phone_edit.text().strip(),
                self.address_edit.text().strip(),
                self.rating_spin.value(),
            )
            
            if self.partner_id:
                # Обновление существующего партнера
                cursor.execute("""
                    UPDATE partners 
                    SET partner_name=?, type_id=?, director_name=?, email=?, 
                        phone=?, legal_address=?, rating=?
                    WHERE id=?
                """, partner_data + (self.partner_id,))
                message = "Данные партнера успешно обновлены!"
            else:
                # Добавление нового партнера
                cursor.execute("""
                    INSERT INTO partners 
                    (partner_name, type_id, director_name, email, phone, legal_address, rating)
                    VALUES (?, ?, ?, ?, ?, ?, ?)
                """, partner_data)
                message = "Новый партнер успешно добавлен!"
            
            self.parent.db_connection.commit()
            QMessageBox.information(self, "Успешно", message)
            self.parent.load_partners()
            self.close()
            
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", 
                               f"Не удалось сохранить данные партнера:\n{str(e)}")

class PartnerSystem(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Система управления партнерами - Мастер пол")
        self.setGeometry(100, 100, 1000, 700)
        
        # Установка стилей согласно Приложению 2
        self.setStyleSheet("""
            QMainWindow {
                background-color: #F4E8D3;
                font-family: 'Segoe UI';
            }
            QFrame {
                background-color: #FFFFFF;
                border-radius: 5px;
            }
            QPushButton {
                min-width: 100px;
                padding: 5px;
            }
        """)
        
        # Создание центрального виджета
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # Основной layout
        main_layout = QVBoxLayout(central_widget)
        main_layout.setContentsMargins(20, 20, 20, 20)
        main_layout.setSpacing(20)
        
        # Заголовок и логотип
        title_layout = QHBoxLayout()
        self.logo_label = QLabel()
        # В реальном приложении здесь должен быть путь к логотипу из ресурсов
        # self.logo_label.setPixmap(QPixmap("logo.png").scaled(100, 100, Qt.KeepAspectRatio))
        title_layout.addWidget(self.logo_label)
        
        title_label = QLabel("Список партнеров компании")
        title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #333333;")
        title_layout.addWidget(title_label)
        title_layout.addStretch()
        
        main_layout.addLayout(title_layout)
        
        # Область с карточками партнеров
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setFrameShape(QFrame.NoFrame)
        
        self.cards_container = QWidget()
        self.cards_layout = QGridLayout(self.cards_container)
        self.cards_layout.setContentsMargins(10, 10, 10, 10)
        self.cards_layout.setSpacing(20)
        self.cards_layout.setAlignment(Qt.AlignTop)
        
        scroll_area.setWidget(self.cards_container)
        main_layout.addWidget(scroll_area)
        
        # Кнопки управления
        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch()
        
        self.refresh_btn = QPushButton("Обновить")
        self.refresh_btn.clicked.connect(self.load_partners)
        buttons_layout.addWidget(self.refresh_btn)
        
        self.add_btn = QPushButton("Добавить партнера")
        self.add_btn.clicked.connect(self.open_add_partner_form)
        buttons_layout.addWidget(self.add_btn)
        
        main_layout.addLayout(buttons_layout)
        
        # Подключение к базе данных
        self.db_connection = sqlite3.connect('master_pol_partners.db')
        
        # Загрузка данных
        self.load_partners()
    
    def load_partners(self):
        """Загрузка списка партнеров из базы данных"""
        try:
            # Очищаем текущие карточки
            for i in reversed(range(self.cards_layout.count())): 
                widget = self.cards_layout.itemAt(i).widget()
                if widget is not None:
                    widget.setParent(None)
            
            cursor = self.db_connection.cursor()
            
            # Получаем список партнеров с информацией об объеме продаж
            query = """
                SELECT p.id, p.partner_name, p.director_name, p.phone, p.rating,
                       COALESCE(SUM(sh.quantity), 0) as total_sales
                FROM partners p
                LEFT JOIN sales_history sh ON p.id = sh.partner_id
                GROUP BY p.id, p.partner_name, p.director_name, p.phone, p.rating
                ORDER BY p.partner_name
            """
            cursor.execute(query)
            partners = cursor.fetchall()
            
            # Создаем карточки для каждого партнера
            row, col = 0, 0
            max_cols = 3
            
            for partner in partners:
                partner_id, name, director, phone, rating, total_sales = partner
                
                # Рассчитываем скидку
                discount = self.calculate_discount(total_sales)
                
                # Создаем карточку
                partner_data = {
                    'id': partner_id,
                    'name': name,
                    'director': director,
                    'phone': phone,
                    'rating': rating,
                    'discount': discount
                }
                
                card = PartnerCard(partner_data, self)
                self.cards_layout.addWidget(card, row, col)
                
                col += 1
                if col >= max_cols:
                    col = 0
                    row += 1
            
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", 
                               f"Не удалось загрузить данные партнеров:\n{str(e)}")
    
    def calculate_discount(self, total_sales):
        """Расчет скидки для партнера на основе общего объема продаж"""
        if total_sales < 10000:
            return 0
        elif 10000 <= total_sales < 50000:
            return 5
        elif 50000 <= total_sales < 300000:
            return 10
        else:
            return 15
    
    def open_add_partner_form(self):
        """Открытие формы добавления нового партнера"""
        form = PartnerForm(self)
        form.show()
    
    def open_edit_partner_form(self, partner_id):
        """Открытие формы редактирования партнера"""
        form = PartnerForm(self, partner_id)
        form.show()
    
    def closeEvent(self, event):
        """Обработчик закрытия окна"""
        reply = QMessageBox.question(
            self, 'Подтверждение', 
            'Вы уверены, что хотите закрыть приложение?',
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No
        )
        
        if reply == QMessageBox.Yes:
            self.db_connection.close()
            event.accept()
        else:
            event.ignore()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    # Установка иконки приложения (если есть в ресурсах)
    # app.setWindowIcon(QIcon("app_icon.ico"))
    
    window = PartnerSystem()
    window.show()
    sys.exit(app.exec_())
    
    
    
  4 задание:
  1.1. Главный модуль приложения (main.py)
python
Copy
Download
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                            QLabel, QScrollArea, QFrame, QPushButton, QMessageBox, QGridLayout,
                            QLineEdit, QComboBox, QFormLayout, QSpinBox, QTableWidget, 
                            QTableWidgetItem, QHeaderView)
from PyQt5.QtGui import QFont, QPixmap, QIcon
from PyQt5.QtCore import Qt
import sqlite3
from datetime import datetime
from material_calculator import calculate_material_required

class PartnerCard(QFrame):
    def __init__(self, partner_data, parent=None):
        super().__init__(parent)
        self.partner_data = partner_data
        self.parent = parent
        self.setup_ui()
        
    def setup_ui(self):
        self.setFrameShape(QFrame.StyledPanel)
        self.setLineWidth(1)
        self.setFixedSize(300, 200)
        
        layout = QVBoxLayout()
        layout.setContentsMargins(15, 15, 15, 15)
        layout.setSpacing(10)
        
        # Верхняя строка с названием и скидкой
        top_layout = QHBoxLayout()
        
        name_label = QLabel(self.partner_data['name'])
        name_label.setStyleSheet("font-weight: bold; font-size: 14px;")
        top_layout.addWidget(name_label)
        
        discount_label = QLabel(f"{self.partner_data['discount']}%")
        discount_label.setStyleSheet("color: #67BA80; font-weight: bold; font-size: 14px;")
        top_layout.addStretch()
        top_layout.addWidget(discount_label)
        
        layout.addLayout(top_layout)
        
        # Информация о партнере
        type_label = QLabel(f"Тип: {self.partner_data['type']}")
        type_label.setStyleSheet("font-size: 12px; color: #555555;")
        layout.addWidget(type_label)
        
        director_label = QLabel(self.partner_data['director'])
        director_label.setStyleSheet("font-size: 12px; color: #555555;")
        layout.addWidget(director_label)
        
        phone_label = QLabel(self.partner_data['phone'])
        phone_label.setStyleSheet("font-size: 12px; color: #555555;")
        layout.addWidget(phone_label)
        
        rating_label = QLabel(f"Рейтинг: {self.partner_data['rating']}")
        rating_label.setStyleSheet("font-size: 12px; color: #555555;")
        layout.addWidget(rating_label)
        
        # Кнопки управления
        btn_layout = QHBoxLayout()
        
        sales_btn = QPushButton("Продажи")
        sales_btn.setStyleSheet("""
            QPushButton {
                background-color: #67BA80;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 3px;
                font-size: 12px;
            }
            QPushButton:hover {
                background-color: #5AA770;
            }
        """)
        sales_btn.setFixedWidth(80)
        sales_btn.clicked.connect(self.show_sales_history)
        btn_layout.addWidget(sales_btn)
        
        edit_btn = QPushButton("Ред.")
        edit_btn.setStyleSheet("""
            QPushButton {
                background-color: #F4E8D3;
                color: #333333;
                border: 1px solid #999999;
                padding: 5px;
                border-radius: 3px;
                font-size: 12px;
            }
            QPushButton:hover {
                background-color: #E5D9C3;
            }
        """)
        edit_btn.setFixedWidth(50)
        edit_btn.clicked.connect(self.edit_partner)
        btn_layout.addWidget(edit_btn)
        
        layout.addLayout(btn_layout)
        self.setLayout(layout)
    
    def edit_partner(self):
        self.parent.open_edit_partner_form(self.partner_data['id'])
    
    def show_sales_history(self):
        self.parent.open_sales_history(self.partner_data['id'])

class SalesHistoryWindow(QWidget):
    def __init__(self, partner_id, parent=None):
        super().__init__(parent)
        self.partner_id = partner_id
        self.parent = parent
        self.setup_ui()
        self.load_sales_history()
        
    def setup_ui(self):
        self.setWindowTitle(f"История продаж партнера ID: {self.partner_id}")
        self.setFixedSize(800, 600)
        
        layout = QVBoxLayout()
        layout.setContentsMargins(20, 20, 20, 20)
        layout.setSpacing(20)
        
        # Заголовок
        title_layout = QHBoxLayout()
        
        back_btn = QPushButton("Назад")
        back_btn.setStyleSheet("background-color: #F4E8D3;")
        back_btn.clicked.connect(self.close)
        title_layout.addWidget(back_btn)
        
        title_label = QLabel("История продаж")
        title_label.setStyleSheet("font-size: 16px; font-weight: bold;")
        title_layout.addWidget(title_label, alignment=Qt.AlignCenter)
        title_layout.addStretch()
        
        layout.addLayout(title_layout)
        
        # Таблица истории продаж
        self.sales_table = QTableWidget()
        self.sales_table.setColumnCount(4)
        self.sales_table.setHorizontalHeaderLabels(["Продукция", "Тип продукции", "Количество", "Дата продажи"])
        self.sales_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.sales_table.verticalHeader().setVisible(False)
        self.sales_table.setEditTriggers(QTableWidget.NoEditTriggers)
        
        layout.addWidget(self.sales_table)
        
        # Расчет материалов
        materials_layout = QHBoxLayout()
        
        calc_btn = QPushButton("Рассчитать материалы")
        calc_btn.setStyleSheet("background-color: #67BA80; color: white;")
        calc_btn.clicked.connect(self.calculate_materials)
        materials_layout.addWidget(calc_btn)
        
        self.materials_label = QLabel("")
        materials_layout.addWidget(self.materials_label)
        
        layout.addLayout(materials_layout)
        self.setLayout(layout)
    
    def load_sales_history(self):
        try:
            cursor = self.parent.db_connection.cursor()
            
            # Получаем историю продаж для партнера
            query = """
                SELECT p.product_name, pt.type_name, sh.quantity, sh.sale_date
                FROM sales_history sh
                JOIN products p ON sh.product_id = p.id
                JOIN product_types pt ON p.type_id = pt.id
                WHERE sh.partner_id = ?
                ORDER BY sh.sale_date DESC
            """
            cursor.execute(query, (self.partner_id,))
            sales = cursor.fetchall()
            
            self.sales_table.setRowCount(len(sales))
            
            for row, (product, p_type, quantity, date) in enumerate(sales):
                self.sales_table.setItem(row, 0, QTableWidgetItem(product))
                self.sales_table.setItem(row, 1, QTableWidgetItem(p_type))
                self.sales_table.setItem(row, 2, QTableWidgetItem(str(quantity)))
                self.sales_table.setItem(row, 3, QTableWidgetItem(date))
                
        except Exception as e:
            QMessageBox.critical(self, "Ошибка", 
                               f"Не удалось загрузить историю продаж:\n{str(e)}")
    
    def calculate_materials(self):
        try:
            cursor = self.parent.db_connection.cursor()
            
            # Получаем все продажи для расчета материалов
            query = """
                SELECT p.type_id, sh.quantity, p.length, p.width
                FROM sales_history sh
                JOIN products p ON sh.product_id = p.id
                WHERE sh.partner_id = ?
            """
            cursor.execute(query, (self.partner_id,))
            sales = cursor.fetchall()
            
            total_materials = {}
            
            for type_id, quantity, length, width in sales:
                # Для простоты примера используем тип материала 1
                material_type = 1
                material_needed = calculate_material_required(
                    product_type_id=type_id,
                    material_type_id=material_type,
                    product_count=quantity,
                    param1=length,
                    param2=width
                )
                
                if material_needed == -1:
                    raise ValueError("Некорректные параметры для расчета материалов")
                
                if material_type not in total_materials:
                    total_materials[material_type] = 0
                total_materials[material_type] += material_needed
            
            # Формируем текст результата
            result_text = "Всего материалов требуется: "
            for mat_type, amount in total_materials.items():
                result_text += f"\nТип {mat_type}: {amount} ед."
            
            self.materials_label.setText(result_text)
            
        except Exception as e:
            QMessageBox.critical(self, "Ошибка расчета", 
                               f"Ошибка при расчете материалов:\n{str(e)}")

# Остальной код (PartnerForm, PartnerSystem) остается таким же, как в предыдущем примере
# Добавляются только новые методы для открытия истории продаж

class PartnerSystem(QMainWindow):
    # ... (предыдущий код остается без изменений)
    
    def open_sales_history(self, partner_id):
        """Открытие окна истории продаж для партнера"""
        history_window = SalesHistoryWindow(partner_id, self)
        history_window.show()
1.2. Модуль расчета материалов (material_calculator.py)
python
Copy
Download
def calculate_material_required(product_type_id, material_type_id, product_count, param1, param2):
    """
    Расчет количества материала, необходимого для производства продукции
    
    Параметры:
    - product_type_id: ID типа продукции (целое число)
    - material_type_id: ID типа материала (целое число)
    - product_count: количество продукции (целое число)
    - param1: первый параметр продукции (вещественное число)
    - param2: второй параметр продукции (вещественное число)
    
    Возвращает:
    - Количество материала с учетом брака или -1 при ошибке
    """
    try:
        # Проверка входных параметров
        if (not isinstance(product_type_id, int) or (not isinstance(material_type_id, int)) or \
           (not isinstance(product_count, int)) or (product_count <= 0) or \
           (not isinstance(param1, (int, float))) or (param1 <= 0) or \
           (not isinstance(param2, (int, float))) or (param2 <= 0):
            return -1
        
        # Подключение к базе данных для получения коэффициентов
        conn = sqlite3.connect('master_pol_partners.db')
        cursor = conn.cursor()
        
        # Получаем коэффициент типа продукции
        cursor.execute("SELECT type_coefficient FROM product_types WHERE id = ?", (product_type_id,))
        product_coeff = cursor.fetchone()
        if not product_coeff:
            return -1
        product_coeff = product_coeff[0]
        
        # Получаем процент брака материала
        cursor.execute("SELECT defect_percentage FROM material_types WHERE id = ?", (material_type_id,))
        material_defect = cursor.fetchone()
        if not material_defect:
            return -1
        material_defect = material_defect[0]
        
        conn.close()
        
        # Расчет материала на одну единицу продукции
        material_per_unit = param1 * param2 * product_coeff
        
        # Расчет общего количества материала с учетом брака
        total_material = (material_per_unit * product_count) / (1 - material_defect)
        
        # Округляем в большую сторону до целого числа
        return int(total_material) + (total_material % 1 > 0)
    
    except:
        return -1
1.3. SQL-скрипт базы данных (database.sql)
sql
Copy
Download
-- Создание таблицы типов партнеров
CREATE TABLE IF NOT EXISTS partner_types (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    type_name TEXT NOT NULL UNIQUE
);

-- Создание таблицы партнеров
CREATE TABLE IF NOT EXISTS partners (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    partner_name TEXT NOT NULL UNIQUE,
    type_id INTEGER NOT NULL,
    director_name TEXT NOT NULL,
    email TEXT NOT NULL,
    phone TEXT NOT NULL,
    legal_address TEXT NOT NULL,
    inn TEXT NOT NULL UNIQUE,
    rating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 10),
    FOREIGN KEY (type_id) REFERENCES partner_types(id)
);

-- Создание таблицы типов продукции
CREATE TABLE IF NOT EXISTS product_types (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    type_name TEXT NOT NULL UNIQUE,
    type_coefficient REAL NOT NULL
);

-- Создание таблицы материалов
CREATE TABLE IF NOT EXISTS material_types (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    type_name TEXT NOT NULL UNIQUE,
    defect_percentage REAL NOT NULL
);

-- Создание таблицы продукции
CREATE TABLE IF NOT EXISTS products (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    product_name TEXT NOT NULL UNIQUE,
    article TEXT NOT NULL UNIQUE,
    type_id INTEGER NOT NULL,
    min_partner_price REAL NOT NULL,
    length REAL,
    width REAL,
    FOREIGN KEY (type_id) REFERENCES product_types(id)
);

-- Создание таблицы истории продаж
CREATE TABLE IF NOT EXISTS sales_history (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    product_id INTEGER NOT NULL,
    partner_id INTEGER NOT NULL,
    quantity INTEGER NOT NULL,
    sale_date TEXT NOT NULL,
    FOREIGN KEY (product_id) REFERENCES products(id),
    FOREIGN KEY (partner_id) REFERENCES partners(id)
);

-- Заполнение начальными данными
INSERT INTO partner_types (type_name) VALUES 
('ЗАО'), ('ООО'), ('ПАО'), ('ОАО');

INSERT INTO product_types (type_name, type_coefficient) VALUES 
('Ламинат', 2.35),
('Массивная доска', 5.15),
('Паркетная доска', 4.34),
('Пробковое покрытие', 1.5);

INSERT INTO material_types (type_name, defect_percentage) VALUES 
('Тип материала 1', 0.001),
('Тип материала 2', 0.0095),
('Тип материала 3', 0.0028),
('Тип материала 4', 0.0055),
('Тип материала 5', 0.0034);
2. Структура проекта
text
Copy
Download
master_pol_partners/
├── main.py                    # Главный модуль приложения
├── material_calculator.py     # Модуль расчета материалов
├── database.sql               # SQL-скрипт для создания БД
├── requirements.txt           # Зависимости Python
└── resources/                 # Ресурсы (изображения)
    ├── logo.png               # Логотип компании
    └── app_icon.ico           # Иконка приложения
3. Ключевые особенности реализации
1.	Модуль истории продаж:
o	Отдельное окно с таблицей продаж
o	Фильтрация по выбранному партнеру
o	Кнопка "Назад" для возврата к списку партнеров
o	Расчет необходимых материалов для всех продаж партнера
2.	Метод расчета материалов:
o	Учитывает параметры продукции (длина, ширина)
o	Учитывает коэффициент типа продукции
o	Учитывает процент брака материала
o	Возвращает -1 при неверных входных данных
o	Округляет результат до целого числа в большую сторону
3.	Интеграция с GUI:
o	Единый стиль интерфейса (цвета, шрифты)
o	Обработка ошибок с информативными сообщениями
o	Подтверждение действий пользователя
4.	База данных:
o	Полная схема с таблицами и связями
o	Начальные данные для типов продукции и материалов
o	Поддержка всех необходимых полей для расчетов
4. Инструкция по запуску
1.	Установить зависимости:
bash
Copy
Download
pip install -r requirements.txt
2.	Инициализировать базу данных:
bash
Copy
Download
sqlite3 master_pol_partners.db < database.sql
3.	Запустить приложение:
bash
Copy
Download
python main.py
5. Тестирование функционала
1.	Просмотр списка партнеров:
o	Карточки с основной информацией
o	Расчет скидки на основе объема продаж
2.	История продаж:
o	Открывается по кнопке "Продажи"
o	Таблица с детализацией по каждой продаже
o	Расчет материалов для всех продаж партнера
3.	Расчет материалов:
o	Автоматический расчет при нажатии кнопки
o	Учет всех параметров продукции
o	Обработка ошибок ввода
Все компоненты системы соответствуют руководству по стилю и требованиям заказчика.





  

Это мой первый сайт. Он размещён бесплатно!