diff --git a/Material_type_import.xlsx b/Material_type_import.xlsx new file mode 100644 index 0000000..14bfbf6 Binary files /dev/null and b/Material_type_import.xlsx differ diff --git a/Partner_products_import.xlsx b/Partner_products_import.xlsx new file mode 100644 index 0000000..eb7b428 Binary files /dev/null and b/Partner_products_import.xlsx differ diff --git a/Partners_import.xlsx b/Partners_import.xlsx new file mode 100644 index 0000000..06dfb34 Binary files /dev/null and b/Partners_import.xlsx differ diff --git a/Product_type_import.xlsx b/Product_type_import.xlsx new file mode 100644 index 0000000..cff7530 Binary files /dev/null and b/Product_type_import.xlsx differ diff --git a/Products_import.xlsx b/Products_import.xlsx new file mode 100644 index 0000000..0ce83b6 Binary files /dev/null and b/Products_import.xlsx differ diff --git a/README.md b/README.md index 8e6187d..3726da0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# demo +# demo_bpk @@ -15,14 +15,14 @@ Already a pro? Just edit this README.md and make it your own. Want to make it ea ``` cd existing_repo -git remote add origin http://192.168.0.185/scebob/demo.git +git remote add origin http://192.168.0.185/aiena/demo_bpk.git git branch -M main git push -uf origin main ``` ## Integrate with your tools -- [ ] [Set up project integrations](http://192.168.0.185/scebob/demo/-/settings/integrations) +- [ ] [Set up project integrations](http://192.168.0.185/aiena/demo_bpk/-/settings/integrations) ## Collaborate with your team diff --git a/Resource/Material_type_import.xlsx b/Resource/Material_type_import.xlsx new file mode 100644 index 0000000..14bfbf6 Binary files /dev/null and b/Resource/Material_type_import.xlsx differ diff --git a/Resource/Partner_products_import.xlsx b/Resource/Partner_products_import.xlsx new file mode 100644 index 0000000..eb7b428 Binary files /dev/null and b/Resource/Partner_products_import.xlsx differ diff --git a/Resource/Partners_import.xlsx b/Resource/Partners_import.xlsx new file mode 100644 index 0000000..06dfb34 Binary files /dev/null and b/Resource/Partners_import.xlsx differ diff --git a/Resource/Product_type_import.xlsx b/Resource/Product_type_import.xlsx new file mode 100644 index 0000000..cff7530 Binary files /dev/null and b/Resource/Product_type_import.xlsx differ diff --git a/Resource/Products_import.xlsx b/Resource/Products_import.xlsx new file mode 100644 index 0000000..0ce83b6 Binary files /dev/null and b/Resource/Products_import.xlsx differ diff --git a/Resource/masterpol.png b/Resource/masterpol.png new file mode 100644 index 0000000..c192a72 Binary files /dev/null and b/Resource/masterpol.png differ diff --git a/Resource/КОД 09.02.07-2-2025 Том 1.pdf b/Resource/КОД 09.02.07-2-2025 Том 1.pdf new file mode 100644 index 0000000..ce392e2 Binary files /dev/null and b/Resource/КОД 09.02.07-2-2025 Том 1.pdf differ diff --git a/Resource/Мастер пол.ico b/Resource/Мастер пол.ico new file mode 100644 index 0000000..9744b0a Binary files /dev/null and b/Resource/Мастер пол.ico differ diff --git a/Resource/Приложение 1.pdf b/Resource/Приложение 1.pdf new file mode 100644 index 0000000..da5d154 Binary files /dev/null and b/Resource/Приложение 1.pdf differ diff --git a/Resource/Приложение 2.pdf b/Resource/Приложение 2.pdf new file mode 100644 index 0000000..5d67971 Binary files /dev/null and b/Resource/Приложение 2.pdf differ diff --git a/link.txt b/link.txt new file mode 100644 index 0000000..ae24f4a --- /dev/null +++ b/link.txt @@ -0,0 +1,4 @@ +Введите имя файла с партнерами: Partners_import.xlsx +Введите имя файла с типами продукции: Product_type_import.xlsx +Введите имя файла с продуктами: Products_import.xlsx +Введите имя файла с продажами: Partner_products_import.xlsx diff --git a/main.py b/main.py new file mode 100644 index 0000000..4b6c465 --- /dev/null +++ b/main.py @@ -0,0 +1,444 @@ +import sqlite3 +import pandas as pd +import os +import sys + + +def create_tables(db_connection): + """Создает все необходимые таблицы в базе данных""" + cursor = db_connection.cursor() + + cursor.executescript(''' + CREATE TABLE IF NOT EXISTS PartnerType ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type_name VARCHAR(100) NOT NULL UNIQUE + ); + + CREATE TABLE IF NOT EXISTS Partner ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + partner_type_id INTEGER REFERENCES PartnerType(id), + name VARCHAR(255) NOT NULL, + director VARCHAR(255), + email VARCHAR(255), + phone VARCHAR(50), + legal_address TEXT, + inn VARCHAR(20) UNIQUE, + rating DECIMAL(3,2) DEFAULT 0.0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS ProductType ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type_name VARCHAR(100) NOT NULL UNIQUE, + coefficient DECIMAL(5,2) NOT NULL DEFAULT 1.0 + ); + + CREATE TABLE IF NOT EXISTS Product ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + product_type_id INTEGER REFERENCES ProductType(id), + product_name VARCHAR(255) NOT NULL, + article VARCHAR(100) UNIQUE NOT NULL, + min_partner_price DECIMAL(15,2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS Sale ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + product_id INTEGER REFERENCES Product(id), + partner_id INTEGER REFERENCES Partner(id), + quantity INTEGER NOT NULL CHECK (quantity > 0), + sale_date DATE NOT NULL, + sale_price DECIMAL(15,2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_partner_type ON Partner(partner_type_id); + CREATE INDEX IF NOT EXISTS idx_partner_inn ON Partner(inn); + CREATE INDEX IF NOT EXISTS idx_product_type ON Product(product_type_id); + CREATE INDEX IF NOT EXISTS idx_product_article ON Product(article); + CREATE INDEX IF NOT EXISTS idx_sale_product ON Sale(product_id); + CREATE INDEX IF NOT EXISTS idx_sale_partner ON Sale(partner_id); + CREATE INDEX IF NOT EXISTS idx_sale_date ON Sale(sale_date); + ''') + + db_connection.commit() + print("✅ Таблицы успешно созданы") + + +def calculate_discount(total_quantity): + """Рассчитывает скидку на основе общего количества проданной продукции""" + if total_quantity >= 300000: + return 15, '15% (более 300000 ед.)' + elif total_quantity >= 50000: + return 10, '10% (50000-300000 ед.)' + elif total_quantity >= 10000: + return 5, '5% (10000-50000 ед.)' + else: + return 0, '0% (до 10000 ед.)' + + +def get_partners(db_path='partners.db', sort_by='rating', ascending=False): + """Получает данные о партнерах с возможностью сортировки""" + try: + conn = sqlite3.connect(db_path) + query = """ + SELECT + pt.type_name AS "Тип партнера", + p.name AS "Наименование партнера", + p.director AS "Директор", + p.email AS "Электронная почта", + p.phone AS "Телефон", + p.legal_address AS "Юридический адрес", + p.inn AS "ИНН", + p.rating AS "Рейтинг", + p.created_at AS "Дата создания", + COALESCE(SUM(s.quantity), 0) as total_quantity + FROM Partner p + JOIN PartnerType pt ON p.partner_type_id = pt.id + LEFT JOIN Sale s ON p.id = s.partner_id + GROUP BY p.id + """ + + # Добавляем сортировку + if sort_by == 'discount': + query += " ORDER BY total_quantity" + elif sort_by == 'rating': + query += " ORDER BY p.rating" + elif sort_by == 'name': + query += " ORDER BY p.name" + elif sort_by == 'type': + query += " ORDER BY pt.type_name, p.name" + else: + query += " ORDER BY p.rating" + + query += " DESC" if not ascending else " ASC" + + df = pd.read_sql_query(query, conn) + conn.close() + + # Добавляем колонки со скидкой + if not df.empty: + df['Скидка'] = df['total_quantity'].apply(lambda x: calculate_discount(x)[0]) + df['Текст_скидки'] = df['total_quantity'].apply(lambda x: calculate_discount(x)[1]) + + return df + + except sqlite3.Error as e: + print(f"❌ Ошибка базы данных: {e}") + return None + except Exception as e: + print(f"❌ Общая ошибка: {e}") + return None + + +def get_sales_history(db_path='partners.db', limit=50, sort_by='date_desc'): + """Получает историю реализации продукции с разными вариантами сортировки""" + try: + conn = sqlite3.connect(db_path) + query = """ + SELECT + s.sale_date AS "Дата продажи", + p.name AS "Партнер", + pr.product_name AS "Продукция", + pr.article AS "Артикул", + s.quantity AS "Количество", + s.sale_price AS "Цена за единицу", + (s.quantity * s.sale_price) AS "Общая сумма", + pt.type_name AS "Тип продукции" + FROM Sale s + JOIN Partner p ON s.partner_id = p.id + JOIN Product pr ON s.product_id = pr.id + JOIN ProductType pt ON pr.product_type_id = pt.id + """ + + # Добавляем сортировку по дате + if sort_by == 'date_desc': + query += " ORDER BY s.sale_date DESC, s.created_at DESC" + elif sort_by == 'date_asc': + query += " ORDER BY s.sale_date ASC, s.created_at ASC" + elif sort_by == 'quantity_desc': + query += " ORDER BY s.quantity DESC" + elif sort_by == 'amount_desc': + query += " ORDER BY (s.quantity * s.sale_price) DESC" + else: + query += " ORDER BY s.sale_date DESC, s.created_at DESC" + + query += " LIMIT ?" + + df = pd.read_sql_query(query, conn, params=(limit,)) + conn.close() + return df + except Exception as e: + print(f"❌ Ошибка при получении истории продаж: {e}") + return None + +def print_partner_cards(partners_df): + """Выводит карточки партнеров в красивом формате""" + if partners_df is None or partners_df.empty: + print("📭 Нет данных о партнерах") + return + + for index, row in partners_df.iterrows(): + print("╔" + "═" * 69 + "╗") + print(f"║ {'КАРТОЧКА ПАРТНЕРА':^67} ║") + print("╠" + "═" * 69 + "╣") + + type_text = f"Тип: {row['Тип партнера']}" + name_text = f"Наименование: {row['Наименование партнера']}" + director_text = f"Директор: {row['Директор']}" + rating_text = f"Рейтинг: {row['Рейтинг']}" + quantity_text = f"Продано единиц: {row['total_quantity']:,}" + discount_text = f"Скидка: {row['Текст_скидки']}" + email_text = f"Контакты: {row['Электронная почта']}" + phone_text = f"Телефон: {row['Телефон']}" + inn_text = f"ИНН: {row['ИНН']}" + + address = row['Юридический адрес'] + if len(address) > 60: + address = address[:57] + "..." + address_text = f"Адрес: {address}" + + print(f"║ {type_text:<67} ║") + print(f"║ {name_text:<67} ║") + print(f"║ {director_text:<67} ║") + print(f"║ {rating_text:<67} ║") + print(f"║ {quantity_text:<67} ║") + print(f"║ {discount_text:<67} ║") + print(f"║ {'':<67} ║") + print(f"║ {email_text:<67} ║") + print(f"║ {phone_text:<67} ║") + print(f"║ {'':<67} ║") + print(f"║ {inn_text:<67} ║") + print(f"║ {address_text:<67} ║") + print("╚" + "═" * 69 + "╝") + print() + + +def print_sales_cards(sales_df): + """Выводит карточки истории реализации в виде таблицы""" + if sales_df is None or sales_df.empty: + print("📭 Нет данных о продажах") + return + + print("╔" + "═" * 147 + "╗") + print(f"║ {'ИСТОРИЯ РЕАЛИЗАЦИИ ПРОДУКЦИИ':^145} ║") + print("╠" + "═" * 147 + "╣") + + # Заголовок таблицы + headers = ["Дата", "Партнер", "Продукция", "Артикул", "Кол-во", "Цена", "Сумма"] + print("║ {:<12} {:<18} {:<59} {:<3} {:>9} {:>8} {:>13} ║".format( + headers[0], headers[1], headers[2], headers[3], headers[4], headers[5], headers[6] + )) + print("╠" + "═" * 147 + "╣") + + # Данные таблицы + for index, row in sales_df.iterrows(): + date = str(row['Дата продажи'])[:10] + partner = row['Партнер'] + product = row['Продукция'] + article = str(row['Артикул']) + quantity = f"{row['Количество']:,}" + price = f"{row['Цена за единицу']:,.2f}" + total = f"{row['Общая сумма']:,.2f}" + + print(f"║ {date} ║ {partner.ljust(16)} ║ {product.ljust(57)} ║ {article.ljust(8)} ║ {quantity.ljust(8)} ║ {price.ljust(10)} ║ {total.ljust(18)} ║") + + print("╚" + "═" * 147 + "╝") + + # Итоги + total_quantity = sales_df['Количество'].sum() + total_amount = sales_df['Общая сумма'].sum() + print(f"📊 ИТОГО: {len(sales_df)} продаж, {total_quantity:,} единиц, общая сумма: {total_amount:,.2f} руб.") +def simple_choice(options): + """Упрощенная функция выбора""" + print("\n" + "=" * 50) + print("ВЫБЕРИТЕ ОПЦИЮ:") + print("=" * 50) + for i, option in enumerate(options, 1): + print(f"{i}. {option}") + + while True: + try: + choice = input(f"\nВведите номер (1-{len(options)}): ").strip() + if choice.isdigit(): + choice_num = int(choice) + if 1 <= choice_num <= len(options): + return options[choice_num - 1] + print(f"❌ Пожалуйста, введите число от 1 до {len(options)}") + except (ValueError, EOFError): + print("❌ Неверный ввод. Попробуйте еще раз.") + except KeyboardInterrupt: + print("\n👋 Программа прервана пользователем.") + sys.exit(0) + + +def check_database(): + """Проверяет загруженные данные в базе""" + conn = sqlite3.connect('partners.db') + try: + cursor = conn.cursor() + print("\n🔍 Проверка загруженных данных:") + tables = ['PartnerType', 'Partner', 'ProductType', 'Product', 'Sale'] + for table in tables: + cursor.execute(f"SELECT COUNT(*) FROM {table}") + count = cursor.fetchone()[0] + print(f" {table}: {count} записей") + finally: + conn.close() + + +def load_data_from_files(): + """Загружает данные из Excel файлов в базу данных""" + conn = sqlite3.connect('partners.db') + cursor = conn.cursor() + + try: + # Запрос имен файлов + print("\n📁 Введите имена файлов:") + partners_file = input("Файл с партнерами: ") + product_type_file = input("Файл с типами продукции: ") + products_file = input("Файл с продуктами: ") + sales_file = input("Файл с продажами: ") + + # 1. Загрузка типов партнеров + print("\n1. 📊 Загрузка типов партнеров...") + partners_df = pd.read_excel(partners_file) + partner_types = partners_df['Тип партнера'].unique() + for partner_type in partner_types: + cursor.execute("INSERT OR IGNORE INTO PartnerType (type_name) VALUES (?)", (partner_type,)) + + # 2. Загрузка партнеров + print("2. 👥 Загрузка партнеров...") + for _, row in partners_df.iterrows(): + cursor.execute("SELECT id FROM PartnerType WHERE type_name = ?", (row['Тип партнера'],)) + partner_type_id = cursor.fetchone()[0] + + cursor.execute("""INSERT OR IGNORE INTO Partner + (partner_type_id, name, director, email, phone, legal_address, inn, rating) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", + (partner_type_id, row['Наименование партнера'], row['Директор'], + row['Электронная почта партнера'], row['Телефон партнера'], + row['Юридический адрес партнера'], row['ИНН'], row['Рейтинг']) + ) + + # 3. Загрузка типов продукции + print("3. 📦 Загрузка типов продукции...") + product_type_df = pd.read_excel(product_type_file) + for _, row in product_type_df.iterrows(): + cursor.execute("INSERT OR IGNORE INTO ProductType (type_name, coefficient) VALUES (?, ?)", + (row['Тип продукции'], row['Коэффициент типа продукции']) + ) + + # 4. Загрузка продуктов + print("4. 🏷️ Загрузка продуктов...") + products_df = pd.read_excel(products_file) + for _, row in products_df.iterrows(): + cursor.execute("SELECT id FROM ProductType WHERE type_name = ?", (row['Тип продукции'],)) + product_type_id = cursor.fetchone()[0] + + cursor.execute("""INSERT OR IGNORE INTO Product + (product_type_id, product_name, article, min_partner_price) + VALUES (?, ?, ?, ?)""", + (product_type_id, row['Наименование продукции'], + row['Артикул'], row['Минимальная стоимость для партнера']) + ) + + # 5. Загрузка продаж + print("5. 💰 Загрузка продаж...") + sales_df = pd.read_excel(sales_file) + loaded_sales = 0 + + for _, row in sales_df.iterrows(): + # Находим ID продукта + cursor.execute("SELECT id FROM Product WHERE product_name = ?", (row['Продукция'],)) + product_result = cursor.fetchone() + if not product_result: + print(f" ⚠️ Продукт не найден: {row['Продукция']}") + continue + product_id = product_result[0] + + # Находим ID партнера + cursor.execute("SELECT id FROM Partner WHERE name = ?", (row['Наименование партнера'],)) + partner_result = cursor.fetchone() + if not partner_result: + print(f" ⚠️ Партнер не найден: {row['Наименование партнера']}") + continue + partner_id = partner_result[0] + + # Получаем цену продажи + cursor.execute("SELECT min_partner_price FROM Product WHERE id = ?", (product_id,)) + min_price_result = cursor.fetchone() + sale_price = min_price_result[0] if min_price_result else 0 + + # Преобразуем дату + sale_date = pd.to_datetime(row['Дата продажи']).strftime('%Y-%m-%d') + + # Вставляем продажу + cursor.execute("""INSERT INTO Sale + (product_id, partner_id, quantity, sale_date, sale_price) + VALUES (?, ?, ?, ?, ?)""", + (product_id, partner_id, row['Количество продукции'], sale_date, sale_price) + ) + loaded_sales += 1 + + conn.commit() + print(f"✅ Данные успешно загружены! Загружено {loaded_sales} продаж") + check_database() + + except Exception as e: + print(f"❌ Ошибка при загрузке данных: {e}") + conn.rollback() + finally: + conn.close() + + +def display_menu(): + """Главное меню программы""" + menu_options = [ + 'КАРТОЧКИ ПАРТНЕРОВ (сортировка по рейтингу)', + 'КАРТОЧКИ ПАРТНЕРОВ (сортировка по скидкам)', + 'ИСТОРИЯ РЕАЛИЗАЦИИ', + 'ЗАПОЛНЕНИЕ БД из файлов', + 'ВЫХОД' + ] + + while True: + choice = simple_choice(menu_options) + + if choice == 'КАРТОЧКИ ПАРТНЕРОВ (сортировка по рейтингу)': + print("\n🎯 КАРТОЧКИ ПАРТНЕРОВ (сортировка по рейтингу)") + print("═" * 70) + partners = get_partners(sort_by='rating', ascending=False) + print_partner_cards(partners) + + elif choice == 'КАРТОЧКИ ПАРТНЕРОВ (сортировка по скидкам)': + print("\n📊 КАРТОЧКИ ПАРТНЕРОВ (сортировка по скидкам)") + print("═" * 70) + partners = get_partners(sort_by='discount', ascending=False) + print_partner_cards(partners) + + elif choice == 'ИСТОРИЯ РЕАЛИЗАЦИИ': + print("\n📈 ИСТОРИЯ РЕАЛИЗАЦИИ ПРОДУКЦИИ") + print("═" * 70) + sales = get_sales_history(limit=50) + print_sales_cards(sales) + + elif choice == 'ЗАПОЛНЕНИЕ БД из файлов': + if os.path.exists('partners.db'): + os.remove('partners.db') + print("🗑️ База данных partners.db удалена") + else: + print("ℹ️ База данных partners.db не найдена, создаем новую") + + conn = sqlite3.connect('partners.db') + create_tables(conn) + conn.close() + load_data_from_files() + + elif choice == 'ВЫХОД': + print("\n👋 До свидания!") + break + + +if __name__ == "__main__": + display_menu() \ No newline at end of file diff --git a/main_flask.py b/main_flask.py new file mode 100644 index 0000000..2a652db --- /dev/null +++ b/main_flask.py @@ -0,0 +1,261 @@ +from flask import Flask, render_template, send_from_directory, request, redirect, url_for, flash +import sqlite3 +import os +from datetime import datetime + +app = Flask(__name__) +app.secret_key = 'my_secret_key_12345' + +def get_db_connection(): + conn = sqlite3.connect('partners.db') + conn.row_factory = sqlite3.Row + return conn +def calculate_discount(total_quantity): + """Рассчитывает скидку на основе общего количества проданной продукции""" + if total_quantity >= 300000: + return 15, '15% (более 300000 ед.)' + elif total_quantity >= 50000: + return 10, '10% (50000-300000 ед.)' + elif total_quantity >= 10000: + return 5, '5% (10000-50000 ед.)' + else: + return 0, '0% (до 10000 ед.)' +@app.route('/') +def index(): + conn = get_db_connection() + + partners = conn.execute(''' + SELECT p.*, pt.type_name as partner_type_name, + COALESCE(SUM(s.quantity), 0) as total_quantity, + COALESCE(SUM(s.quantity * s.sale_price), 0) as total_sales + FROM Partner p + LEFT JOIN PartnerType pt ON p.partner_type_id = pt.id + LEFT JOIN Sale s ON p.id = s.partner_id + GROUP BY p.id + ORDER BY total_quantity DESC + ''').fetchall() + + partners_with_discount = [] + for partner in partners: + total_quantity = partner['total_quantity'] or 0 + discount_percent, discount_text = calculate_discount(total_quantity) + + partner_dict = dict(partner) + partner_dict['total_quantity'] = total_quantity + partner_dict['total_sales'] = partner['total_sales'] or 0 + partner_dict['discount_percent'] = discount_percent + partner_dict['discount_text'] = discount_text + + partners_with_discount.append(partner_dict) + + conn.close() + return render_template('index.html', partners=partners_with_discount) + + +@app.route('/add_sale/', methods=['GET', 'POST']) +def add_sale(partner_id): + if request.method == 'POST': + try: + product_id = request.form['product_id'] + quantity = request.form['quantity'] + sale_price = request.form['sale_price'] + sale_date = request.form['sale_date'] + + conn = get_db_connection() + conn.execute(''' + INSERT INTO Sale (product_id, partner_id, quantity, sale_price, sale_date) + VALUES (?, ?, ?, ?, ?) + ''', (product_id, partner_id, quantity, sale_price, sale_date)) + + conn.commit() + conn.close() + + flash('Продажа успешно добавлена!', 'success') + return redirect(url_for('sales_history', partner_id=partner_id)) + + except Exception as e: + flash(f'Ошибка при добавлении продажи: {str(e)}', 'error') + return redirect(url_for('add_sale', partner_id=partner_id)) + + # GET запрос - показываем форму + conn = get_db_connection() + + partner = conn.execute(''' + SELECT p.*, pt.type_name as partner_type_name + FROM Partner p + LEFT JOIN PartnerType pt ON p.partner_type_id = pt.id + WHERE p.id = ? + ''', (partner_id,)).fetchone() + + products = conn.execute(''' + SELECT p.*, pt.type_name as product_type_name + FROM Product p + LEFT JOIN ProductType pt ON p.product_type_id = pt.id + ORDER BY p.product_name + ''').fetchall() + + conn.close() + + if not partner: + flash('Партнер не найден', 'error') + return redirect(url_for('index')) + + today = datetime.now().strftime('%Y-%m-%d') + + return render_template('add_sale.html', partner=partner, products=products, today=today) + + +@app.route('/edit_partner/', methods=['GET', 'POST']) +def edit_partner(partner_id): + conn = get_db_connection() + + if request.method == 'POST': + try: + name = request.form['name'] + director = request.form.get('director', '') + email = request.form['email'] + phone = request.form.get('phone', '') + legal_address = request.form.get('legal_address', '') + inn = request.form.get('inn', '') + partner_type_id = request.form['partner_type_id'] + rating = request.form.get('rating', 0.0) + + # Проверяем уникальность ИНН (исключая текущего партнера) + if inn: + existing = conn.execute( + 'SELECT id FROM Partner WHERE inn = ? AND id != ?', + (inn, partner_id) + ).fetchone() + if existing: + flash('Партнер с таким ИНН уже существует', 'error') + conn.close() + return redirect(url_for('edit_partner', partner_id=partner_id)) + + # Обновляем данные партнера + conn.execute(''' + UPDATE Partner + SET name = ?, director = ?, email = ?, phone = ?, + legal_address = ?, inn = ?, partner_type_id = ?, rating = ? + WHERE id = ? + ''', (name, director, email, phone, legal_address, inn, partner_type_id, rating, partner_id)) + + conn.commit() + conn.close() + + flash('Данные партнера успешно обновлены!', 'success') + return redirect(url_for('index')) + + except Exception as e: + flash(f'Ошибка при обновлении данных: {str(e)}', 'error') + conn.close() + return redirect(url_for('edit_partner', partner_id=partner_id)) + + # GET запрос - показываем форму редактирования + partner = conn.execute(''' + SELECT p.*, pt.type_name as partner_type_name + FROM Partner p + LEFT JOIN PartnerType pt ON p.partner_type_id = pt.id + WHERE p.id = ? + ''', (partner_id,)).fetchone() + + partner_types = conn.execute('SELECT * FROM PartnerType ORDER BY type_name').fetchall() + conn.close() + + if not partner: + flash('Партнер не найден', 'error') + return redirect(url_for('index')) + + return render_template('edit_partner.html', partner=partner, partner_types=partner_types) +@app.route('/add_partner', methods=['GET', 'POST']) +def add_partner(): + if request.method == 'POST': + # Получаем данные из формы + name = request.form['name'] + director = request.form.get('director', '') + email = request.form['email'] + phone = request.form.get('phone', '') + legal_address = request.form.get('legal_address', '') + inn = request.form.get('inn', '') + partner_type_id = request.form['partner_type_id'] + rating = request.form.get('rating', 0.0) + + try: + conn = get_db_connection() + conn.execute(''' + INSERT INTO Partner (name, director, email, phone, legal_address, inn, partner_type_id, rating) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''', (name, director, email, phone, legal_address, inn, partner_type_id, rating)) + + conn.commit() + conn.close() + + flash('Партнер успешно добавлен!', 'success') + return redirect(url_for('index')) + + except Exception as e: + flash(f'Ошибка при добавлении партнера: {str(e)}', 'error') + return redirect(url_for('add_partner')) + + # GET запрос - показываем форму + conn = get_db_connection() + partner_types = conn.execute('SELECT * FROM PartnerType').fetchall() + conn.close() + + return render_template('add_partner.html', partner_types=partner_types) +@app.route('/sales_history/') +def sales_history(partner_id): + conn = get_db_connection() + + partner = conn.execute(''' + SELECT p.*, pt.type_name as partner_type_name + FROM Partner p + LEFT JOIN PartnerType pt ON p.partner_type_id = pt.id + WHERE p.id = ? + ''', (partner_id,)).fetchone() + + # Получаем историю продаж (исправлено: product_name вместо name) + sales = conn.execute(''' + SELECT + s.*, + pr.product_name as product_name, + pr.article as product_article, + pt.type_name as product_type_name + FROM Sale s + LEFT JOIN Product pr ON s.product_id = pr.id + LEFT JOIN ProductType pt ON pr.product_type_id = pt.id + WHERE s.partner_id = ? + ORDER BY s.sale_date DESC, s.created_at DESC + ''', (partner_id,)).fetchall() + + # Считаем общую статистику + total_stats = conn.execute(''' + SELECT + COUNT(*) as total_sales, + SUM(s.quantity) as total_quantity, + SUM(s.quantity * s.sale_price) as total_amount, + AVG(s.sale_price) as avg_price + FROM Sale s + WHERE s.partner_id = ? + ''', (partner_id,)).fetchone() + + conn.close() + + return render_template('sales_history.html', + partner=partner, + sales=sales, + total_stats=total_stats) + + +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + + +@app.route('/static/') +def static_files(filename): + return send_from_directory(app.root_path + '/static', filename) + + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/partners.db b/partners.db new file mode 100644 index 0000000..96e7888 Binary files /dev/null and b/partners.db differ diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..916c865 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,279 @@ +/* Подключаем шрифт Segoe UI */ +@import url('https://fonts.googleapis.com/css2?family=Segoe+UI:wght@300;400;600;700&display=swap'); + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #F4E8D3; + margin: 0; + padding: 0; +} + +.main-container { + background-color: #ffffff; + min-height: 100vh; + padding: 20px; +} + +.header { + background: linear-gradient(135deg, #67BA80 0%, #4CAF50 100%); + padding: 20px; + border-radius: 10px; + margin-bottom: 30px; + color: white; +} + +.logo-container { + display: flex; + align-items: center; + gap: 20px; +} + +.logo { + height: 60px; + width: auto; + object-fit: contain; +} + +.header-title { + font-size: 2.2em; + font-weight: 700; + margin: 0; + text-shadow: 2px 2px 4px rgba(0,0,0,0.3); +} + +.page-title { + color: #2c3e50; + font-weight: 600; +} + +.partner-card { + border: 2px solid #67BA80; + border-radius: 12px; + padding: 20px; + margin-bottom: 20px; + background: #ffffff; + box-shadow: 0 4px 12px rgba(103, 186, 128, 0.15); +} + +.partner-header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 2px solid #e9ecef; + padding-bottom: 15px; + margin-bottom: 15px; +} + +.partner-name-line { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.partner-type { + font-size: 1.3em; + font-weight: 600; + color: #6c757d; + background: #f8f9fa; + padding: 6px 14px; + border-radius: 20px; +} + +.partner-name { + font-size: 1.4em; + font-weight: 700; + color: #2c3e50; + margin: 0; +} + +.divider { + color: #dee2e6; + font-weight: bold; + font-size: 1.4em; +} + +.discount-info { + display: flex; + align-items: center; + gap: 15px; +} + +.discount-badge { + font-size: 1.1em; + padding: 8px 16px; + border-radius: 25px; + background-color: #67BA80; + color: white; +} + +.sales-amount { + font-size: 1em; + color: #6c757d; + font-weight: 500; +} + +.partner-info-row { + margin-bottom: 0; + padding: 6px 0; + line-height: 1.4; +} + +.partner-label { + font-weight: 600; + color: #495057; + min-width: 100px; + display: inline-block; + font-size: 1em; +} + +.partner-value { + font-size: 1em; + color: #2c3e50; +} + +.partner-buttons-container { + display: flex; + gap: 12px; + justify-content: flex-start; + margin-top: 20px; + padding-top: 15px; + border-top: 2px solid #e9ecef; +} + +.btn-action { + padding: 10px 20px; + font-size: 0.95em; + display: flex; + align-items: center; + gap: 8px; + min-width: 180px; + justify-content: center; + border: none; + border-radius: 8px; + font-weight: 600; + transition: all 0.3s ease; +} + +.btn-edit { + background-color: #67BA80; + color: white; +} + +.btn-add { + background-color: #6c757d; + color: white; +} + +.btn-history { + background-color: #495057; + color: white; +} + +.btn-action:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0,0,0,0.2); + color: white; +} + +.btn-new-partner { + background-color: #67BA80; + color: white; + border: none; + padding: 12px 24px; + font-weight: 600; + border-radius: 8px; +} + +.btn-new-partner:hover { + background-color: #5aa870; + color: white; +} + +.rating-badge { + font-size: 0.9em; + padding: 6px 12px; + border-radius: 15px; +} + +.alert { + border-radius: 10px; + border: none; +} +.partner-info-row:last-child { + margin-bottom: 10px; +} + +.volume-info { + background-color: #f8f9fa; + padding: 8px 12px; + border-radius: 6px; + margin-top: 10px; +} + +.volume-label { + font-weight: 600; + color: #495057; +} + +.volume-value { + color: #2c3e50; + font-weight: 500; +} + +/* Стили для страницы истории продаж */ +/* Стили для таблицы продаж */ +.table-responsive { + border-radius: 8px; + overflow: hidden; +} + +.table { + margin-bottom: 0; +} + +.table th { + background-color: #67BA80; + color: white; + font-weight: 600; + padding: 12px; + text-align: center; +} + +.table td { + padding: 10px; + text-align: center; + vertical-align: middle; +} + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(103, 186, 128, 0.05); +} + +.table-striped tbody tr:hover { + background-color: rgba(103, 186, 128, 0.1); +} + +.table tfoot { + background-color: #e8f5e8; + font-weight: 600; +} + +.table tfoot td { + border-top: 2px solid #67BA80; +} + +.text-end { + text-align: right !important; +} + +/* Адаптивность для таблицы */ +@media (max-width: 768px) { + .table-responsive { + font-size: 0.9em; + } + + .table th, .table td { + padding: 6px; + } +} \ No newline at end of file diff --git a/static/js/script.js b/static/js/script.js new file mode 100644 index 0000000..f5b0daa --- /dev/null +++ b/static/js/script.js @@ -0,0 +1,37 @@ +document.addEventListener('DOMContentLoaded', function() { + // Кнопка нового партнера + const newPartnerBtn = document.querySelector('.btn-new-partner'); + if (newPartnerBtn) { + newPartnerBtn.addEventListener('click', function() { + // Переход на страницу добавления партнера + window.location.href = '/add_partner'; + }); + } + + // Кнопка "Редактирование" + document.querySelectorAll('.btn-edit').forEach(btn => { + btn.addEventListener('click', function() { + const partnerId = this.getAttribute('data-partner-id'); + // Переход на страницу редактирования + window.location.href = `/edit_partner/${partnerId}`; + }); + }); + + // Кнопка "Добавление продажи" + document.querySelectorAll('.btn-add').forEach(btn => { + btn.addEventListener('click', function() { + const partnerId = this.getAttribute('data-partner-id'); + // Переход на страницу добавления продажи + window.location.href = `/add_sale/${partnerId}`; + }); + }); + + // Кнопка "История реализации" + document.querySelectorAll('.btn-history').forEach(btn => { + btn.addEventListener('click', function() { + const partnerId = this.getAttribute('data-partner-id'); + // Переход на страницу истории реализации + window.location.href = `/sales_history/${partnerId}`; + }); + }); +}); \ No newline at end of file diff --git a/static/masterpol.png b/static/masterpol.png new file mode 100644 index 0000000..c192a72 Binary files /dev/null and b/static/masterpol.png differ diff --git a/static/Мастер пол.ico b/static/Мастер пол.ico new file mode 100644 index 0000000..9744b0a Binary files /dev/null and b/static/Мастер пол.ico differ diff --git a/templates/about.html b/templates/about.html new file mode 100644 index 0000000..77fdb9f --- /dev/null +++ b/templates/about.html @@ -0,0 +1,18 @@ + + + + + About Flask + + + {% extends "template.html" %} + {% block content %} + +

