897 lines
34 KiB
Python
897 lines
34 KiB
Python
from flask import Flask, render_template, send_from_directory, request, redirect, url_for, flash, session
|
||
import sqlite3
|
||
import os
|
||
from datetime import datetime, timedelta
|
||
|
||
app = Flask(__name__)
|
||
app.secret_key = 'my_secret_key_12345'
|
||
|
||
# ========== ФИЛЬТР ДЛЯ ФОРМАТИРОВАНИЯ ДАТ ==========
|
||
@app.template_filter('date_format')
|
||
def date_format(value):
|
||
"""Преобразует дату из формата YYYY-MM-DD в ДД.ММ.ГГГГ"""
|
||
if not value:
|
||
return '-'
|
||
try:
|
||
# Если value уже строка, пробуем распарсить
|
||
if isinstance(value, str) and len(value) >= 10:
|
||
# Проверяем, что это похоже на дату в формате YYYY-MM-DD
|
||
if value[4] == '-' and value[7] == '-':
|
||
year, month, day = value[:10].split('-')
|
||
return f"{day}.{month}.{year}"
|
||
return value
|
||
except:
|
||
return value
|
||
|
||
def get_db_connection():
|
||
conn = sqlite3.connect('partners.db')
|
||
conn.row_factory = sqlite3.Row
|
||
return conn
|
||
|
||
def dict_from_row(row):
|
||
"""Преобразует sqlite3.Row в словарь"""
|
||
if row is None:
|
||
return None
|
||
return {key: row[key] for key in row.keys()}
|
||
|
||
def list_from_rows(rows):
|
||
"""Преобразует список sqlite3.Row в список словарей"""
|
||
return [dict_from_row(row) for row in rows]
|
||
|
||
def calculate_discount(total_amount):
|
||
"""Рассчитывает скидку на основе общей суммы заказов в рублях"""
|
||
return 0, '0%'
|
||
|
||
def calculate_order_discount(order_amount):
|
||
"""Рассчитывает скидку на текущий заказ в зависимости от его суммы"""
|
||
return 0
|
||
|
||
# ========== ГЛАВНАЯ СТРАНИЦА ==========
|
||
|
||
@app.route('/')
|
||
def index():
|
||
# Получаем параметры фильтрации из URL
|
||
search_query = request.args.get('search', '')
|
||
client_type = request.args.get('type', 'all')
|
||
min_rating = request.args.get('rating', '0')
|
||
sort_by = request.args.get('sort', 'name_asc')
|
||
|
||
conn = get_db_connection()
|
||
|
||
try:
|
||
# Получаем общее количество всех клиентов (без фильтров)
|
||
total_count = conn.execute('SELECT COUNT(*) as count FROM Clients').fetchone()['count']
|
||
|
||
# Базовый SQL запрос для клиентов
|
||
sql = '''
|
||
SELECT c.*, ct.type_name as contractor_type_name,
|
||
COALESCE(SUM(o.total_amount), 0) as total_amount,
|
||
COALESCE(SUM(oi.quantity), 0) as total_quantity,
|
||
COUNT(DISTINCT o.id) as orders_count
|
||
FROM Clients c
|
||
LEFT JOIN ContractorType ct ON c.contractor_type_id = ct.id
|
||
LEFT JOIN Orders o ON c.id = o.client_id
|
||
LEFT JOIN OrderItems oi ON o.id = oi.order_id
|
||
WHERE 1=1
|
||
'''
|
||
params = []
|
||
|
||
# Поиск по названию или ИНН
|
||
if search_query:
|
||
sql += " AND (c.name LIKE ? OR c.inn LIKE ?)"
|
||
params.extend([f'%{search_query}%', f'%{search_query}%'])
|
||
|
||
# Фильтр по типу клиента
|
||
if client_type != 'all':
|
||
sql += " AND ct.type_name = ?"
|
||
params.append(client_type)
|
||
|
||
# Фильтр по минимальному рейтингу
|
||
if min_rating != '0':
|
||
sql += " AND c.rating >= ?"
|
||
params.append(float(min_rating))
|
||
|
||
# Группировка (обязательно после WHERE)
|
||
sql += " GROUP BY c.id"
|
||
|
||
# Сортировка
|
||
if sort_by == 'name_asc':
|
||
sql += " ORDER BY c.name ASC"
|
||
elif sort_by == 'name_desc':
|
||
sql += " ORDER BY c.name DESC"
|
||
elif sort_by == 'rating_desc':
|
||
sql += " ORDER BY c.rating DESC"
|
||
elif sort_by == 'rating_asc':
|
||
sql += " ORDER BY c.rating ASC"
|
||
elif sort_by == 'discount_desc':
|
||
sql += " ORDER BY total_amount DESC"
|
||
elif sort_by == 'discount_asc':
|
||
sql += " ORDER BY total_amount ASC"
|
||
else:
|
||
sql += " ORDER BY c.name ASC"
|
||
|
||
# Выполняем запрос и преобразуем в словари
|
||
rows = conn.execute(sql, params).fetchall()
|
||
clients = list_from_rows(rows)
|
||
|
||
# Добавляем информацию о скидке на основе суммы
|
||
clients_with_discount = []
|
||
for client in clients:
|
||
total_amount = client['total_amount'] or 0
|
||
discount_percent, discount_text = calculate_discount(total_amount)
|
||
|
||
client['total_sales'] = total_amount
|
||
client['discount_percent'] = discount_percent
|
||
client['discount'] = discount_percent
|
||
client['discount_text'] = discount_text
|
||
|
||
clients_with_discount.append(client)
|
||
|
||
conn.close()
|
||
|
||
return render_template('index.html',
|
||
partners=clients_with_discount,
|
||
total_count=total_count)
|
||
|
||
except Exception as e:
|
||
conn.close()
|
||
print(f"❌ ОШИБКА: {e}")
|
||
return render_template('index.html',
|
||
partners=[],
|
||
total_count=0,
|
||
error_message=f"Ошибка базы данных: {str(e)}")
|
||
|
||
# ========== МАРШРУТЫ ДЛЯ КЛИЕНТОВ ==========
|
||
|
||
@app.route('/add_client', methods=['GET', 'POST'])
|
||
def add_client():
|
||
if request.method == 'POST':
|
||
try:
|
||
name = request.form['name']
|
||
director = request.form.get('director', '')
|
||
email = request.form['email']
|
||
phone = request.form.get('phone', '')
|
||
address = request.form.get('address', '')
|
||
inn = request.form.get('inn', '')
|
||
contractor_type_id = request.form['contractor_type_id']
|
||
rating = request.form.get('rating', 0.0)
|
||
|
||
conn = get_db_connection()
|
||
|
||
# Проверяем уникальность ИНН
|
||
if inn:
|
||
existing = conn.execute('SELECT id FROM Clients WHERE inn = ?', (inn,)).fetchone()
|
||
if existing:
|
||
flash('Клиент с таким ИНН уже существует', 'error')
|
||
conn.close()
|
||
return redirect(url_for('add_client'))
|
||
|
||
conn.execute('''
|
||
INSERT INTO Clients (name, director, email, phone, address, inn, contractor_type_id, rating)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||
''', (name, director, email, phone, address, inn, contractor_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_client'))
|
||
|
||
# GET запрос
|
||
conn = get_db_connection()
|
||
contractor_types = conn.execute('SELECT * FROM ContractorType ORDER BY type_name').fetchall()
|
||
conn.close()
|
||
return render_template('add_client.html', contractor_types=contractor_types)
|
||
|
||
@app.route('/edit_client/<int:client_id>', methods=['GET', 'POST'])
|
||
def edit_client(client_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', '')
|
||
address = request.form.get('address', '')
|
||
inn = request.form.get('inn', '')
|
||
contractor_type_id = request.form['contractor_type_id']
|
||
rating = request.form.get('rating', 0.0)
|
||
|
||
# Проверяем уникальность ИНН
|
||
if inn:
|
||
existing = conn.execute(
|
||
'SELECT id FROM Clients WHERE inn = ? AND id != ?',
|
||
(inn, client_id)
|
||
).fetchone()
|
||
if existing:
|
||
flash('Клиент с таким ИНН уже существует', 'error')
|
||
conn.close()
|
||
return redirect(url_for('edit_client', client_id=client_id))
|
||
|
||
conn.execute('''
|
||
UPDATE Clients
|
||
SET name = ?, director = ?, email = ?, phone = ?,
|
||
address = ?, inn = ?, contractor_type_id = ?, rating = ?
|
||
WHERE id = ?
|
||
''', (name, director, email, phone, address, inn, contractor_type_id, rating, client_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_client', client_id=client_id))
|
||
|
||
# GET запрос
|
||
client_row = conn.execute('''
|
||
SELECT c.*, ct.type_name as contractor_type_name
|
||
FROM Clients c
|
||
LEFT JOIN ContractorType ct ON c.contractor_type_id = ct.id
|
||
WHERE c.id = ?
|
||
''', (client_id,)).fetchone()
|
||
|
||
client_dict = dict_from_row(client_row)
|
||
|
||
contractor_types = conn.execute('SELECT * FROM ContractorType ORDER BY type_name').fetchall()
|
||
conn.close()
|
||
|
||
if not client_dict:
|
||
flash('Клиент не найден', 'error')
|
||
return redirect(url_for('index'))
|
||
|
||
return render_template('edit_client.html', client=client_dict, contractor_types=contractor_types)
|
||
|
||
# ========== МАРШРУТЫ ДЛЯ ЗАКАЗОВ ==========
|
||
|
||
@app.route('/order_history/<int:client_id>')
|
||
def order_history(client_id):
|
||
conn = get_db_connection()
|
||
|
||
client_row = conn.execute('''
|
||
SELECT c.*, ct.type_name as contractor_type_name
|
||
FROM Clients c
|
||
LEFT JOIN ContractorType ct ON c.contractor_type_id = ct.id
|
||
WHERE c.id = ?
|
||
''', (client_id,)).fetchone()
|
||
|
||
client_dict = dict_from_row(client_row)
|
||
|
||
# Получаем заказы клиента
|
||
orders_rows = conn.execute('''
|
||
SELECT
|
||
o.*
|
||
FROM Orders o
|
||
WHERE o.client_id = ?
|
||
ORDER BY o.order_date DESC, o.id DESC
|
||
''', (client_id,)).fetchall()
|
||
|
||
# Получаем детальную информацию о товарах в каждом заказе
|
||
orders_with_items = []
|
||
total_quantity_all = 0
|
||
total_amount_all = 0
|
||
completed_count = 0
|
||
|
||
for order_row in orders_rows:
|
||
order_dict = dict_from_row(order_row)
|
||
|
||
# Получаем товары для этого заказа
|
||
items_rows = conn.execute('''
|
||
SELECT oi.*, cat.product_name, cat.article, cat.min_price, cat2.category_name
|
||
FROM OrderItems oi
|
||
JOIN Catalog cat ON oi.product_id = cat.id
|
||
LEFT JOIN Categories cat2 ON cat.category_id = cat2.id
|
||
WHERE oi.order_id = ?
|
||
''', (order_dict['id'],)).fetchall()
|
||
|
||
items_list = list_from_rows(items_rows)
|
||
order_dict['items'] = items_list
|
||
|
||
# Считаем общее количество для этого заказа
|
||
order_total_qty = sum(item['quantity'] for item in items_list) if items_list else 0
|
||
order_dict['total_items'] = order_total_qty
|
||
|
||
total_quantity_all += order_total_qty
|
||
total_amount_all += order_dict['total_amount']
|
||
|
||
if order_dict['status'] == 'Выполнен':
|
||
completed_count += 1
|
||
|
||
orders_with_items.append(order_dict)
|
||
|
||
# Статистика
|
||
total_stats = {
|
||
'total_orders': len(orders_with_items),
|
||
'total_quantity': total_quantity_all,
|
||
'total_amount': total_amount_all,
|
||
'completed_orders': completed_count
|
||
}
|
||
|
||
conn.close()
|
||
|
||
if not client_dict:
|
||
flash('Клиент не найден', 'error')
|
||
return redirect(url_for('index'))
|
||
|
||
return render_template('order_history.html',
|
||
client=client_dict,
|
||
orders=orders_with_items,
|
||
total_stats=total_stats)
|
||
|
||
# ========== МАРШРУТ ДЛЯ ОБНОВЛЕНИЯ СТАТУСА ЗАКАЗА ==========
|
||
@app.route('/update_order_status/<int:order_id>', methods=['POST'])
|
||
def update_order_status(order_id):
|
||
"""Обновление статуса заказа"""
|
||
try:
|
||
new_status = request.form['status']
|
||
|
||
conn = get_db_connection()
|
||
conn.execute('UPDATE Orders SET status = ? WHERE id = ?', (new_status, order_id))
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
flash(f'Статус заказа обновлен на "{new_status}"', 'success')
|
||
except Exception as e:
|
||
flash(f'Ошибка при обновлении статуса: {str(e)}', 'error')
|
||
|
||
return redirect(request.referrer or url_for('orders_list'))
|
||
|
||
# ========== МАРШРУТ ДЛЯ ОБНОВЛЕНИЯ СТАТУСА ПРОИЗВОДСТВА ==========
|
||
@app.route('/update_production_status/<int:order_id>', methods=['POST'])
|
||
def update_production_status(order_id):
|
||
"""Обновление статуса производства"""
|
||
try:
|
||
new_status = request.form['production_status']
|
||
conn = get_db_connection()
|
||
conn.execute('UPDATE Orders SET production_status = ? WHERE id = ?', (new_status, order_id))
|
||
conn.commit()
|
||
conn.close()
|
||
flash(f'Статус производства обновлен на "{new_status}"', 'success')
|
||
except Exception as e:
|
||
flash(f'Ошибка при обновлении статуса производства: {str(e)}', 'error')
|
||
return redirect(request.referrer)
|
||
|
||
# ========== МАРШРУТЫ ДЛЯ ТОВАРОВ ==========
|
||
|
||
@app.route('/add_product', methods=['GET', 'POST'])
|
||
def add_product():
|
||
if request.method == 'POST':
|
||
try:
|
||
product_name = request.form['product_name']
|
||
article = request.form['article']
|
||
category_id = request.form['category_id']
|
||
material_id = request.form.get('material_id') or None
|
||
description = request.form.get('description', '')
|
||
min_price = float(request.form['min_price'])
|
||
quantity_in_stock = int(request.form.get('quantity_in_stock', 0))
|
||
production_time_days = int(request.form.get('production_time_days', 1))
|
||
|
||
conn = get_db_connection()
|
||
|
||
existing = conn.execute('SELECT id FROM Catalog WHERE article = ?', (article,)).fetchone()
|
||
if existing:
|
||
flash('Товар с таким артикулом уже существует', 'error')
|
||
conn.close()
|
||
return redirect(url_for('add_product'))
|
||
|
||
conn.execute('''
|
||
INSERT INTO Catalog
|
||
(product_name, article, category_id, material_id, description, min_price,
|
||
quantity_in_stock, production_time_days)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||
''', (product_name, article, category_id, material_id, description, min_price,
|
||
quantity_in_stock, production_time_days))
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
flash('Товар успешно добавлен!', 'success')
|
||
return redirect(url_for('catalog'))
|
||
|
||
except Exception as e:
|
||
flash(f'Ошибка: {str(e)}', 'error')
|
||
return redirect(url_for('add_product'))
|
||
|
||
conn = get_db_connection()
|
||
categories = conn.execute('SELECT * FROM Categories ORDER BY category_name').fetchall()
|
||
materials = conn.execute('SELECT * FROM Materials ORDER BY material_name').fetchall()
|
||
conn.close()
|
||
|
||
return render_template('add_product.html', categories=categories, materials=materials)
|
||
|
||
@app.route('/edit_product/<int:product_id>', methods=['GET', 'POST'])
|
||
def edit_product(product_id):
|
||
conn = get_db_connection()
|
||
|
||
if request.method == 'POST':
|
||
try:
|
||
product_name = request.form['product_name']
|
||
article = request.form['article']
|
||
category_id = request.form['category_id']
|
||
material_id = request.form.get('material_id') or None
|
||
description = request.form.get('description', '')
|
||
min_price = float(request.form['min_price'])
|
||
quantity_in_stock = int(request.form.get('quantity_in_stock', 0))
|
||
production_time_days = int(request.form.get('production_time_days', 1))
|
||
|
||
existing = conn.execute(
|
||
'SELECT id FROM Catalog WHERE article = ? AND id != ?',
|
||
(article, product_id)
|
||
).fetchone()
|
||
if existing:
|
||
flash('Товар с таким артикулом уже существует', 'error')
|
||
conn.close()
|
||
return redirect(url_for('edit_product', product_id=product_id))
|
||
|
||
conn.execute('''
|
||
UPDATE Catalog
|
||
SET product_name = ?, article = ?, category_id = ?, material_id = ?,
|
||
description = ?, min_price = ?, quantity_in_stock = ?,
|
||
production_time_days = ?
|
||
WHERE id = ?
|
||
''', (product_name, article, category_id, material_id, description, min_price,
|
||
quantity_in_stock, production_time_days, product_id))
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
flash('Товар успешно обновлен!', 'success')
|
||
return redirect(url_for('catalog'))
|
||
|
||
except Exception as e:
|
||
flash(f'Ошибка: {str(e)}', 'error')
|
||
return redirect(url_for('edit_product', product_id=product_id))
|
||
|
||
product_row = conn.execute('''
|
||
SELECT cat.*, c.category_name, m.material_name
|
||
FROM Catalog cat
|
||
LEFT JOIN Categories c ON cat.category_id = c.id
|
||
LEFT JOIN Materials m ON cat.material_id = m.id
|
||
WHERE cat.id = ?
|
||
''', (product_id,)).fetchone()
|
||
|
||
product_dict = dict_from_row(product_row)
|
||
|
||
categories = conn.execute('SELECT * FROM Categories ORDER BY category_name').fetchall()
|
||
materials = conn.execute('SELECT * FROM Materials ORDER BY material_name').fetchall()
|
||
conn.close()
|
||
|
||
if not product_dict:
|
||
flash('Товар не найден', 'error')
|
||
return redirect(url_for('catalog'))
|
||
|
||
return render_template('edit_product.html',
|
||
product=product_dict,
|
||
categories=categories,
|
||
materials=materials)
|
||
|
||
@app.route('/delete_product/<int:product_id>', methods=['POST'])
|
||
def delete_product(product_id):
|
||
try:
|
||
conn = get_db_connection()
|
||
|
||
order_items = conn.execute('SELECT COUNT(*) as count FROM OrderItems WHERE product_id = ?', (product_id,)).fetchone()
|
||
if order_items['count'] > 0:
|
||
flash('Нельзя удалить товар, по которому есть заказы', 'error')
|
||
conn.close()
|
||
return redirect(url_for('catalog'))
|
||
|
||
conn.execute('DELETE FROM Catalog WHERE id = ?', (product_id,))
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
flash('Товар успешно удален', 'success')
|
||
except Exception as e:
|
||
flash(f'Ошибка при удалении: {str(e)}', 'error')
|
||
|
||
return redirect(url_for('catalog'))
|
||
|
||
@app.route('/catalog')
|
||
def catalog():
|
||
conn = get_db_connection()
|
||
|
||
categories = conn.execute('SELECT * FROM Categories ORDER BY category_name').fetchall()
|
||
# Получаем список всех клиентов для выпадающего списка
|
||
clients_rows = conn.execute('SELECT id, name FROM Clients ORDER BY name').fetchall()
|
||
clients = list_from_rows(clients_rows)
|
||
|
||
search_query = request.args.get('search', '')
|
||
category_id = request.args.get('category', 'all')
|
||
sort_by = request.args.get('sort', 'name_asc')
|
||
|
||
sql = '''
|
||
SELECT cat.*, c.category_name, m.material_name, m.color,
|
||
m.production_time_days, m.daily_capacity
|
||
FROM Catalog cat
|
||
LEFT JOIN Categories c ON cat.category_id = c.id
|
||
LEFT JOIN Materials m ON cat.material_id = m.id
|
||
WHERE 1=1
|
||
'''
|
||
params = []
|
||
|
||
if search_query:
|
||
sql += " AND (cat.product_name LIKE ? OR cat.article LIKE ?)"
|
||
params.extend([f'%{search_query}%', f'%{search_query}%'])
|
||
|
||
if category_id != 'all':
|
||
sql += " AND cat.category_id = ?"
|
||
params.append(category_id)
|
||
|
||
if sort_by == 'name_asc':
|
||
sql += " ORDER BY cat.product_name ASC"
|
||
elif sort_by == 'name_desc':
|
||
sql += " ORDER BY cat.product_name DESC"
|
||
elif sort_by == 'price_asc':
|
||
sql += " ORDER BY cat.min_price ASC"
|
||
elif sort_by == 'price_desc':
|
||
sql += " ORDER BY cat.min_price DESC"
|
||
elif sort_by == 'stock_desc':
|
||
sql += " ORDER BY cat.quantity_in_stock DESC"
|
||
else:
|
||
sql += " ORDER BY cat.product_name ASC"
|
||
|
||
products_rows = conn.execute(sql, params).fetchall()
|
||
products = list_from_rows(products_rows)
|
||
|
||
cart = session.get('cart', {})
|
||
cart_count = sum(item['quantity'] for item in cart.values()) if cart else 0
|
||
|
||
conn.close()
|
||
|
||
return render_template('catalog.html',
|
||
products=products,
|
||
categories=categories,
|
||
clients=clients,
|
||
cart_count=cart_count)
|
||
|
||
@app.route('/product/<int:product_id>')
|
||
def product_detail(product_id):
|
||
conn = get_db_connection()
|
||
|
||
product_row = conn.execute('''
|
||
SELECT cat.*, c.category_name, m.material_name,
|
||
m.price_per_unit, m.unit, m.in_stock as material_stock,
|
||
m.daily_capacity as material_capacity,
|
||
m.production_time_days as material_production_time,
|
||
m.cost_price as material_cost_price
|
||
FROM Catalog cat
|
||
LEFT JOIN Categories c ON cat.category_id = c.id
|
||
LEFT JOIN Materials m ON cat.material_id = m.id
|
||
WHERE cat.id = ?
|
||
''', (product_id,)).fetchone()
|
||
|
||
product_dict = dict_from_row(product_row)
|
||
|
||
material = None
|
||
if product_dict and product_dict.get('material_id'):
|
||
material_row = conn.execute('SELECT * FROM Materials WHERE id = ?', (product_dict['material_id'],)).fetchone()
|
||
material = dict_from_row(material_row)
|
||
|
||
orders_stats_row = conn.execute('''
|
||
SELECT
|
||
COUNT(DISTINCT oi.order_id) as total_orders,
|
||
SUM(oi.quantity) as total_sold,
|
||
AVG(oi.quantity) as avg_order_size
|
||
FROM OrderItems oi
|
||
WHERE oi.product_id = ?
|
||
''', (product_id,)).fetchone()
|
||
|
||
orders_stats_dict = dict_from_row(orders_stats_row)
|
||
|
||
conn.close()
|
||
|
||
if not product_dict:
|
||
flash('Товар не найден', 'error')
|
||
return redirect(url_for('catalog'))
|
||
|
||
return render_template('product_detail.html',
|
||
product=product_dict,
|
||
material=material,
|
||
orders_stats=orders_stats_dict)
|
||
|
||
# ========== МАРШРУТЫ ДЛЯ КОРЗИНЫ ==========
|
||
|
||
@app.route('/cart')
|
||
def view_cart():
|
||
cart = session.get('cart', {})
|
||
cart_items = []
|
||
total_amount = 0
|
||
|
||
conn = get_db_connection()
|
||
for key, item_data in cart.items():
|
||
product_id = item_data['product_id']
|
||
quantity = item_data['quantity']
|
||
client_id = item_data['client_id']
|
||
|
||
# Получаем информацию о товаре
|
||
product_row = conn.execute('''
|
||
SELECT cat.*, c.category_name, m.material_name, cat.quantity_in_stock
|
||
FROM Catalog cat
|
||
LEFT JOIN Categories c ON cat.category_id = c.id
|
||
LEFT JOIN Materials m ON cat.material_id = m.id
|
||
WHERE cat.id = ?
|
||
''', (product_id,)).fetchone()
|
||
|
||
# Получаем информацию о партнере
|
||
client_row = conn.execute('SELECT name FROM Clients WHERE id = ?', (client_id,)).fetchone()
|
||
client_name = client_row['name'] if client_row else 'Неизвестный'
|
||
|
||
if product_row:
|
||
product = dict_from_row(product_row)
|
||
cart_items.append({
|
||
'id': key,
|
||
'product_id': product_id,
|
||
'product_name': product['product_name'],
|
||
'article': product['article'],
|
||
'category_name': product['category_name'],
|
||
'material_name': product['material_name'],
|
||
'client_id': client_id,
|
||
'client_name': client_name,
|
||
'price': product['min_price'],
|
||
'quantity': quantity,
|
||
'in_stock': product['quantity_in_stock'] or 0
|
||
})
|
||
total_amount += product['min_price'] * quantity
|
||
|
||
conn.close()
|
||
|
||
return render_template('cart_new.html', cart_items=cart_items, total_amount=total_amount)
|
||
|
||
@app.route('/add_to_cart/<int:product_id>', methods=['POST'])
|
||
def add_to_cart(product_id):
|
||
quantity = int(request.form.get('quantity', 1))
|
||
client_id = request.form.get('client_id')
|
||
|
||
if not client_id:
|
||
flash('Выберите партнера', 'error')
|
||
return redirect(url_for('catalog'))
|
||
|
||
cart = session.get('cart', {})
|
||
|
||
# Проверяем, что все товары в корзине от одного партнера
|
||
if cart:
|
||
existing_client = next(iter(cart.values()))['client_id']
|
||
if existing_client != int(client_id):
|
||
flash('Нельзя добавлять товары разных партнеров в одну корзину', 'error')
|
||
return redirect(url_for('catalog'))
|
||
|
||
# Создаем уникальный ключ для комбинации товар + партнер
|
||
key = f"{product_id}_{client_id}"
|
||
|
||
if key in cart:
|
||
cart[key]['quantity'] += quantity
|
||
else:
|
||
cart[key] = {
|
||
'product_id': product_id,
|
||
'client_id': int(client_id),
|
||
'quantity': quantity
|
||
}
|
||
|
||
session['cart'] = cart
|
||
flash('Товар добавлен в корзину', 'success')
|
||
return redirect(url_for('catalog'))
|
||
|
||
@app.route('/update_cart/<path:item_id>', methods=['POST'])
|
||
def update_cart(item_id):
|
||
quantity = int(request.form.get('quantity', 1))
|
||
|
||
cart = session.get('cart', {})
|
||
|
||
if quantity > 0 and item_id in cart:
|
||
cart[item_id]['quantity'] = quantity
|
||
elif quantity <= 0 and item_id in cart:
|
||
cart.pop(item_id, None)
|
||
|
||
session['cart'] = cart
|
||
|
||
return ('', 204)
|
||
|
||
@app.route('/remove_from_cart/<path:item_id>', methods=['POST'])
|
||
def remove_from_cart(item_id):
|
||
cart = session.get('cart', {})
|
||
cart.pop(item_id, None)
|
||
session['cart'] = cart
|
||
|
||
return redirect(url_for('view_cart'))
|
||
|
||
# ========== МАРШРУТ ДЛЯ ОФОРМЛЕНИЯ ЗАКАЗА ==========
|
||
|
||
@app.route('/checkout', methods=['POST'])
|
||
def checkout():
|
||
cart = session.get('cart', {})
|
||
if not cart:
|
||
flash('Корзина пуста', 'error')
|
||
return redirect(url_for('catalog'))
|
||
|
||
conn = get_db_connection()
|
||
|
||
# Проверяем, что все товары от одного партнера
|
||
client_ids = set(item_data['client_id'] for item_data in cart.values())
|
||
|
||
if len(client_ids) > 1:
|
||
flash('Ошибка: в корзине товары от разных партнеров', 'error')
|
||
conn.close()
|
||
return redirect(url_for('view_cart'))
|
||
|
||
client_id = list(client_ids)[0] if client_ids else None
|
||
|
||
# Собираем все товары
|
||
items = []
|
||
subtotal = 0
|
||
|
||
for key, item_data in cart.items():
|
||
product_id = item_data['product_id']
|
||
quantity = item_data['quantity']
|
||
|
||
product_row = conn.execute('SELECT * FROM Catalog WHERE id = ?', (product_id,)).fetchone()
|
||
if not product_row:
|
||
flash(f'Товар с ID {product_id} не найден', 'error')
|
||
conn.close()
|
||
return redirect(url_for('view_cart'))
|
||
|
||
product = dict_from_row(product_row)
|
||
|
||
if product['quantity_in_stock'] < quantity:
|
||
flash(f'Недостаточно товара "{product["product_name"]}" на складе', 'error')
|
||
conn.close()
|
||
return redirect(url_for('view_cart'))
|
||
|
||
items.append({
|
||
'product': product,
|
||
'quantity': quantity
|
||
})
|
||
subtotal += product['min_price'] * quantity
|
||
|
||
try:
|
||
# Рассчитываем наценку 15% + 10% = 25%
|
||
assembly_percent = 15
|
||
delivery_percent = 10
|
||
assembly_cost = subtotal * assembly_percent / 100
|
||
delivery_cost = subtotal * delivery_percent / 100
|
||
total_amount = subtotal + assembly_cost + delivery_cost
|
||
|
||
# Срок изготовления (максимальный из всех товаров)
|
||
production_days = max(item['product']['production_time_days'] or 1 for item in items)
|
||
today = datetime.now().strftime('%Y-%m-%d')
|
||
end_date = (datetime.now() + timedelta(days=production_days)).strftime('%Y-%m-%d')
|
||
|
||
# Создаем ОДИН заказ
|
||
cursor = conn.execute('''
|
||
INSERT INTO Orders (
|
||
client_id, order_date, total_amount, status,
|
||
production_days, production_start_date, production_end_date, production_status,
|
||
assembly_percent, delivery_percent, assembly_cost, delivery_cost, base_amount
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
''', (
|
||
client_id, today, total_amount, 'Новый',
|
||
production_days, today, end_date, 'Ожидает',
|
||
assembly_percent, delivery_percent, assembly_cost, delivery_cost, subtotal
|
||
))
|
||
|
||
order_id = cursor.lastrowid
|
||
|
||
# Добавляем все товары в OrderItems
|
||
for item in items:
|
||
product = item['product']
|
||
quantity = item['quantity']
|
||
|
||
conn.execute('''
|
||
INSERT INTO OrderItems (order_id, product_id, quantity, price_at_moment)
|
||
VALUES (?, ?, ?, ?)
|
||
''', (order_id, product['id'], quantity, product['min_price']))
|
||
|
||
conn.execute('''
|
||
UPDATE Catalog
|
||
SET quantity_in_stock = quantity_in_stock - ?
|
||
WHERE id = ?
|
||
''', (quantity, product['id']))
|
||
|
||
conn.commit()
|
||
session.pop('cart', None)
|
||
flash(f'✅ Заказ успешно оформлен! Сумма: {total_amount:.2f} руб.', 'success')
|
||
|
||
except Exception as e:
|
||
conn.rollback()
|
||
flash(f'Ошибка при создании заказа: {str(e)}', 'error')
|
||
|
||
conn.close()
|
||
return redirect(url_for('orders_list'))
|
||
|
||
# ========== МАРШРУТ ДЛЯ СПИСКА ВСЕХ ЗАКАЗОВ ==========
|
||
|
||
@app.route('/orders_list')
|
||
def orders_list():
|
||
"""Список всех заказов"""
|
||
conn = get_db_connection()
|
||
|
||
clients_rows = conn.execute('SELECT id, name FROM Clients ORDER BY name').fetchall()
|
||
clients_list = list_from_rows(clients_rows)
|
||
|
||
# Получаем заказы с основной информацией
|
||
orders_rows = conn.execute('''
|
||
SELECT
|
||
o.*,
|
||
c.name as client_name,
|
||
c.inn as client_inn,
|
||
COUNT(oi.id) as items_count,
|
||
COALESCE(SUM(oi.quantity), 0) as total_items
|
||
FROM Orders o
|
||
LEFT JOIN Clients c ON o.client_id = c.id
|
||
LEFT JOIN OrderItems oi ON o.id = oi.order_id
|
||
GROUP BY o.id
|
||
ORDER BY o.order_date DESC, o.id DESC
|
||
LIMIT 100
|
||
''').fetchall()
|
||
|
||
# Для каждого заказа получаем список товаров
|
||
orders_with_items = []
|
||
for order_row in orders_rows:
|
||
order_dict = dict_from_row(order_row)
|
||
|
||
# Получаем товары для этого заказа
|
||
items_rows = conn.execute('''
|
||
SELECT oi.*, cat.product_name, cat.article, cat.min_price, cat.category_id,
|
||
cat2.category_name
|
||
FROM OrderItems oi
|
||
JOIN Catalog cat ON oi.product_id = cat.id
|
||
LEFT JOIN Categories cat2 ON cat.category_id = cat2.id
|
||
WHERE oi.order_id = ?
|
||
''', (order_dict['id'],)).fetchall()
|
||
|
||
order_dict['items'] = list_from_rows(items_rows)
|
||
orders_with_items.append(order_dict)
|
||
|
||
# Считаем общую статистику
|
||
total_stats_row = conn.execute('''
|
||
SELECT
|
||
COUNT(DISTINCT o.id) as total_orders,
|
||
COALESCE(SUM(oi.quantity), 0) as total_quantity,
|
||
COALESCE(SUM(o.total_amount), 0) as total_amount
|
||
FROM Orders o
|
||
LEFT JOIN OrderItems oi ON o.id = oi.order_id
|
||
''').fetchone()
|
||
|
||
total_stats = dict_from_row(total_stats_row)
|
||
|
||
conn.close()
|
||
|
||
return render_template('orders_list.html',
|
||
orders=orders_with_items,
|
||
total_stats=total_stats,
|
||
clients=clients_list)
|
||
|
||
# ========== МАРШРУТ ДЛЯ МАТЕРИАЛОВ ==========
|
||
|
||
@app.route('/materials')
|
||
def materials():
|
||
conn = get_db_connection()
|
||
|
||
materials_rows = conn.execute('''
|
||
SELECT * FROM Materials
|
||
ORDER BY material_name
|
||
''').fetchall()
|
||
|
||
materials_list = list_from_rows(materials_rows)
|
||
conn.close()
|
||
|
||
return render_template('materials.html', materials=materials_list)
|
||
|
||
# ========== СЛУЖЕБНЫЕ МАРШРУТЫ ==========
|
||
|
||
@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/<path:filename>')
|
||
def static_files(filename):
|
||
return send_from_directory(app.root_path + '/static', filename)
|
||
|
||
if __name__ == '__main__':
|
||
app.run(debug=True) |