project322/app.py

432 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
import os
from datetime import datetime
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
import pandas as pd
app = Flask(__name__)
app.config['SECRET_KEY'] = 'mint-warehouse-secret-key-2025'
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(BASE_DIR, 'instance', 'warehouse.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)
# ========== МОДЕЛИ ==========
class Partner(db.Model):
__tablename__ = 'partners'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
short_name = db.Column(db.String(100))
partner_type = db.Column(db.String(50))
director = db.Column(db.String(255))
phone = db.Column(db.String(50))
discount = db.Column(db.Numeric(5, 2), default=0.00)
total_orders_sum = db.Column(db.Numeric(12, 2), default=0.00)
actual_address = db.Column(db.Text)
legal_address = db.Column(db.Text)
orders = db.relationship('Order', backref='partner', lazy=True)
# ДОБАВЬТЕ ЭТОТ МЕТОД
def calculate_discount(self):
"""Расчёт скидки на основе суммы заказов"""
total = float(self.total_orders_sum) if self.total_orders_sum else 0
if total >= 50000:
return 20
elif total >= 20000:
return 10
elif total >= 10000:
return 5
else:
return 0
def update_discount(self):
"""Обновить скидку партнёра"""
self.discount = self.calculate_discount()
def __repr__(self):
return f'<Partner {self.name}>'
class ProductCategory(db.Model):
__tablename__ = 'product_categories'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False, unique=True)
description = db.Column(db.Text)
products = db.relationship('Product', backref='category', lazy=True)
def __repr__(self):
return f'<Category {self.name}>'
class Product(db.Model):
__tablename__ = 'products'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
article = db.Column(db.String(50), unique=True, nullable=False)
name = db.Column(db.String(255), nullable=False)
commercial_name = db.Column(db.String(255))
unit = db.Column(db.String(20), default='шт')
base_price = db.Column(db.Numeric(10, 2), nullable=False)
excise_tax = db.Column(db.Numeric(10, 2), default=0.00)
license_fee = db.Column(db.Numeric(10, 2), default=0.00)
is_alcohol = db.Column(db.Boolean, default=False)
alcohol_content = db.Column(db.Numeric(5, 2), nullable=True)
volume = db.Column(db.Numeric(10, 3), nullable=True)
category_id = db.Column(db.Integer, db.ForeignKey('product_categories.id'), nullable=False)
order_items = db.relationship('OrderItem', backref='product', lazy=True)
def __repr__(self):
return f'<Product {self.article}: {self.name}>'
class Order(db.Model):
__tablename__ = 'orders'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
order_number = db.Column(db.String(50), unique=True, nullable=False)
date_created = db.Column(db.DateTime, default=datetime.now)
partner_id = db.Column(db.Integer, db.ForeignKey('partners.id'), nullable=False)
items = db.relationship('OrderItem', backref='order', lazy=True, cascade='all, delete-orphan')
def calculate_total(self):
total = 0
for item in self.items:
total += item.calculate_subtotal()
return round(total, 2)
def __repr__(self):
return f'<Order {self.order_number}>'
class OrderItem(db.Model):
__tablename__ = 'order_items'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
quantity = db.Column(db.Numeric(10, 3), nullable=False)
price_per_unit = db.Column(db.Numeric(10, 2), nullable=False)
order_id = db.Column(db.Integer, db.ForeignKey('orders.id'), nullable=False)
product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False)
def calculate_subtotal(self):
base = float(self.price_per_unit) * float(self.quantity)
discount = float(self.order.partner.discount) if self.order and self.order.partner else 0
discounted_base = base * (1 - discount / 100)
excise = float(self.product.excise_tax) * float(self.quantity)
license = float(self.product.license_fee) * float(self.quantity)
return round(discounted_base + excise + license, 2)
def __repr__(self):
return f'<OrderItem {self.id}>'
def calculate_subtotal(self):
base = float(self.price_per_unit) * float(self.quantity)
# Скидка берётся из партнёра (уже накопительная)
discount = float(self.order.partner.discount) if self.order and self.order.partner else 0
discounted_base = base * (1 - discount / 100)
excise = float(self.product.excise_tax) * float(self.quantity)
license = float(self.product.license_fee) * float(self.quantity)
return round(discounted_base + excise + license, 2)
# ========== МАРШРУТЫ ==========
@app.route('/')
def index():
orders = Order.query.order_by(Order.date_created.desc()).all()
return render_template('index.html', orders=orders)
@app.route('/products')
def products():
products = Product.query.all()
categories = ProductCategory.query.all()
return render_template('products.html', products=products, categories=categories)
@app.route('/product/new', methods=['GET', 'POST'])
def new_product():
if request.method == 'POST':
try:
product = Product(
article=request.form['article'],
name=request.form['name'],
commercial_name=request.form.get('commercial_name', ''),
unit=request.form.get('unit', 'шт'),
base_price=float(request.form['base_price']),
excise_tax=float(request.form.get('excise_tax', 0)),
license_fee=float(request.form.get('license_fee', 0)),
is_alcohol='is_alcohol' in request.form,
alcohol_content=float(request.form['alcohol_content']) if request.form.get('alcohol_content') else None,
volume=float(request.form['volume']) if request.form.get('volume') else None,
category_id=int(request.form['category_id'])
)
db.session.add(product)
db.session.commit()
flash(f'✅ Товар "{product.name}" добавлен!', 'success')
return redirect(url_for('products'))
except Exception as e:
flash(f'❌ Ошибка: {str(e)}', 'error')
db.session.rollback()
categories = ProductCategory.query.all()
return render_template('product_form.html', product=None, categories=categories)
@app.route('/product/edit/<int:id>', methods=['GET', 'POST'])
def edit_product(id):
product = Product.query.get_or_404(id)
if request.method == 'POST':
try:
product.article = request.form['article']
product.name = request.form['name']
product.commercial_name = request.form.get('commercial_name', '')
product.unit = request.form.get('unit', 'шт')
product.base_price = float(request.form['base_price'])
product.excise_tax = float(request.form.get('excise_tax', 0))
product.license_fee = float(request.form.get('license_fee', 0))
product.is_alcohol = 'is_alcohol' in request.form
product.alcohol_content = float(request.form['alcohol_content']) if request.form.get('alcohol_content') else None
product.volume = float(request.form['volume']) if request.form.get('volume') else None
product.category_id = int(request.form['category_id'])
db.session.commit()
flash(f'✅ Товар "{product.name}" обновлен!', 'success')
return redirect(url_for('products'))
except Exception as e:
flash(f'❌ Ошибка: {str(e)}', 'error')
db.session.rollback()
categories = ProductCategory.query.all()
return render_template('product_form.html', product=product, categories=categories)
@app.route('/product/delete/<int:id>')
def delete_product(id):
product = Product.query.get_or_404(id)
if product.order_items:
flash(f'❌ Нельзя удалить товар "{product.name}", он используется в заявках!', 'error')
return redirect(url_for('products'))
db.session.delete(product)
db.session.commit()
flash(f'✅ Товар "{product.name}" удален!', 'success')
return redirect(url_for('products'))
@app.route('/partners')
def partners():
partners = Partner.query.all()
return render_template('partners.html', partners=partners)
@app.route('/partner/new', methods=['GET', 'POST'])
def new_partner():
if request.method == 'POST':
try:
partner = Partner(
name=request.form['name'],
short_name=request.form.get('short_name', ''),
partner_type=request.form.get('partner_type', ''),
director=request.form.get('director', ''),
phone=request.form.get('phone', ''),
discount=float(request.form.get('discount', 0)),
actual_address=request.form.get('actual_address', ''),
legal_address=request.form.get('legal_address', '')
)
db.session.add(partner)
db.session.commit()
flash(f'✅ Партнер "{partner.name}" добавлен!', 'success')
return redirect(url_for('partners'))
except Exception as e:
flash(f'❌ Ошибка: {str(e)}', 'error')
db.session.rollback()
return render_template('partner_form.html', partner=None)
@app.route('/partner/edit/<int:id>', methods=['GET', 'POST'])
def edit_partner(id):
partner = Partner.query.get_or_404(id)
if request.method == 'POST':
try:
partner.name = request.form['name']
partner.short_name = request.form.get('short_name', '')
partner.partner_type = request.form.get('partner_type', '')
partner.director = request.form.get('director', '')
partner.phone = request.form.get('phone', '')
partner.discount = float(request.form.get('discount', 0))
partner.actual_address = request.form.get('actual_address', '')
partner.legal_address = request.form.get('legal_address', '')
db.session.commit()
flash(f'✅ Партнер "{partner.name}" обновлен!', 'success')
return redirect(url_for('partners'))
except Exception as e:
flash(f'❌ Ошибка: {str(e)}', 'error')
db.session.rollback()
return render_template('partner_form.html', partner=partner)
@app.route('/partner/delete/<int:id>')
def delete_partner(id):
partner = Partner.query.get_or_404(id)
if partner.orders:
flash(f'❌ Нельзя удалить партнера "{partner.name}", у него есть заявки!', 'error')
return redirect(url_for('partners'))
db.session.delete(partner)
db.session.commit()
flash(f'✅ Партнер "{partner.name}" удален!', 'success')
return redirect(url_for('partners'))
@app.route('/partner/<int:id>')
def partner_detail(id):
partner = Partner.query.get_or_404(id)
orders = Order.query.filter_by(partner_id=id).order_by(Order.date_created.desc()).all()
return render_template('partner_detail.html', partner=partner, orders=orders)
@app.route('/categories')
def categories():
categories = ProductCategory.query.all()
return render_template('categories.html', categories=categories)
@app.route('/category/new', methods=['GET', 'POST'])
def new_category():
if request.method == 'POST':
try:
category = ProductCategory(
name=request.form['name'],
description=request.form.get('description', '')
)
db.session.add(category)
db.session.commit()
flash('✅ Категория добавлена!', 'success')
return redirect(url_for('categories'))
except Exception as e:
flash(f'❌ Ошибка: {str(e)}', 'error')
db.session.rollback()
return render_template('category_form.html', category=None)
@app.route('/category/edit/<int:id>', methods=['GET', 'POST'])
def edit_category(id):
category = ProductCategory.query.get_or_404(id)
if request.method == 'POST':
try:
category.name = request.form['name']
category.description = request.form.get('description', '')
db.session.commit()
flash('✅ Категория обновлена!', 'success')
return redirect(url_for('categories'))
except Exception as e:
flash(f'❌ Ошибка: {str(e)}', 'error')
db.session.rollback()
return render_template('category_form.html', category=category)
@app.route('/category/delete/<int:id>')
def delete_category(id):
category = ProductCategory.query.get_or_404(id)
if category.products:
flash('❌ Нельзя удалить категорию, в которой есть товары!', 'error')
return redirect(url_for('categories'))
db.session.delete(category)
db.session.commit()
flash('✅ Категория удалена!', 'success')
return redirect(url_for('categories'))
@app.route('/category/<int:id>')
def category_detail(id):
category = ProductCategory.query.get_or_404(id)
products = Product.query.filter_by(category_id=id).all()
return render_template('category_detail.html', category=category, products=products)
@app.route('/order/new', methods=['GET', 'POST'])
def new_order():
if request.method == 'POST':
try:
# Генерация номера заявки
last_order = Order.query.order_by(Order.id.desc()).first()
if last_order:
last_num = int(last_order.order_number.split('-')[-1])
new_num = last_num + 1
else:
new_num = 1
order_number = f"ZAK-{datetime.now().strftime('%Y%m')}-{new_num:04d}"
# Создаём заявку
order = Order(
order_number=order_number,
partner_id=int(request.form['partner_id'])
)
db.session.add(order)
db.session.flush()
# Добавляем позиции
product_ids = request.form.getlist('product_id[]')
quantities = request.form.getlist('quantity[]')
prices = request.form.getlist('price[]')
for i in range(len(product_ids)):
if product_ids[i] and quantities[i] and float(quantities[i]) > 0:
item = OrderItem(
order_id=order.id,
product_id=int(product_ids[i]),
quantity=float(quantities[i]),
price_per_unit=float(prices[i])
)
db.session.add(item)
# Сохраняем заявку
db.session.commit()
# ========== ОБНОВЛЯЕМ СУММУ ЗАКАЗОВ ПАРТНЁРА ==========
partner = Partner.query.get(order.partner_id)
if partner:
# Получаем все заявки партнёра
all_orders = Order.query.filter_by(partner_id=partner.id).all()
total_sum = 0
for o in all_orders:
total_sum += o.calculate_total()
# Обновляем сумму и скидку
partner.total_orders_sum = total_sum
partner.discount = partner.calculate_discount()
db.session.commit()
print(f"📊 Партнёр {partner.name}: сумма заказов = {total_sum:.2f} ₽, скидка = {partner.discount}%")
flash(f'✅ Заявка {order_number} создана!', 'success')
return redirect(url_for('index'))
except Exception as e:
flash(f'❌ Ошибка: {str(e)}', 'error')
db.session.rollback()
partners = Partner.query.all()
products = Product.query.all()
return render_template('order_form.html', partners=partners, products=products, order=None)
@app.route('/order/<int:id>')
def order_detail(id):
order = Order.query.get_or_404(id)
return render_template('order_detail.html', order=order)
@app.route('/order/delete/<int:id>')
def delete_order(id):
order = Order.query.get_or_404(id)
db.session.delete(order)
db.session.commit()
flash('✅ Заявка удалена!', 'success')
return redirect(url_for('index'))
if __name__ == '__main__':
print("🚀 Запуск АлкоСклад...")
app.run(debug=True, host='0.0.0.0')