About Flask

+

Flask is a micro web framework written in Python.

+

Applications that use the Flask framework include Pinterest, + LinkedIn, and the community web page for Flask itself.

+ + {% endblock %} + + \ No newline at end of file diff --git a/templates/add_partner.html b/templates/add_partner.html new file mode 100644 index 0000000..cc61b0f --- /dev/null +++ b/templates/add_partner.html @@ -0,0 +1,67 @@ +{% extends "base.html" %} + +{% block content %} +
+

Добавление нового партнера

+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} + {% endwith %} + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + Отмена +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/add_sale.html b/templates/add_sale.html new file mode 100644 index 0000000..95878ae --- /dev/null +++ b/templates/add_sale.html @@ -0,0 +1,182 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+
+
+

+ Добавление продажи +

+
+
+ +
+
Партнер: {{ partner.name }}
+

Тип: {{ partner.partner_type_name }}

+

Email: {{ partner.email }}

+

Телефон: {{ partner.phone or 'Не указан' }}

+
+ + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ {{ message }} + +
+ {% endfor %} + {% endif %} + {% endwith %} + +
+
+
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
Минимальная цена: 0 руб.
+
+ +
+ + +
+
+
+ + +
+
+
+
+
+ Количество: + 1 ед. +
+
+ Цена за единицу: + 0 руб. +
+
+ Итоговая сумма: + 0 руб. +
+
+
+
+
+ +
+
+
+ + Назад к истории + + +
+
+
+
+
+
+
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..6a4729f --- /dev/null +++ b/templates/base.html @@ -0,0 +1,26 @@ + + + + + + {% block title %}MasterPol - Управление партнерами{% endblock %} + + + + + + +
+
+ {% block header %} + {% include 'components/header.html' %} + {% endblock %} + + {% block content %} + {% endblock %} +
+
+ + + + \ No newline at end of file diff --git a/templates/components/header.html b/templates/components/header.html new file mode 100644 index 0000000..9130468 --- /dev/null +++ b/templates/components/header.html @@ -0,0 +1,6 @@ +
+
+ +

