20260420
This commit is contained in:
commit
5f22af0ec0
3
.idea/.gitignore
vendored
Normal file
3
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
4
.idea/misc.xml
Normal file
4
.idea/misc.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (zxc)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/project.iml" filepath="$PROJECT_DIR$/.idea/project.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/project.iml
Normal file
8
.idea/project.iml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
293
app.py
Normal file
293
app.py
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
from flask import Flask, render_template, request, redirect, url_for, flash
|
||||
from database import db
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = 'your-secret-key-2026'
|
||||
|
||||
# Функция валидации телефона
|
||||
def validate_phone(phone):
|
||||
phone_clean = re.sub(r'[^\d+]', '', phone)
|
||||
|
||||
if phone_clean.startswith('+7') and len(phone_clean) == 12:
|
||||
return True
|
||||
elif phone_clean.startswith('8') and len(phone_clean) == 11:
|
||||
return True
|
||||
elif len(phone_clean) == 10 and phone_clean.isdigit():
|
||||
return True
|
||||
elif len(phone_clean) == 11 and phone_clean.isdigit():
|
||||
return True
|
||||
return False
|
||||
|
||||
def format_phone(phone):
|
||||
phone_clean = re.sub(r'[^\d+]', '', phone)
|
||||
|
||||
if phone_clean.startswith('+7') and len(phone_clean) == 12:
|
||||
return f"+7 ({phone_clean[2:5]}) {phone_clean[5:8]}-{phone_clean[8:10]}-{phone_clean[10:12]}"
|
||||
elif phone_clean.startswith('8') and len(phone_clean) == 11:
|
||||
return f"+7 ({phone_clean[1:4]}) {phone_clean[4:7]}-{phone_clean[7:9]}-{phone_clean[9:11]}"
|
||||
elif len(phone_clean) == 10:
|
||||
return f"+7 ({phone_clean[0:3]}) {phone_clean[3:6]}-{phone_clean[6:8]}-{phone_clean[8:10]}"
|
||||
elif len(phone_clean) == 11:
|
||||
return f"+7 ({phone_clean[1:4]}) {phone_clean[4:7]}-{phone_clean[7:9]}-{phone_clean[9:11]}"
|
||||
return phone
|
||||
|
||||
# Функция расчета коэффициента срока годности
|
||||
def get_expiry_coefficient(expiry_date):
|
||||
"""
|
||||
Расчет коэффициента скидки в зависимости от срока годности
|
||||
< 30 дней -> 50% скидка
|
||||
30-90 дней -> 30% скидка
|
||||
90-180 дней -> 15% скидка
|
||||
> 180 дней -> 0% скидка
|
||||
"""
|
||||
if not expiry_date:
|
||||
return 0
|
||||
|
||||
try:
|
||||
# Преобразуем строку в дату (формат дд.мм.гггг)
|
||||
expiry = datetime.strptime(expiry_date, '%d.%m.%Y')
|
||||
today = datetime.now()
|
||||
|
||||
days_until_expiry = (expiry - today).days
|
||||
|
||||
if days_until_expiry < 0:
|
||||
return 100 # Просрочен - 100% скидка (не продаем)
|
||||
elif days_until_expiry < 30:
|
||||
return 50 # 50% скидка
|
||||
elif days_until_expiry < 90:
|
||||
return 30 # 30% скидка
|
||||
elif days_until_expiry < 180:
|
||||
return 15 # 15% скидка
|
||||
else:
|
||||
return 0
|
||||
except:
|
||||
return 0
|
||||
|
||||
# Функция расчета итоговой цены с учетом скидки и срока годности
|
||||
def calculate_final_price(price, quantity, expiry_date, quantity_discount_percent=0):
|
||||
"""
|
||||
Расчет итоговой цены:
|
||||
1. Базовая сумма = цена * количество
|
||||
2. Скидка по сроку годности
|
||||
3. Скидка по количеству
|
||||
"""
|
||||
total = price * quantity
|
||||
|
||||
# Скидка по сроку годности
|
||||
expiry_discount_percent = get_expiry_coefficient(expiry_date)
|
||||
expiry_discount_amount = total * expiry_discount_percent / 100
|
||||
|
||||
# Скидка по количеству
|
||||
quantity_discount_amount = total * quantity_discount_percent / 100
|
||||
|
||||
# Общая скидка
|
||||
total_discount = expiry_discount_amount + quantity_discount_amount
|
||||
final_total = total - total_discount
|
||||
|
||||
return {
|
||||
'total': total,
|
||||
'expiry_discount_percent': expiry_discount_percent,
|
||||
'expiry_discount_amount': expiry_discount_amount,
|
||||
'quantity_discount_percent': quantity_discount_percent,
|
||||
'quantity_discount_amount': quantity_discount_amount,
|
||||
'total_discount': total_discount,
|
||||
'final_total': final_total
|
||||
}
|
||||
|
||||
# ============ ГЛАВНАЯ ============
|
||||
@app.route('/')
|
||||
def index():
|
||||
products = db.get_products()
|
||||
total_items = sum(p.get('stock_quantity', 0) for p in products)
|
||||
total_value = sum(p.get('stock_quantity', 0) * p.get('selling_price', 0) for p in products)
|
||||
return render_template('index.html', total_items=total_items, total_value=total_value)
|
||||
|
||||
# ============ ТОВАРЫ ============
|
||||
@app.route('/products', methods=['GET', 'POST'])
|
||||
def products():
|
||||
if request.method == 'POST':
|
||||
product = {
|
||||
'name': request.form.get('name'),
|
||||
'manufacturer': request.form.get('manufacturer'),
|
||||
'product_type': request.form.get('product_type'),
|
||||
'purchase_price': float(request.form.get('purchase_price', 0)),
|
||||
'selling_price': float(request.form.get('selling_price', 0)),
|
||||
'expiry_date': request.form.get('expiry_date', ''),
|
||||
'stock_quantity': int(request.form.get('stock_quantity', 0))
|
||||
}
|
||||
db.save_product(product)
|
||||
flash('Товар добавлен', 'success')
|
||||
return redirect(url_for('products'))
|
||||
|
||||
products = db.get_products()
|
||||
categories = db.get_categories()
|
||||
return render_template('products.html', products=products, categories=categories)
|
||||
|
||||
@app.route('/products/delete/<int:product_id>')
|
||||
def delete_product(product_id):
|
||||
db.delete_product(product_id)
|
||||
flash('Товар удален', 'warning')
|
||||
return redirect(url_for('products'))
|
||||
|
||||
@app.route('/products/edit/<int:product_id>', methods=['GET', 'POST'])
|
||||
def edit_product(product_id):
|
||||
product = db.get_product_by_id(product_id)
|
||||
if not product:
|
||||
flash('Товар не найден', 'danger')
|
||||
return redirect(url_for('products'))
|
||||
|
||||
if request.method == 'POST':
|
||||
updated_data = {
|
||||
'name': request.form.get('name'),
|
||||
'manufacturer': request.form.get('manufacturer'),
|
||||
'product_type': request.form.get('product_type'),
|
||||
'purchase_price': float(request.form.get('purchase_price', 0)),
|
||||
'selling_price': float(request.form.get('selling_price', 0)),
|
||||
'expiry_date': request.form.get('expiry_date', ''),
|
||||
'stock_quantity': int(request.form.get('stock_quantity', 0))
|
||||
}
|
||||
db.update_product(product_id, updated_data)
|
||||
flash('Товар обновлен', 'success')
|
||||
return redirect(url_for('products'))
|
||||
|
||||
return render_template('edit_product.html', product=product)
|
||||
|
||||
# ============ ПОСТАВЩИКИ ============
|
||||
@app.route('/suppliers', methods=['GET', 'POST'])
|
||||
def suppliers():
|
||||
if request.method == 'POST':
|
||||
name = request.form.get('name')
|
||||
contact = request.form.get('contact')
|
||||
phone = request.form.get('phone')
|
||||
|
||||
if phone and not validate_phone(phone):
|
||||
flash('Неверный формат телефона', 'danger')
|
||||
suppliers = db.get_suppliers()
|
||||
return render_template('suppliers.html', suppliers=suppliers)
|
||||
|
||||
supplier = {
|
||||
'name': name,
|
||||
'contact': contact,
|
||||
'phone': format_phone(phone) if phone else ''
|
||||
}
|
||||
db.save_supplier(supplier)
|
||||
flash('Поставщик добавлен', 'success')
|
||||
return redirect(url_for('suppliers'))
|
||||
|
||||
suppliers = db.get_suppliers()
|
||||
return render_template('suppliers.html', suppliers=suppliers)
|
||||
|
||||
@app.route('/suppliers/edit/<int:supplier_id>', methods=['GET', 'POST'])
|
||||
def edit_supplier(supplier_id):
|
||||
supplier = db.get_supplier_by_id(supplier_id)
|
||||
if not supplier:
|
||||
flash('Поставщик не найден', 'danger')
|
||||
return redirect(url_for('suppliers'))
|
||||
|
||||
if request.method == 'POST':
|
||||
name = request.form.get('name')
|
||||
contact = request.form.get('contact')
|
||||
phone = request.form.get('phone')
|
||||
|
||||
if phone and not validate_phone(phone):
|
||||
flash('Неверный формат телефона', 'danger')
|
||||
return render_template('edit_supplier.html', supplier=supplier)
|
||||
|
||||
updated_data = {
|
||||
'name': name,
|
||||
'contact': contact,
|
||||
'phone': format_phone(phone) if phone else ''
|
||||
}
|
||||
db.update_supplier(supplier_id, updated_data)
|
||||
flash('Поставщик обновлен', 'success')
|
||||
return redirect(url_for('suppliers'))
|
||||
|
||||
return render_template('edit_supplier.html', supplier=supplier)
|
||||
|
||||
@app.route('/supplier/orders/<int:supplier_id>')
|
||||
def supplier_orders(supplier_id):
|
||||
supplier = db.get_supplier_by_id(supplier_id)
|
||||
if not supplier:
|
||||
flash('Поставщик не найден', 'danger')
|
||||
return redirect(url_for('suppliers'))
|
||||
|
||||
all_orders = db.get_incoming_orders()
|
||||
supplier_orders = [o for o in all_orders if o.get('supplier_id') == supplier_id]
|
||||
|
||||
total_sum = sum(o.get('total', 0) for o in supplier_orders)
|
||||
total_items = sum(o.get('quantity', 0) for o in supplier_orders)
|
||||
|
||||
return render_template('supplier_orders.html',
|
||||
supplier=supplier,
|
||||
orders=supplier_orders,
|
||||
total_sum=total_sum,
|
||||
total_items=total_items)
|
||||
|
||||
# ============ ПОСТУПЛЕНИЕ ТОВАРА (С УЧЕТОМ СРОКА ГОДНОСТИ) ============
|
||||
@app.route('/incoming', methods=['GET', 'POST'])
|
||||
def incoming():
|
||||
if request.method == 'POST':
|
||||
product_id = int(request.form.get('product_id'))
|
||||
supplier_id = int(request.form.get('supplier_id'))
|
||||
quantity = int(request.form.get('quantity'))
|
||||
purchase_price = float(request.form.get('purchase_price'))
|
||||
|
||||
product = db.get_product_by_id(product_id)
|
||||
supplier = db.get_supplier_by_id(supplier_id)
|
||||
|
||||
if product and supplier:
|
||||
# Получаем срок годности товара
|
||||
expiry_date = product.get('expiry_date', '')
|
||||
|
||||
# Рассчитываем коэффициенты
|
||||
expiry_discount_percent = get_expiry_coefficient(expiry_date)
|
||||
|
||||
# Базовая сумма
|
||||
total = quantity * purchase_price
|
||||
|
||||
# Скидка по сроку годности
|
||||
discount_amount = total * expiry_discount_percent / 100
|
||||
final_total = total - discount_amount
|
||||
|
||||
incoming_order = {
|
||||
'supplier_id': supplier_id,
|
||||
'supplier_name': supplier['name'],
|
||||
'product_id': product_id,
|
||||
'product_name': product['name'],
|
||||
'expiry_date': expiry_date,
|
||||
'quantity': quantity,
|
||||
'purchase_price': purchase_price,
|
||||
'total': total,
|
||||
'expiry_discount_percent': expiry_discount_percent,
|
||||
'discount_amount': discount_amount,
|
||||
'final_total': final_total
|
||||
}
|
||||
db.save_incoming_order(incoming_order)
|
||||
db.update_stock(product_id, quantity)
|
||||
|
||||
if expiry_discount_percent > 0:
|
||||
flash(f'Товар "{product["name"]}" поступил на склад. Применена скидка {expiry_discount_percent}% по сроку годности!', 'success')
|
||||
else:
|
||||
flash(f'Товар "{product["name"]}" поступил на склад в количестве {quantity} шт.', 'success')
|
||||
else:
|
||||
flash('Ошибка при оформлении поступления', 'danger')
|
||||
return redirect(url_for('incoming'))
|
||||
|
||||
products = db.get_products()
|
||||
suppliers = db.get_suppliers()
|
||||
return render_template('incoming.html', products=products, suppliers=suppliers)
|
||||
|
||||
# ============ ПРИХОДНЫЕ НАКЛАДНЫЕ ============
|
||||
@app.route('/incoming-orders')
|
||||
def incoming_orders():
|
||||
sort_by = request.args.get('sort', 'date')
|
||||
order = request.args.get('order', 'desc')
|
||||
orders = db.get_incoming_orders(sort_by=sort_by, order=order)
|
||||
return render_template('incoming_orders.html', orders=orders, sort_by=sort_by, order=order)
|
||||
|
||||
# ============ ЗАПУСК ============
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
6
categories.json
Normal file
6
categories.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[
|
||||
{"id":1,"name":"Фотобумага"},
|
||||
{"id":2,"name":"Чернила"},
|
||||
{"id":3,"name":"Фотопленка"},
|
||||
{"id":4,"name":"Аксессуары"}
|
||||
]
|
||||
2
create_tables.py
Normal file
2
create_tables.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Flask==2.3.3
|
||||
Flask-SQLAlchemy==3.1.1
|
||||
140
database.py
Normal file
140
database.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
class SimpleDB:
|
||||
def __init__(self):
|
||||
self.products_file = 'products.json'
|
||||
self.suppliers_file = 'suppliers.json'
|
||||
self.categories_file = 'categories.json'
|
||||
self.incoming_file = 'incoming_orders.json'
|
||||
self._init_files()
|
||||
|
||||
def _init_files(self):
|
||||
files = [self.products_file, self.suppliers_file, self.categories_file, self.incoming_file]
|
||||
for file in files:
|
||||
if not os.path.exists(file):
|
||||
with open(file, 'w', encoding='utf-8') as f:
|
||||
json.dump([], f)
|
||||
|
||||
# ==================== ТОВАРЫ ====================
|
||||
def get_products(self):
|
||||
try:
|
||||
with open(self.products_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
return []
|
||||
|
||||
def get_product_by_id(self, product_id):
|
||||
products = self.get_products()
|
||||
for product in products:
|
||||
if product.get('id') == product_id:
|
||||
return product
|
||||
return None
|
||||
|
||||
def save_product(self, product):
|
||||
products = self.get_products()
|
||||
product['id'] = len(products) + 1
|
||||
products.append(product)
|
||||
with open(self.products_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(products, f, ensure_ascii=False, indent=2)
|
||||
return product
|
||||
|
||||
def update_product(self, product_id, updated_data):
|
||||
products = self.get_products()
|
||||
for i, product in enumerate(products):
|
||||
if product.get('id') == product_id:
|
||||
products[i].update(updated_data)
|
||||
break
|
||||
with open(self.products_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(products, f, ensure_ascii=False, indent=2)
|
||||
return True
|
||||
|
||||
def delete_product(self, product_id):
|
||||
products = self.get_products()
|
||||
products = [p for p in products if p.get('id') != product_id]
|
||||
with open(self.products_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(products, f, ensure_ascii=False, indent=2)
|
||||
|
||||
def update_stock(self, product_id, quantity_change):
|
||||
products = self.get_products()
|
||||
for product in products:
|
||||
if product.get('id') == product_id:
|
||||
product['stock_quantity'] = product.get('stock_quantity', 0) + quantity_change
|
||||
break
|
||||
with open(self.products_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(products, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# ==================== ПОСТАВЩИКИ ====================
|
||||
def get_suppliers(self):
|
||||
try:
|
||||
with open(self.suppliers_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
return []
|
||||
|
||||
def get_supplier_by_id(self, supplier_id):
|
||||
suppliers = self.get_suppliers()
|
||||
for supplier in suppliers:
|
||||
if supplier.get('id') == supplier_id:
|
||||
return supplier
|
||||
return None
|
||||
|
||||
def save_supplier(self, supplier):
|
||||
suppliers = self.get_suppliers()
|
||||
supplier['id'] = len(suppliers) + 1
|
||||
suppliers.append(supplier)
|
||||
with open(self.suppliers_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(suppliers, f, ensure_ascii=False, indent=2)
|
||||
return supplier
|
||||
|
||||
def update_supplier(self, supplier_id, updated_data):
|
||||
suppliers = self.get_suppliers()
|
||||
for i, supplier in enumerate(suppliers):
|
||||
if supplier.get('id') == supplier_id:
|
||||
suppliers[i].update(updated_data)
|
||||
break
|
||||
with open(self.suppliers_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(suppliers, f, ensure_ascii=False, indent=2)
|
||||
return True
|
||||
|
||||
def delete_supplier(self, supplier_id):
|
||||
suppliers = self.get_suppliers()
|
||||
suppliers = [s for s in suppliers if s.get('id') != supplier_id]
|
||||
with open(self.suppliers_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(suppliers, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# ==================== КАТЕГОРИИ ====================
|
||||
def get_categories(self):
|
||||
try:
|
||||
with open(self.categories_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
return []
|
||||
|
||||
# ==================== ПРИХОДНЫЕ НАКЛАДНЫЕ ====================
|
||||
def get_incoming_orders(self, sort_by='date', order='desc'):
|
||||
try:
|
||||
with open(self.incoming_file, 'r', encoding='utf-8') as f:
|
||||
orders = json.load(f)
|
||||
|
||||
if sort_by == 'date':
|
||||
orders.sort(key=lambda x: x.get('date', ''), reverse=(order == 'desc'))
|
||||
elif sort_by == 'total':
|
||||
orders.sort(key=lambda x: x.get('final_total', x.get('total', 0)), reverse=(order == 'desc'))
|
||||
elif sort_by == 'supplier':
|
||||
orders.sort(key=lambda x: x.get('supplier_name', ''), reverse=(order == 'desc'))
|
||||
return orders
|
||||
except:
|
||||
return []
|
||||
|
||||
def save_incoming_order(self, order):
|
||||
orders = self.get_incoming_orders()
|
||||
order['id'] = len(orders) + 1
|
||||
order['date'] = datetime.now().strftime('%d.%m.%Y %H:%M')
|
||||
orders.append(order)
|
||||
with open(self.incoming_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(orders, f, ensure_ascii=False, indent=2)
|
||||
return order
|
||||
|
||||
db = SimpleDB()
|
||||
47
incoming_orders.json
Normal file
47
incoming_orders.json
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
[
|
||||
{
|
||||
"supplier_id": 2,
|
||||
"supplier_name": "ИП ФотоСнаб",
|
||||
"product_id": 4,
|
||||
"product_name": "Фотопленка 35мм",
|
||||
"expiry_date": "31.03.2026",
|
||||
"quantity": 5000,
|
||||
"purchase_price": 250.0,
|
||||
"total": 1250000.0,
|
||||
"expiry_discount_percent": 50,
|
||||
"discount_amount": 625000.0,
|
||||
"final_total": 625000.0,
|
||||
"id": 2,
|
||||
"date": "30.03.2026 11:38"
|
||||
},
|
||||
{
|
||||
"supplier_id": 1,
|
||||
"supplier_name": "ООО КанцТорг",
|
||||
"product_id": 4,
|
||||
"product_name": "Фотопленка 35мм",
|
||||
"expiry_date": "31.03.2026",
|
||||
"quantity": 13,
|
||||
"purchase_price": 250.0,
|
||||
"total": 3250.0,
|
||||
"expiry_discount_percent": 50,
|
||||
"discount_amount": 1625.0,
|
||||
"final_total": 1625.0,
|
||||
"id": 1,
|
||||
"date": "30.03.2026 11:33"
|
||||
},
|
||||
{
|
||||
"supplier_id": 1,
|
||||
"supplier_name": "ООО КанцТорг",
|
||||
"product_id": 4,
|
||||
"product_name": "Фотопленка 35мм",
|
||||
"expiry_date": "31.03.2026",
|
||||
"quantity": 12,
|
||||
"purchase_price": 250.0,
|
||||
"total": 3000.0,
|
||||
"expiry_discount_percent": 50,
|
||||
"discount_amount": 1500.0,
|
||||
"final_total": 1500.0,
|
||||
"id": 3,
|
||||
"date": "30.03.2026 11:39"
|
||||
}
|
||||
]
|
||||
1
orders.json
Normal file
1
orders.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
[]
|
||||
1
outgoing_orders.json
Normal file
1
outgoing_orders.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
[]
|
||||
20
partners.json
Normal file
20
partners.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "ООО ФотоМир",
|
||||
"contact": "ivan@fotomir.ru",
|
||||
"phone": "+7 (999) 111-22-33"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "ТОО ФотоПлюс",
|
||||
"contact": "petr@fotoplus.ru",
|
||||
"phone": "+7 (999) 444-55-66"
|
||||
},
|
||||
{
|
||||
"name": "ячс",
|
||||
"contact": "ячс",
|
||||
"phone": "ячс",
|
||||
"id": 3
|
||||
}
|
||||
]
|
||||
52
products.json
Normal file
52
products.json
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Фотобумага глянцевая",
|
||||
"manufacturer": "Lomond",
|
||||
"product_type": "Фотобумага",
|
||||
"purchase_price": 300,
|
||||
"selling_price": 450,
|
||||
"expiry_date": "31.12.2026",
|
||||
"stock_quantity": 50
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Фотобумага матовая",
|
||||
"manufacturer": "Canon",
|
||||
"product_type": "Фотобумага",
|
||||
"purchase_price": 400,
|
||||
"selling_price": 550,
|
||||
"expiry_date": "31.12.2027",
|
||||
"stock_quantity": 30
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Чернила для принтера",
|
||||
"manufacturer": "Epson",
|
||||
"product_type": "Чернила",
|
||||
"purchase_price": 600,
|
||||
"selling_price": 890,
|
||||
"expiry_date": "30.06.2026",
|
||||
"stock_quantity": 20
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Фотопленка 35мм",
|
||||
"manufacturer": "Kodak",
|
||||
"product_type": "Пленка",
|
||||
"purchase_price": 250,
|
||||
"selling_price": 350,
|
||||
"expiry_date": "31.03.2026",
|
||||
"stock_quantity": 5040
|
||||
},
|
||||
{
|
||||
"name": "zxc",
|
||||
"manufacturer": "zxc",
|
||||
"product_type": "Фотопленка",
|
||||
"purchase_price": 1289.0,
|
||||
"selling_price": 3000.0,
|
||||
"expiry_date": " 31.12.2027",
|
||||
"stock_quantity": 10,
|
||||
"id": 5
|
||||
}
|
||||
]
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
Flask==2.3.3
|
||||
2
requirements.txt.txt
Normal file
2
requirements.txt.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Flask==2.3.3
|
||||
Flask-SQLAlchemy==3.1.1
|
||||
11
reviews.json
Normal file
11
reviews.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[
|
||||
{
|
||||
"user_id": 1,
|
||||
"author_name": "RoTacy",
|
||||
"rating": 5,
|
||||
"text": "ееееееееееееееееееееееееее",
|
||||
"id": 1,
|
||||
"created_at": "27.03.2026",
|
||||
"is_approved": true
|
||||
}
|
||||
]
|
||||
5
static/image_names.txt
Normal file
5
static/image_names.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
Портретная съемка
|
||||
Пейзажная фотография
|
||||
Предметная съемка
|
||||
Свадебная фотография
|
||||
Уличная съемка
|
||||
BIN
static/images/photo1.jpg
Normal file
BIN
static/images/photo1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
BIN
static/images/photo2.jpg
Normal file
BIN
static/images/photo2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
static/images/photo3.jpg
Normal file
BIN
static/images/photo3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
BIN
static/images/photo4.jpg
Normal file
BIN
static/images/photo4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
BIN
static/images/photo5.jpg
Normal file
BIN
static/images/photo5.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
231
static/styles.css
Normal file
231
static/styles.css
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Old Standard TT', 'Times New Roman', serif;
|
||||
background-color: #FDF5E6;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Шапка сайта */
|
||||
nav {
|
||||
background-color: #CD853F;
|
||||
padding: 1rem 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
list-style: none;
|
||||
gap: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
transition: all 0.3s;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
background-color: #FDF5E6;
|
||||
color: #CD853F;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Основной контент */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 2rem auto;
|
||||
padding: 2rem;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #CD853F;
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 2rem;
|
||||
border-left: 4px solid #CD853F;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
color: #8B5A2B;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/* Герой-секция */
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
background: linear-gradient(135deg, #FDF5E6 0%, #E6D5B8 100%);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
border-left: none;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Карточки */
|
||||
.features-grid, .services-grid, .products-grid, .gallery-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.feature-card, .service-card, .product-card, .discount-card {
|
||||
background: #FDF5E6;
|
||||
padding: 1.5rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #CD853F;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.feature-card:hover, .service-card:hover, .product-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(205, 133, 63, 0.2);
|
||||
}
|
||||
|
||||
/* Товары */
|
||||
.product-card {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.product-card h3 {
|
||||
color: #CD853F;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size: 1.3rem;
|
||||
color: #CD853F;
|
||||
font-weight: bold;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.manufacturer {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.expiry {
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.stock {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.8rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.in-stock {
|
||||
background-color: #90EE90;
|
||||
color: #228B22;
|
||||
}
|
||||
|
||||
.out-of-stock {
|
||||
background-color: #FFB6C1;
|
||||
color: #DC143C;
|
||||
}
|
||||
|
||||
/* Форма */
|
||||
.order-form {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
color: #CD853F;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #CD853F;
|
||||
border-radius: 5px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.btn-primary, .btn-submit {
|
||||
background-color: #CD853F;
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
transition: background-color 0.3s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primary:hover, .btn-submit:hover {
|
||||
background-color: #B87333;
|
||||
}
|
||||
|
||||
/* Футер */
|
||||
footer {
|
||||
background-color: #CD853F;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
/* Акции */
|
||||
.discount-card {
|
||||
background: #FDF5E6;
|
||||
border-left: 4px solid #CD853F;
|
||||
}
|
||||
|
||||
/* Адаптивность */
|
||||
@media (max-width: 768px) {
|
||||
nav ul {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
0
static/styles.css.txt
Normal file
0
static/styles.css.txt
Normal file
4
suppliers.json
Normal file
4
suppliers.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[
|
||||
{"id":1,"name":"ООО КанцТорг","contact":"Иванов Иван","phone":"+7 (495) 123-45-67"},
|
||||
{"id":2,"name":"ИП ФотоСнаб","contact":"Петров Петр","phone":"+7 (495) 987-65-43"}
|
||||
]
|
||||
8
templates/about.html
Normal file
8
templates/about.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>О нас</h1>
|
||||
<p>Мы занимаемся продажей фотоматериалов с 2010 года.</p>
|
||||
<p>В нашем ассортименте представлены фотобумага, чернила, фотопленка и аксессуары.</p>
|
||||
<p>Мы сотрудничаем с проверенными производителями: Canon, Epson, Kodak, Lomond.</p>
|
||||
{% endblock %}
|
||||
38
templates/base.html
Normal file
38
templates/base.html
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Фотоматериалы - Склад</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Old+Standard+TT:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Главная</a></li>
|
||||
<li><a href="/products">Товары</a></li>
|
||||
<li><a href="/suppliers">Поставщики</a></li>
|
||||
<li><a href="/incoming">Поступление</a></li>
|
||||
<li><a href="/incoming-orders">Приходные</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="flash {{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Фотоматериалы. Система учета склада</p>
|
||||
<p>📞 Телефон: +7 (999) 123-45-67 | ✉️ Email: info@photomaterials.ru</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
56
templates/certificates.html
Normal file
56
templates/certificates.html
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Сертификаты качества</h1>
|
||||
<p>Вся наша продукция имеет официальные сертификаты и соответствует высоким стандартам качества.</p>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; margin-top: 2rem;">
|
||||
|
||||
<div style="background: linear-gradient(135deg, #FDF5E6 0%, #fff 100%); border-radius: 15px; padding: 1.5rem; border: 1px solid #CD853F; text-align: center;">
|
||||
<div style="font-size: 3rem; margin-bottom: 1rem;">📜</div>
|
||||
<h3 style="color: #CD853F;">Сертификат ISO 9001</h3>
|
||||
<p>Международный стандарт качества менеджмента</p>
|
||||
<p style="font-size: 0.9rem; color: #666;">Действителен до 2026 года</p>
|
||||
</div>
|
||||
|
||||
<div style="background: linear-gradient(135deg, #FDF5E6 0%, #fff 100%); border-radius: 15px; padding: 1.5rem; border: 1px solid #CD853F; text-align: center;">
|
||||
<div style="font-size: 3rem; margin-bottom: 1rem;">🏆</div>
|
||||
<h3 style="color: #CD853F;">Гарантия производителя</h3>
|
||||
<p>Официальная гарантия от Canon, Epson, Kodak</p>
|
||||
<p style="font-size: 0.9rem; color: #666;">Срок гарантии 12 месяцев</p>
|
||||
</div>
|
||||
|
||||
<div style="background: linear-gradient(135deg, #FDF5E6 0%, #fff 100%); border-radius: 15px; padding: 1.5rem; border: 1px solid #CD853F; text-align: center;">
|
||||
<div style="font-size: 3rem; margin-bottom: 1rem;">🌿</div>
|
||||
<h3 style="color: #CD853F;">Экологический сертификат</h3>
|
||||
<p>Продукция безопасна для окружающей среды</p>
|
||||
<p style="font-size: 0.9rem; color: #666;">Соответствует стандартам RoHS</p>
|
||||
</div>
|
||||
|
||||
<div style="background: linear-gradient(135deg, #FDF5E6 0%, #fff 100%); border-radius: 15px; padding: 1.5rem; border: 1px solid #CD853F; text-align: center;">
|
||||
<div style="font-size: 3rem; margin-bottom: 1rem;">🔬</div>
|
||||
<h3 style="color: #CD853F;">Сертификат качества</h3>
|
||||
<p>Лабораторные испытания продукции</p>
|
||||
<p style="font-size: 0.9rem; color: #666;">Соответствие ГОСТ Р</p>
|
||||
</div>
|
||||
|
||||
<div style="background: linear-gradient(135deg, #FDF5E6 0%, #fff 100%); border-radius: 15px; padding: 1.5rem; border: 1px solid #CD853F; text-align: center;">
|
||||
<div style="font-size: 3rem; margin-bottom: 1rem;">📸</div>
|
||||
<h3 style="color: #CD853F;">Сертификат соответствия</h3>
|
||||
<p>Профессиональная фотопродукция</p>
|
||||
<p style="font-size: 0.9rem; color: #666;">Рекомендовано профессиональными фотографами</p>
|
||||
</div>
|
||||
|
||||
<div style="background: linear-gradient(135deg, #FDF5E6 0%, #fff 100%); border-radius: 15px; padding: 1.5rem; border: 1px solid #CD853F; text-align: center;">
|
||||
<div style="font-size: 3rem; margin-bottom: 1rem;">📋</div>
|
||||
<h3 style="color: #CD853F;">Декларация о соответствии</h3>
|
||||
<p>Таможенного союза ЕАЭС</p>
|
||||
<p style="font-size: 0.9rem; color: #666;">Действительна до 2027 года</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 2rem; padding: 1.5rem; background: #CD853F10; border-radius: 10px; text-align: center; border-left: 4px solid #CD853F;">
|
||||
<p style="margin: 0;">Все сертификаты можно запросить по электронной почте или посмотреть в нашем магазине</p>
|
||||
<p style="margin-top: 0.5rem; color: #CD853F;">✉️ rotacyzxc@gmail.com | 📞 +7 (995) 947-00-42</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
56
templates/contacts.html
Normal file
56
templates/contacts.html
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Контакты</h1>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
||||
<div>
|
||||
<h3 style="color: #CD853F;">Наши контакты</h3>
|
||||
|
||||
<p><strong>📞 Телефон:</strong><br>
|
||||
<span style="font-size: 1.3rem;">+7 (995) 947-00-42</span></p>
|
||||
|
||||
<p><strong>💬 WhatsApp / Telegram:</strong><br>
|
||||
+7 (995) 947-00-42</p>
|
||||
|
||||
<p><strong>✉️ Email: rotacyzxc@gmail.com</strong><br>
|
||||
</p>
|
||||
|
||||
<p><strong>📍 Адрес:</strong><br>
|
||||
г. Белорецк, ул. Точисского, д. 16/2</p>
|
||||
|
||||
<p><strong>🕒 Режим работы:</strong><br>
|
||||
Понедельник - Пятница: 10:00 - 20:00<br>
|
||||
Суббота: 11:00 - 18:00<br>
|
||||
Воскресенье: выходной</p>
|
||||
|
||||
<p><strong>🚗 Как добраться:</strong><br>
|
||||
на такси или пешочком</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 style="color: #CD853F;">Свяжитесь с нами</h3>
|
||||
<form method="POST" style="background: #FDF5E6; padding: 1.5rem; border-radius: 10px;">
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label>Ваше имя:</label><br>
|
||||
<input type="text" name="name" style="width: 100%; padding: 0.5rem; border: 1px solid #CD853F; border-radius: 5px;">
|
||||
</div>
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label>Email:</label><br>
|
||||
<input type="email" name="email" style="width: 100%; padding: 0.5rem; border: 1px solid #CD853F; border-radius: 5px;">
|
||||
</div>
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label>Сообщение:</label><br>
|
||||
<textarea name="message" rows="4" style="width: 100%; padding: 0.5rem; border: 1px solid #CD853F; border-radius: 5px;"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Отправить</button>
|
||||
</form>
|
||||
|
||||
<div style="margin-top: 2rem; background: #CD853F10; padding: 1rem; border-radius: 10px; text-align: center;">
|
||||
<p>📱 Мы в соцсетях:</p>
|
||||
<p>Instagram | VK | Telegram</p>
|
||||
<p style="font-size: 0.9rem; color: #666;">@RoTacy</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
29
templates/discounts.html
Normal file
29
templates/discounts.html
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Акции и скидки</h1>
|
||||
|
||||
<div class="discount-card">
|
||||
<h3>🎉 Скидка 10% на первый заказ</h3>
|
||||
<p>Для новых клиентов при первом заказе от 1000 ₽</p>
|
||||
<p>Акция действует до 31.12.2024</p>
|
||||
</div>
|
||||
|
||||
<div class="discount-card">
|
||||
<h3>🎁 Подарок при заказе</h3>
|
||||
<p>При покупке фотобумаги - чернила со скидкой 15%</p>
|
||||
<p>Акция действует до 31.03.2024</p>
|
||||
</div>
|
||||
|
||||
<div class="discount-card">
|
||||
<h3>🏆 Оптовым покупателям</h3>
|
||||
<p>Индивидуальные условия для постоянных клиентов и оптовых закупок от 5000 ₽</p>
|
||||
<p>Скидка до 20% в зависимости от объема</p>
|
||||
</div>
|
||||
|
||||
<div class="discount-card">
|
||||
<h3>📸 Спецпредложение месяца</h3>
|
||||
<p>Фотобумага Lomond глянцевая + чернила Epson = 1200 ₽ (экономия 140 ₽)</p>
|
||||
<p>Предложение ограничено!</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
62
templates/edit_product.html
Normal file
62
templates/edit_product.html
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>✏️ Редактировать товар</h1>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;">
|
||||
<form method="POST" style="background: #FDF5E6; padding: 2rem; border-radius: 10px;">
|
||||
<div class="form-group">
|
||||
<label>Название *</label>
|
||||
<input type="text" name="name" value="{{ product.name }}" required style="width: 100%; padding: 0.5rem;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Производитель</label>
|
||||
<input type="text" name="manufacturer" value="{{ product.manufacturer }}" style="width: 100%; padding: 0.5rem;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Категория</label>
|
||||
<input type="text" name="product_type" value="{{ product.product_type }}" style="width: 100%; padding: 0.5rem;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Цена закупки (₽)</label>
|
||||
<input type="number" name="purchase_price" value="{{ product.purchase_price }}" step="0.01" style="width: 100%; padding: 0.5rem;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Цена продажи (₽)</label>
|
||||
<input type="number" name="selling_price" value="{{ product.selling_price }}" step="0.01" style="width: 100%; padding: 0.5rem;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Срок годности</label>
|
||||
<input type="text" name="expiry_date" value="{{ product.expiry_date }}" placeholder="дд.мм.гггг" style="width: 100%; padding: 0.5rem;">
|
||||
<small style="color: #666;">📅 Формат: 31.12.2026</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Остаток на складе</label>
|
||||
<input type="number" name="stock_quantity" value="{{ product.stock_quantity }}" style="width: 100%; padding: 0.5rem;">
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<button type="submit" style="background: #CD853F; color: white; padding: 0.5rem 1rem; border: none; border-radius: 5px;">💾 Сохранить</button>
|
||||
<a href="/products" style="background: #666; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 5px;">❌ Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
color: #CD853F;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
44
templates/edit_supplier.html
Normal file
44
templates/edit_supplier.html
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>✏️ Редактировать поставщика</h1>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;">
|
||||
<form method="POST" style="background: #FDF5E6; padding: 2rem; border-radius: 10px;">
|
||||
<div class="form-group">
|
||||
<label>Название *</label>
|
||||
<input type="text" name="name" value="{{ supplier.name }}" required style="width: 100%; padding: 0.5rem;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Контактное лицо</label>
|
||||
<input type="text" name="contact" value="{{ supplier.contact }}" style="width: 100%; padding: 0.5rem;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Телефон</label>
|
||||
<input type="text" name="phone" value="{{ supplier.phone }}" id="phone" style="width: 100%; padding: 0.5rem;">
|
||||
<small style="color: #666; display: block; margin-top: 0.25rem;">
|
||||
📞 Форматы: +7 (999) 123-45-67, 8-999-123-45-67, 9991234567
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<button type="submit" style="background: #CD853F; color: white; padding: 0.5rem 1rem; border: none; border-radius: 5px;">💾 Сохранить</button>
|
||||
<a href="/suppliers" style="background: #666; color: white; padding: 0.5rem 1rem; text-decoration: none; border-radius: 5px;">❌ Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
color: #CD853F;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
53
templates/gallery.html
Normal file
53
templates/gallery.html
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Галерея работ</h1>
|
||||
<p>Примеры работ, напечатанных на наших материалах:</p>
|
||||
|
||||
{% if gallery_items %}
|
||||
<div class="gallery-grid">
|
||||
{% for item in gallery_items %}
|
||||
<div class="gallery-item">
|
||||
<img src="{{ url_for('static', filename='images/' + item.file) }}"
|
||||
alt="{{ item.name }}"
|
||||
style="width: 100%; height: 250px; object-fit: cover; border-radius: 10px;">
|
||||
<p style="text-align: center; margin-top: 10px; font-weight: bold; color: #CD853F;">
|
||||
{{ item.name }}
|
||||
</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p style="text-align: center; color: #999;">Фотографии пока не добавлены. Добавьте изображения в папку static/images/</p>
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
.gallery-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.gallery-item {
|
||||
background: #FDF5E6;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
transition: transform 0.3s;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.gallery-item:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(205, 133, 63, 0.2);
|
||||
}
|
||||
|
||||
.gallery-item img {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.gallery-item:hover img {
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
140
templates/incoming.html
Normal file
140
templates/incoming.html
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>📥 Поступление товара на склад</h1>
|
||||
|
||||
<div style="max-width: 600px; margin: 0 auto;">
|
||||
<form method="POST" style="background: #FDF5E6; padding: 2rem; border-radius: 10px;" oninput="calculatePrice()">
|
||||
<div class="form-group">
|
||||
<label>Поставщик *</label>
|
||||
<select name="supplier_id" required style="width: 100%; padding: 0.5rem;">
|
||||
<option value="">Выберите поставщика</option>
|
||||
{% for supplier in suppliers %}
|
||||
<option value="{{ supplier.id }}">{{ supplier.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Товар *</label>
|
||||
<select name="product_id" id="product_id" required style="width: 100%; padding: 0.5rem;" onchange="updateProductInfo()">
|
||||
<option value="">Выберите товар</option>
|
||||
{% for product in products %}
|
||||
<option value="{{ product.id }}"
|
||||
data-price="{{ product.purchase_price }}"
|
||||
data-expiry="{{ product.expiry_date }}">
|
||||
{{ product.name }} (закупка: {{ product.purchase_price }} ₽, срок: {{ product.expiry_date or 'не указан' }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Количество *</label>
|
||||
<input type="number" name="quantity" id="quantity" min="1" value="1" required style="width: 100%; padding: 0.5rem;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Цена закупки (₽) *</label>
|
||||
<input type="number" name="purchase_price" id="purchase_price" step="0.01" required style="width: 100%; padding: 0.5rem;">
|
||||
</div>
|
||||
|
||||
<div style="background: white; padding: 1rem; border-radius: 5px; margin: 1rem 0;">
|
||||
<p><strong>💰 Расчет стоимости:</strong></p>
|
||||
<p>Сумма: <span id="total">0</span> ₽</p>
|
||||
<p>Скидка по сроку годности: <span id="discount_percent">0</span>% (<span id="discount_amount">0</span> ₽)</p>
|
||||
<p style="font-size: 1.2rem;"><strong>Итого к оплате: <span id="final_total">0</span> ₽</strong></p>
|
||||
</div>
|
||||
|
||||
<div style="background: #FFF3E0; padding: 0.5rem; border-radius: 5px; margin-bottom: 1rem;">
|
||||
<small>📅 <strong>Условия скидки по сроку годности:</strong></small>
|
||||
<ul style="margin: 0.5rem 0 0 1rem; font-size: 0.85rem;">
|
||||
<li>Менее 30 дней → 50% скидка</li>
|
||||
<li>30-90 дней → 30% скидка</li>
|
||||
<li>90-180 дней → 15% скидка</li>
|
||||
<li>Более 180 дней → 0% скидка</li>
|
||||
<li>Просрочен → 100% скидка (не принимается)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-submit">✅ Оприходовать на склад</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateProductInfo() {
|
||||
let select = document.getElementById('product_id');
|
||||
let selectedOption = select.options[select.selectedIndex];
|
||||
let price = selectedOption.getAttribute('data-price') || 0;
|
||||
let expiry = selectedOption.getAttribute('data-expiry') || '';
|
||||
|
||||
document.getElementById('purchase_price').value = price;
|
||||
calculatePrice();
|
||||
}
|
||||
|
||||
function calculatePrice() {
|
||||
let quantity = parseInt(document.getElementById('quantity').value) || 0;
|
||||
let price = parseFloat(document.getElementById('purchase_price').value) || 0;
|
||||
let total = quantity * price;
|
||||
|
||||
// Расчет скидки по сроку годности
|
||||
let select = document.getElementById('product_id');
|
||||
let selectedOption = select.options[select.selectedIndex];
|
||||
let expiry = selectedOption.getAttribute('data-expiry') || '';
|
||||
|
||||
let discountPercent = 0;
|
||||
if (expiry) {
|
||||
let parts = expiry.split('.');
|
||||
if (parts.length === 3) {
|
||||
let expiryDate = new Date(parts[2], parts[1] - 1, parts[0]);
|
||||
let today = new Date();
|
||||
let daysUntil = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (daysUntil < 0) discountPercent = 100;
|
||||
else if (daysUntil < 30) discountPercent = 50;
|
||||
else if (daysUntil < 90) discountPercent = 30;
|
||||
else if (daysUntil < 180) discountPercent = 15;
|
||||
}
|
||||
}
|
||||
|
||||
let discountAmount = total * discountPercent / 100;
|
||||
let finalTotal = total - discountAmount;
|
||||
|
||||
document.getElementById('total').innerText = total.toFixed(2);
|
||||
document.getElementById('discount_percent').innerText = discountPercent;
|
||||
document.getElementById('discount_amount').innerText = discountAmount.toFixed(2);
|
||||
document.getElementById('final_total').innerText = finalTotal.toFixed(2);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('product_id').addEventListener('change', updateProductInfo);
|
||||
document.getElementById('quantity').addEventListener('input', calculatePrice);
|
||||
updateProductInfo();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
color: #CD853F;
|
||||
}
|
||||
.btn-submit {
|
||||
background: #CD853F;
|
||||
color: white;
|
||||
padding: 0.75rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.btn-submit:hover {
|
||||
background: #B87333;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
64
templates/incoming_orders.html
Normal file
64
templates/incoming_orders.html
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>📋 Приходные накладные</h1>
|
||||
|
||||
<!-- Сортировка -->
|
||||
<div style="margin-bottom: 1rem; padding: 0.5rem; background: #FDF5E6; border-radius: 5px;">
|
||||
<span>Сортировать по: </span>
|
||||
<a href="/incoming-orders?sort=date&order={% if sort_by=='date' and order=='desc' %}asc{% else %}desc{% endif %}" style="color: #CD853F; text-decoration: none;">
|
||||
📅 Дата {% if sort_by=='date' %}{% if order=='desc' %}↓{% else %}↑{% endif %}{% endif %}
|
||||
</a> |
|
||||
<a href="/incoming-orders?sort=total&order={% if sort_by=='total' and order=='desc' %}asc{% else %}desc{% endif %}" style="color: #CD853F; text-decoration: none;">
|
||||
💰 Сумма {% if sort_by=='total' %}{% if order=='desc' %}↓{% else %}↑{% endif %}{% endif %}
|
||||
</a> |
|
||||
<a href="/incoming-orders?sort=supplier&order={% if sort_by=='supplier' and order=='desc' %}asc{% else %}desc{% endif %}" style="color: #CD853F; text-decoration: none;">
|
||||
🚚 Поставщик {% if sort_by=='supplier' %}{% if order=='desc' %}↓{% else %}↑{% endif %}{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if orders %}
|
||||
{% for order in orders %}
|
||||
<div style="background: #FDF5E6; padding: 1rem; margin-bottom: 1rem; border-radius: 10px; border-left: 4px solid
|
||||
{% if order.expiry_discount_percent and order.expiry_discount_percent > 0 %}#FFA500{% else %}#CD853F{% endif %};">
|
||||
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<strong>Поступление #{{ order.id }}</strong>
|
||||
<span style="color: #666;">{{ order.date }}</span>
|
||||
</div>
|
||||
<p><strong>🚚 Поставщик:</strong> {{ order.supplier_name }}</p>
|
||||
<p><strong>📦 Товар:</strong> {{ order.product_name }}</p>
|
||||
<p><strong>📅 Срок годности:</strong> {{ order.expiry_date or 'не указан' }}</p>
|
||||
<p><strong>📦 Количество:</strong> {{ order.quantity }} шт.</p>
|
||||
<p><strong>💰 Цена закупки:</strong> {{ order.purchase_price }} ₽</p>
|
||||
|
||||
{% if order.expiry_discount_percent and order.expiry_discount_percent > 0 %}
|
||||
<div style="background: #FFF3E0; padding: 0.5rem; border-radius: 5px; margin-top: 0.5rem;">
|
||||
<p><strong>💰 Стоимость со скидкой:</strong></p>
|
||||
<p style="margin-left: 1rem;">
|
||||
Сумма: {{ order.total }} ₽<br>
|
||||
Скидка по сроку годности: {{ order.expiry_discount_percent }}% ({{ order.discount_amount }} ₽)<br>
|
||||
<span style="font-size: 1.1rem; color: #CD853F;"><strong>Итого: {{ order.final_total }} ₽</strong></span>
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<p><strong>💰 Итого:</strong> {{ order.total }} ₽</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>Приходных накладных пока нет.</p>
|
||||
<p><a href="/incoming" class="btn-primary">➕ Оформить поступление</a></p>
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
.btn-primary {
|
||||
display: inline-block;
|
||||
background: #CD853F;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
113
templates/index.html
Normal file
113
templates/index.html
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<h1>📦 Система учета склада</h1>
|
||||
<p>Фотоматериалы — управление товарами, поставщиками и поступлениями</p>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<h3>📦 Товаров на складе</h3>
|
||||
<p class="stat-number">{{ total_items }}</p>
|
||||
<p>единиц</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>💰 Общая стоимость</h3>
|
||||
<p class="stat-number">{{ "{:,.0f}".format(total_value) }} ₽</p>
|
||||
<p>по розничным ценам</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<h3>📦 Управление товарами</h3>
|
||||
<p>Добавление, редактирование, удаление товаров. Контроль остатков на складе.</p>
|
||||
<a href="/products" class="btn-small">Перейти →</a>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🚚 Поставщики</h3>
|
||||
<p>Ведение базы поставщиков для оперативного пополнения склада.</p>
|
||||
<a href="/suppliers" class="btn-small">Перейти →</a>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>📥 Поступление товаров</h3>
|
||||
<p>Оприходование товара на склад. Автоматическое увеличение остатков.</p>
|
||||
<a href="/incoming" class="btn-small">Перейти →</a>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>📋 Приходные накладные</h3>
|
||||
<p>История всех поступлений товара на склад. Сортировка по дате и сумме.</p>
|
||||
<a href="/incoming-orders" class="btn-small">Перейти →</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background: #FDF5E6;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.hero h1 {
|
||||
color: #CD853F;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.stat-card {
|
||||
background: #CD853F;
|
||||
color: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-card h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.stat-number {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.feature-card {
|
||||
background: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #CD853F30;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
border-color: #CD853F;
|
||||
}
|
||||
.feature-card h3 {
|
||||
color: #CD853F;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.btn-small {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
background: #CD853F;
|
||||
color: white;
|
||||
padding: 0.3rem 0.8rem;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.btn-small:hover {
|
||||
background: #B87333;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
70
templates/my_orders.html
Normal file
70
templates/my_orders.html
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Все заказы</h1>
|
||||
|
||||
<!-- Сортировка -->
|
||||
<div style="margin-bottom: 1rem; padding: 0.5rem; background: #FDF5E6; border-radius: 5px;">
|
||||
<span>Сортировать по: </span>
|
||||
<a href="/my-orders?sort=date&order={% if sort_by=='date' and order=='desc' %}asc{% else %}desc{% endif %}" style="color: #CD853F; text-decoration: none;">
|
||||
📅 Дата {% if sort_by=='date' %}{% if order=='desc' %}↓{% else %}↑{% endif %}{% endif %}
|
||||
</a> |
|
||||
<a href="/my-orders?sort=total&order={% if sort_by=='total' and order=='desc' %}asc{% else %}desc{% endif %}" style="color: #CD853F; text-decoration: none;">
|
||||
💰 Сумма {% if sort_by=='total' %}{% if order=='desc' %}↓{% else %}↑{% endif %}{% endif %}
|
||||
</a> |
|
||||
<a href="/my-orders?sort=quantity&order={% if sort_by=='quantity' and order=='desc' %}asc{% else %}desc{% endif %}" style="color: #CD853F; text-decoration: none;">
|
||||
📦 Количество {% if sort_by=='quantity' %}{% if order=='desc' %}↓{% else %}↑{% endif %}{% endif %}
|
||||
</a> |
|
||||
<a href="/my-orders?sort=name&order={% if sort_by=='name' and order=='desc' %}asc{% else %}desc{% endif %}" style="color: #CD853F; text-decoration: none;">
|
||||
👤 Имя {% if sort_by=='name' %}{% if order=='desc' %}↓{% else %}↑{% endif %}{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if orders %}
|
||||
{% for order in orders %}
|
||||
<div style="background: #FDF5E6; padding: 1rem; margin-bottom: 1rem; border-radius: 10px; border-left: 4px solid #CD853F;">
|
||||
|
||||
<div style="display: flex; justify-content: space-between; flex-wrap: wrap;">
|
||||
<strong>Заказ #{{ order.id }}</strong>
|
||||
<div>
|
||||
<span style="color: #666;">{{ order.order_date }}</span>
|
||||
<a href="/orders/delete/{{ order.id }}" onclick="return confirm('Удалить заказ #{{ order.id }}?')" style="background: #f44336; color: white; padding: 0.2rem 0.5rem; text-decoration: none; border-radius: 3px; margin-left: 0.5rem;">🗑️ Удалить</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>👤 Клиент:</strong> {{ order.client_name }} ({{ order.phone }})</p>
|
||||
<p><strong>📦 Товар:</strong> {{ order.product_name }}</p>
|
||||
<p><strong>📋 Категория:</strong> {{ order.category }}</p>
|
||||
<p><strong>🤝 Партнер:</strong> {{ order.partner }}</p>
|
||||
<p><strong>📦 Количество:</strong> {{ order.quantity }} шт. × {{ order.price_per_unit }} ₽</p>
|
||||
|
||||
<div style="background: white; padding: 0.5rem; border-radius: 5px; margin-top: 0.5rem;">
|
||||
<p><strong>💰 Стоимость:</strong></p>
|
||||
<p style="margin-left: 1rem;">
|
||||
Сумма: {{ order.total }} ₽<br>
|
||||
Скидка: {{ order.discount_percent }}% ({{ order.discount_amount }} ₽)<br>
|
||||
<span style="font-size: 1.2rem; color: #CD853F;"><strong>Итого: {{ order.final_total }} ₽</strong></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if order.notes %}
|
||||
<p style="margin-top: 0.5rem;"><strong>📝 Примечание:</strong> {{ order.notes }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p style="text-align: center;">Заказов пока нет.</p>
|
||||
<p style="text-align: center;"><a href="/order" class="btn-primary">Сделать первый заказ</a></p>
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
.btn-primary {
|
||||
display: inline-block;
|
||||
background: #CD853F;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
130
templates/order.html
Normal file
130
templates/order.html
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Оформление заказа</h1>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
||||
<div>
|
||||
<form method="POST" class="order-form" oninput="calculateTotal()">
|
||||
<div class="form-group">
|
||||
<label>Ваше имя *</label>
|
||||
<input type="text" name="name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Телефон *</label>
|
||||
<input type="tel" name="phone" required placeholder="+7 (___) ___-__-__">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Email *</label>
|
||||
<input type="email" name="email" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Выберите товар *</label>
|
||||
<select name="product_id" id="product_id" required onchange="updatePrice()">
|
||||
<option value="">Выберите товар</option>
|
||||
{% for product in products %}
|
||||
<option value="{{ product.id }}" data-price="{{ product.price }}" data-category="{{ product.product_type }}">
|
||||
{{ product.name }} - {{ product.price }} ₽ ({{ product.product_type }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Партнер *</label>
|
||||
<select name="partner" required>
|
||||
<option value="">Выберите партнера</option>
|
||||
{% for partner in partners %}
|
||||
<option value="{{ partner.name }}">{{ partner.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Количество</label>
|
||||
<input type="number" name="quantity" id="quantity" min="1" value="1" onchange="calculateTotal()">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Примечание</label>
|
||||
<textarea name="notes" rows="4"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-submit">Отправить заказ</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div style="background: #FDF5E6; padding: 1.5rem; border-radius: 10px; border: 1px solid #CD853F;">
|
||||
<h3>💰 Расчет стоимости</h3>
|
||||
<div id="calculation">
|
||||
<p><strong>Товар:</strong> <span id="selected_product">-</span></p>
|
||||
<p><strong>Цена за шт:</strong> <span id="price">0</span> ₽</p>
|
||||
<p><strong>Сумма:</strong> <span id="total">0</span> ₽</p>
|
||||
<p><strong>Скидка:</strong> <span id="discount_percent">0</span>% (<span id="discount_amount">0</span> ₽)</p>
|
||||
<p style="font-size: 1.2rem;"><strong>Итого:</strong> <span id="final_total">0</span> ₽</p>
|
||||
</div>
|
||||
<hr>
|
||||
<p><strong>Условия скидки:</strong></p>
|
||||
<ul style="font-size: 0.9rem;">
|
||||
<li>от 5 шт. → 5% скидка</li>
|
||||
<li>от 10 шт. → 10% скидка</li>
|
||||
<li>от 20 шт. → 15% скидка</li>
|
||||
<li>от 50 шт. → 20% скидка</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<p><strong>📞 Телефон:</strong> +7 (999) 123-45-67</p>
|
||||
<p><strong>✉️ Email:</strong> info@photomaterials.ru</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updatePrice() {
|
||||
let select = document.getElementById('product_id');
|
||||
let selectedOption = select.options[select.selectedIndex];
|
||||
let price = selectedOption.getAttribute('data-price') || 0;
|
||||
document.getElementById('price').innerText = price;
|
||||
document.getElementById('selected_product').innerText = selectedOption.text.split(' - ')[0];
|
||||
calculateTotal();
|
||||
}
|
||||
|
||||
function calculateTotal() {
|
||||
let quantity = parseInt(document.getElementById('quantity').value) || 0;
|
||||
let select = document.getElementById('product_id');
|
||||
let selectedOption = select.options[select.selectedIndex];
|
||||
let price = parseInt(selectedOption.getAttribute('data-price')) || 0;
|
||||
let total = quantity * price;
|
||||
|
||||
let discountPercent = 0;
|
||||
if (quantity >= 50) discountPercent = 20;
|
||||
else if (quantity >= 20) discountPercent = 15;
|
||||
else if (quantity >= 10) discountPercent = 10;
|
||||
else if (quantity >= 5) discountPercent = 5;
|
||||
|
||||
let discountAmount = total * discountPercent / 100;
|
||||
let finalTotal = total - discountAmount;
|
||||
|
||||
document.getElementById('total').innerText = total.toFixed(2);
|
||||
document.getElementById('discount_percent').innerText = discountPercent;
|
||||
document.getElementById('discount_amount').innerText = discountAmount.toFixed(2);
|
||||
document.getElementById('final_total').innerText = finalTotal.toFixed(2);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('product_id').addEventListener('change', updatePrice);
|
||||
updatePrice();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.order-form { max-width: 100%; }
|
||||
.form-group { margin-bottom: 1rem; }
|
||||
.form-group label { display: block; margin-bottom: 0.5rem; font-weight: bold; color: #CD853F; }
|
||||
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 0.75rem; border: 1px solid #ddd; border-radius: 5px; }
|
||||
.btn-submit { background: #CD853F; color: white; padding: 0.75rem; border: none; border-radius: 5px; width: 100%; cursor: pointer; }
|
||||
.btn-submit:hover { background: #B87333; }
|
||||
#calculation { background: white; padding: 1rem; border-radius: 5px; margin-top: 1rem; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
45
templates/partners.html
Normal file
45
templates/partners.html
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Наши партнеры</h1>
|
||||
|
||||
<div style="background: #FDF5E6; padding: 1rem; margin-bottom: 2rem; border-radius: 10px;">
|
||||
<h3>➕ Добавить партнера</h3>
|
||||
<form method="POST">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem;">
|
||||
<input type="text" name="name" placeholder="Название" required style="padding: 0.5rem;">
|
||||
<input type="text" name="contact" placeholder="Контакт" style="padding: 0.5rem;">
|
||||
<input type="text" name="phone" placeholder="Телефон" style="padding: 0.5rem;">
|
||||
<button type="submit" style="background: #CD853F; color: white; padding: 0.5rem;">Добавить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="partners-grid">
|
||||
{% for partner in partners %}
|
||||
<div class="partner-card">
|
||||
<h3>{{ partner.name }}</h3>
|
||||
<p>📧 {{ partner.contact }}</p>
|
||||
<p>📞 {{ partner.phone }}</p>
|
||||
<a href="/partners/delete/{{ partner.id }}" onclick="return confirm('Удалить партнера {{ partner.name }}?')" style="color: #f44336; text-decoration: none;">🗑️ Удалить</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.partners-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.partner-card {
|
||||
background: #FDF5E6;
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #CD853F;
|
||||
text-align: center;
|
||||
}
|
||||
.partner-card h3 { color: #CD853F; margin-bottom: 0.5rem; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
78
templates/products.html
Normal file
78
templates/products.html
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>📦 Управление товарами</h1>
|
||||
|
||||
<!-- Форма добавления -->
|
||||
<div style="background: #FDF5E6; padding: 1rem; margin-bottom: 2rem; border-radius: 10px;">
|
||||
<h3>➕ Добавить товар</h3>
|
||||
<form method="POST">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.5rem;">
|
||||
<input type="text" name="name" placeholder="Название" required style="padding: 0.5rem;">
|
||||
<input type="text" name="manufacturer" placeholder="Производитель" style="padding: 0.5rem;">
|
||||
<select name="product_type" style="padding: 0.5rem;">
|
||||
<option value="">Выберите категорию</option>
|
||||
{% for cat in categories %}
|
||||
<option value="{{ cat.name }}">{{ cat.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="number" name="purchase_price" placeholder="Цена закупки" step="0.01" style="padding: 0.5rem;">
|
||||
<input type="number" name="selling_price" placeholder="Цена продажи" step="0.01" style="padding: 0.5rem;">
|
||||
<input type="text" name="expiry_date" placeholder="Срок годности" style="padding: 0.5rem;">
|
||||
<input type="number" name="stock_quantity" placeholder="Начальный остаток" style="padding: 0.5rem;">
|
||||
<button type="submit" style="background: #CD853F; color: white; padding: 0.5rem;">Добавить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Список товаров -->
|
||||
<div class="products-grid">
|
||||
{% if products %}
|
||||
{% for product in products %}
|
||||
<div class="product-card">
|
||||
<h3>{{ product.name }}</h3>
|
||||
<p>🏭 Производитель: {{ product.manufacturer }}</p>
|
||||
<p>📋 Категория: {{ product.product_type }}</p>
|
||||
<p>💰 Закупка: {{ product.purchase_price }} ₽ | Продажа: {{ product.selling_price }} ₽</p>
|
||||
<p>📅 Срок годности: {{ product.expiry_date if product.expiry_date else 'не указан' }}</p>
|
||||
<p class="stock {% if product.stock_quantity > 0 %}in-stock{% else %}out-of-stock{% endif %}">
|
||||
📦 Остаток: {{ product.stock_quantity }} шт.
|
||||
</p>
|
||||
<div style="margin-top: 0.5rem; display: flex; gap: 0.5rem;">
|
||||
<a href="/products/edit/{{ product.id }}" style="background: #2196F3; color: white; padding: 0.2rem 0.5rem; text-decoration: none; border-radius: 3px;">✏️ Редактировать</a>
|
||||
<a href="/products/delete/{{ product.id }}" onclick="return confirm('Удалить товар?')" style="background: #f44336; color: white; padding: 0.2rem 0.5rem; text-decoration: none; border-radius: 3px;">🗑️ Удалить</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>Товаров пока нет</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.products-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.product-card {
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #CD853F30;
|
||||
}
|
||||
.product-card h3 {
|
||||
color: #CD853F;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.in-stock {
|
||||
color: #228B22;
|
||||
font-weight: bold;
|
||||
}
|
||||
.out-of-stock {
|
||||
color: #DC143C;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
63
templates/reviews.html
Normal file
63
templates/reviews.html
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Отзывы наших клиентов</h1>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
||||
<!-- Форма отзыва -->
|
||||
<div style="background: #FDF5E6; padding: 1.5rem; border-radius: 10px; border: 1px solid #CD853F;">
|
||||
<h3 style="color: #CD853F;">Оставить отзыв</h3>
|
||||
|
||||
{% if session.get('user_id') %}
|
||||
<form method="POST">
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label style="display: block; margin-bottom: 0.5rem;">Оценка</label>
|
||||
<select name="rating" required style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 5px;">
|
||||
<option value="5">★★★★★ (5 звезд)</option>
|
||||
<option value="4">★★★★☆ (4 звезды)</option>
|
||||
<option value="3">★★★☆☆ (3 звезды)</option>
|
||||
<option value="2">★★☆☆☆ (2 звезды)</option>
|
||||
<option value="1">★☆☆☆☆ (1 звезда)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label style="display: block; margin-bottom: 0.5rem;">Ваш отзыв</label>
|
||||
<textarea name="text" rows="4" required style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 5px;"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary">Отправить отзыв</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>Чтобы оставить отзыв, <a href="/login" style="color: #CD853F;">войдите в систему</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Список отзывов -->
|
||||
<div>
|
||||
<h3 style="color: #CD853F;">Отзывы</h3>
|
||||
|
||||
{% if reviews %}
|
||||
{% for review in reviews %}
|
||||
<div style="background: white; padding: 1rem; margin-bottom: 1rem; border-radius: 10px; border: 1px solid #eee;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<strong style="color: #CD853F;">{{ review.author_name }}</strong>
|
||||
<span style="margin-left: 0.5rem;">
|
||||
{% for i in range(review.rating) %}★{% endfor %}
|
||||
{% for i in range(5 - review.rating) %}☆{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
<div style="color: #999; font-size: 0.8rem;">
|
||||
{{ review.created_at }}
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 0.5rem;">{{ review.text }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p style="color: #999;">Пока нет отзывов. Будьте первым!</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
98
templates/services.html
Normal file
98
templates/services.html
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Наши услуги</h1>
|
||||
<p>Мы предлагаем полный спектр услуг для фотографов и любителей:</p>
|
||||
|
||||
<div class="services-grid">
|
||||
<div class="service-card">
|
||||
<h3>🖨️ Печать фотографий</h3>
|
||||
<p><strong>Форматы:</strong> 10x15, 15x21, 20x30, А4, А3+</p>
|
||||
<p><strong>Материалы:</strong> глянцевая, матовая, перламутровая, холст</p>
|
||||
<p><strong>Цены:</strong> от 30 ₽ за фото 10x15</p>
|
||||
<p><strong>Срок:</strong> от 30 минут</p>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<h3>🎨 Фотообработка</h3>
|
||||
<p><strong>Услуги:</strong> цветокоррекция, ретушь, кадрирование</p>
|
||||
<p><strong>Удаление дефектов:</strong> исправление кожи, удаление объектов</p>
|
||||
<p><strong>Цены:</strong> от 100 ₽ за фото</p>
|
||||
<p><strong>Срок:</strong> от 1 часа</p>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<h3>🖼️ Печать на холсте</h3>
|
||||
<p><strong>Форматы:</strong> любой размер до 90x120 см</p>
|
||||
<p><strong>Особенности:</strong> натяжка на подрамник, покрытие лаком</p>
|
||||
<p><strong>Цены:</strong> от 1500 ₽ за холст 30x40</p>
|
||||
<p><strong>Срок:</strong> 2-3 дня</p>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<h3>📚 Фотокниги и альбомы</h3>
|
||||
<p><strong>Форматы:</strong> 20x20, 20x30, 30x30 см</p>
|
||||
<p><strong>Варианты:</strong> твердая обложка, кожаный переплет</p>
|
||||
<p><strong>Цены:</strong> от 2000 ₽ за фотокнигу 20 страниц</p>
|
||||
<p><strong>Срок:</strong> 5-7 дней</p>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<h3>💬 Консультации</h3>
|
||||
<p><strong>Помощь в выборе:</strong> фотобумаги, чернил, принтеров</p>
|
||||
<p><strong>Настройка оборудования:</strong> калибровка монитора, профилирование</p>
|
||||
<p><strong>Консультация:</strong> бесплатно по телефону или в магазине</p>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<h3>🚚 Доставка</h3>
|
||||
<p><strong>По Москве:</strong> курьером - 300 ₽, бесплатно от 2000 ₽</p>
|
||||
<p><strong>По России:</strong> Почта России, СДЭК от 350 ₽</p>
|
||||
<p><strong>Самовывоз:</strong> бесплатно из магазина</p>
|
||||
<p><strong>Срок:</strong> 1-2 дня по Москве, 3-7 дней по России</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 2rem; padding: 1.5rem; background: #FDF5E6; border-radius: 10px; border-left: 4px solid #CD853F;">
|
||||
<h3>✨ Специальное предложение</h3>
|
||||
<p>При заказе печати от 20 фотографий - скидка 10% на обработку!</p>
|
||||
<p>Акция действует до 31.05.2024</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
background: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #CD853F20;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
border-color: #CD853F;
|
||||
box-shadow: 0 5px 15px rgba(205, 133, 63, 0.1);
|
||||
}
|
||||
|
||||
.service-card h3 {
|
||||
color: #CD853F;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.service-card p {
|
||||
margin: 0.5rem 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.service-card strong {
|
||||
color: #8B5A2B;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
85
templates/supplier_orders.html
Normal file
85
templates/supplier_orders.html
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>📋 Заказы поставщика: {{ supplier.name }}</h1>
|
||||
|
||||
<div style="background: #FDF5E6; padding: 1rem; margin-bottom: 2rem; border-radius: 10px;">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
|
||||
<div>
|
||||
<strong>📞 Контакт:</strong> {{ supplier.contact }}
|
||||
</div>
|
||||
<div>
|
||||
<strong>📱 Телефон:</strong> {{ supplier.phone }}
|
||||
</div>
|
||||
<div>
|
||||
<strong>📦 Всего поставок:</strong> {{ orders|length }}
|
||||
</div>
|
||||
<div>
|
||||
<strong>📦 Всего товара:</strong> {{ total_items }} шт.
|
||||
</div>
|
||||
<div>
|
||||
<strong>💰 Общая сумма:</strong> {{ "{:,.0f}".format(total_sum) }} ₽
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 1rem;">
|
||||
<a href="/suppliers" class="btn-back">← Назад к поставщикам</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if orders %}
|
||||
<div class="orders-list">
|
||||
{% for order in orders %}
|
||||
<div class="order-card">
|
||||
<div style="display: flex; justify-content: space-between; flex-wrap: wrap;">
|
||||
<strong>📄 Поступление #{{ order.id }}</strong>
|
||||
<span style="color: #666;">{{ order.date }}</span>
|
||||
</div>
|
||||
<p><strong>📦 Товар:</strong> {{ order.product_name }}</p>
|
||||
<p><strong>📦 Количество:</strong> {{ order.quantity }} шт.</p>
|
||||
<p><strong>💰 Цена закупки:</strong> {{ order.purchase_price }} ₽</p>
|
||||
<p><strong>💰 Общая сумма:</strong> {{ order.total }} ₽</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="text-align: center; padding: 3rem; background: #FDF5E6; border-radius: 10px;">
|
||||
<p>📭 У этого поставщика пока нет заказов.</p>
|
||||
<p><a href="/incoming" class="btn-primary">➕ Оформить поступление</a></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
.order-card {
|
||||
background: #FDF5E6;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #CD853F;
|
||||
}
|
||||
.btn-back {
|
||||
display: inline-block;
|
||||
background: #666;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.btn-back:hover {
|
||||
background: #555;
|
||||
}
|
||||
.btn-primary {
|
||||
display: inline-block;
|
||||
background: #CD853F;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: #B87333;
|
||||
}
|
||||
.orders-list {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
54
templates/suppliers.html
Normal file
54
templates/suppliers.html
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>🚚 Управление поставщиками</h1>
|
||||
|
||||
<div style="background: #FDF5E6; padding: 1rem; margin-bottom: 2rem; border-radius: 10px;">
|
||||
<h3>➕ Добавить поставщика</h3>
|
||||
<form method="POST">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem;">
|
||||
<input type="text" name="name" placeholder="Название" required style="padding: 0.5rem;">
|
||||
<input type="text" name="contact" placeholder="Контактное лицо" style="padding: 0.5rem;">
|
||||
<input type="text" name="phone" placeholder="Телефон" style="padding: 0.5rem;">
|
||||
<button type="submit" style="background: #CD853F; color: white; padding: 0.5rem;">Добавить</button>
|
||||
</div>
|
||||
<small style="color: #666; display: block; margin-top: 0.5rem;">
|
||||
📞 Форматы телефона: +7 (999) 123-45-67, 8-999-123-45-67, 9991234567
|
||||
</small>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="suppliers-grid">
|
||||
{% for supplier in suppliers %}
|
||||
<div class="supplier-card">
|
||||
<h3>{{ supplier.name }}</h3>
|
||||
<p>📧 {{ supplier.contact }}</p>
|
||||
<p>📞 {{ supplier.phone }}</p>
|
||||
<div style="margin-top: 0.5rem; display: flex; gap: 0.5rem; justify-content: center;">
|
||||
<a href="/suppliers/edit/{{ supplier.id }}" style="background: #2196F3; color: white; padding: 0.3rem 0.8rem; text-decoration: none; border-radius: 5px;">✏️ Редактировать</a>
|
||||
<a href="/supplier/orders/{{ supplier.id }}" style="background: #4CAF50; color: white; padding: 0.3rem 0.8rem; text-decoration: none; border-radius: 5px;">📋 Заказы</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.suppliers-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.supplier-card {
|
||||
background: #FDF5E6;
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #CD853F;
|
||||
text-align: center;
|
||||
}
|
||||
.supplier-card h3 {
|
||||
color: #CD853F;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
Loading…
Reference in a new issue