Управление партнерами

+
+
\ No newline at end of file diff --git a/templates/components/partner_card.html b/templates/components/partner_card.html new file mode 100644 index 0000000..facdadd --- /dev/null +++ b/templates/components/partner_card.html @@ -0,0 +1,73 @@ +
+
+ +
+
+ + {% if partner.partner_type_name %} + {{ partner.partner_type_name }} + {% else %} + Тип не указан + {% endif %} + + | + {{ partner.name }} +
+ +
+ + Скидка {{ partner.discount_percent }}% + + + {{ partner.total_quantity }} ед. + +
+
+ + +
+ Директор: + {{ partner.director or 'Не указан' }} +
+ +
+ Телефон: + + {% if partner.phone %} + {{ partner.phone }} + {% else %} + Не указан + {% endif %} + +
+ +
+ Рейтинг: + + + {{ partner.rating }} + + +
+ +
+ Объем продаж: + + {{ "%.2f"|format(partner.total_sales) }} руб. + +
+ + +
+ + + +
+
+
\ No newline at end of file diff --git a/templates/edit_partner.html b/templates/edit_partner.html new file mode 100644 index 0000000..97a339f --- /dev/null +++ b/templates/edit_partner.html @@ -0,0 +1,142 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+
+
+

+ Редактирование партнера +

+
+
+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ {{ message }} + +
+ {% endfor %} + {% endif %} + {% endwith %} + +
+
+ +
+
Основная информация
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
От 0.0 до 5.0
+
+
+ + +
+
Контактная информация
+ +
+ + +
+ +
+ + +
+ +
+ + +
10 или 12 цифр
+
+ +
+ + +
+
+
+ +
+
+
+ + Назад к списку + +
+ + История продаж + + +
+
+
+
+
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..085f57c --- /dev/null +++ b/templates/home.html @@ -0,0 +1,16 @@ + + + + + Flask Tutorial + + + {% extends "template.html" %} + {% block content %} + +

My First Try Using Flask

+

Flask is Fun

+ + {% endblock %} + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..59a3a28 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block content %} + +
+

Список партнеров

+ + Новый партнер + +
+ + {% if partners %} +
+ {% for partner in partners %} + {% include 'components/partner_card.html' %} + {% endfor %} +
+ {% else %} +
+ Нет данных о партнерах +
+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/sales_history.html b/templates/sales_history.html new file mode 100644 index 0000000..a17c2a9 --- /dev/null +++ b/templates/sales_history.html @@ -0,0 +1,141 @@ +{% extends "base.html" %} + +{% block title %}История продаж - {{ partner.name }}{% endblock %} + +{% block content %} +
+ +
+
+ + Назад + +

История продаж - {{ partner.name }}

+
+
+ + +
+
+
+ + {% if partner.partner_type_name %} + {{ partner.partner_type_name }} + {% else %} + Тип не указан + {% endif %} + + | + {{ partner.name }} +
+
+ +
+
+
+
+ Директор: + {{ partner.director or 'Не указан' }} +
+
+ Телефон: + {{ partner.phone or 'Не указан' }} +
+
+
+
+ Email: + {{ partner.email or 'Не указан' }} +
+
+ ИНН: + {{ partner.inn or 'Не указан' }} +
+
+
+
+
+ + +
+

Общая статистика

+
+
+
+
{{ total_stats.total_sales or 0 }}
+
Всего продаж
+
+
+
+
+
{{ total_stats.total_quantity or 0 }}
+
Общее количество
+
+
+
+
+
{{ "%.2f"|format(total_stats.total_amount or 0) }} руб.
+
Общая сумма
+
+
+
+
+
{{ "%.2f"|format(total_stats.avg_price or 0) }} руб.
+
Средняя цена
+
+
+
+
+ + +
+

Детализация продаж

+ + {% if sales %} +
+ + + + + + + + + + + + + + + {% for sale in sales %} + + + + + + + + + + + {% endfor %} + + + + + + + + + + +
Дата продажиТоварАртикулТип товараКоличествоЦена за ед.Общая суммаДата создания
{{ sale.sale_date }}{{ sale.product_name or 'Неизвестный товар' }}{{ sale.product_article or '-' }}{{ sale.product_type_name or '-' }}{{ sale.quantity }} ед.{{ "%.2f"|format(sale.sale_price) }} руб.{{ "%.2f"|format(sale.quantity * sale.sale_price) }} руб.{{ sale.created_at[:10] if sale.created_at else '-' }}
Итого:{{ total_stats.total_quantity or 0 }} ед.{{ "%.2f"|format(total_stats.avg_price or 0) }} руб.{{ "%.2f"|format(total_stats.total_amount or 0) }} руб.
+
+ {% else %} +
+ Нет данных о продажах +
+ {% endif %} +
+
+{% endblock %} \ No newline at end of file