from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify from . import db, csrf from .models import Partners, Inventory, Categories, IssueOrders, IssueOrderItems, ReturnOrders, ReturnOrderItems from .utils import calculate_item_price, get_current_season_name from datetime import datetime bp = Blueprint('main', __name__) # Главная @bp.route('/') def index(): active_issues = IssueOrders.query.filter(~IssueOrders.return_order.has()).count() partners_count = Partners.query.count() expected_total = 0 for issue in IssueOrders.query.filter(~IssueOrders.return_order.has()).all(): for item in issue.items: expected_total += float(item.inventory.base_price) * item.quantity recent_issues = IssueOrders.query.order_by(IssueOrders.issue_date.desc()).limit(5).all() recent_returns = ReturnOrders.query.order_by(ReturnOrders.return_date.desc()).limit(5).all() return render_template('index.html', active_issues=active_issues, partners_count=partners_count, expected_total=round(expected_total, 2), recent_issues=recent_issues, recent_returns=recent_returns) # Партнёры @bp.route('/partners') def partners(): search = request.args.get('search', '') query = Partners.query if search: query = query.filter( (Partners.name.contains(search)) | (Partners.phone.contains(search)) ) all_partners = query.all() return render_template('partners.html', partners=all_partners) @bp.route('/partners/new', methods=['GET', 'POST']) def partner_new(): if request.method == 'POST': name = request.form['name'] phone = request.form['phone'].strip() email = request.form['email'] address = request.form['address'] # Валидация телефона if phone: digits = ''.join(filter(str.isdigit, phone)) if len(digits) < 10 or len(digits) > 15: flash('Телефон должен содержать от 10 до 15 цифр (например, +7(999)123-45-67)', 'danger') # Создаём временный объект для возврата в форму temp_partner = Partners(name=name, phone=phone, email=email, address=address) return render_template('partner_form.html', partner=temp_partner) p = Partners(name=name, phone=phone, email=email, address=address) db.session.add(p) db.session.commit() flash('Партнёр добавлен', 'success') return redirect(url_for('main.partners')) return render_template('partner_form.html') @bp.route('/partners/') def partner_detail(id): partner = Partners.query.get_or_404(id) return render_template('partner_detail.html', partner=partner) @bp.route('/partners//edit', methods=['GET', 'POST']) def partner_edit(id): partner = Partners.query.get_or_404(id) if request.method == 'POST': name = request.form['name'] phone = request.form['phone'].strip() email = request.form['email'] address = request.form['address'] # Сначала обновляем поля (ещё не коммитим) partner.name = name partner.phone = phone partner.email = email partner.address = address # Валидация телефона if phone: digits = ''.join(filter(str.isdigit, phone)) if len(digits) < 10 or len(digits) > 15: flash('Телефон должен содержать от 10 до 15 цифр (например, +7(999)123-45-67)', 'danger') return render_template('partner_form.html', partner=partner) db.session.commit() flash('Данные обновлены', 'success') return redirect(url_for('main.partner_detail', id=id)) return render_template('partner_form.html', partner=partner) # Инвентарь @bp.route('/inventory') def inventory(): category_id = request.args.get('category', type=int) season = request.args.get('season', '') query = Inventory.query if category_id: query = query.filter_by(category_id=category_id) if season in ['winter', 'summer']: query = query.filter_by(season_type=season) items = query.all() categories = Categories.query.all() return render_template('inventory.html', items=items, categories=categories, selected_category=category_id, selected_season=season) @bp.route('/inventory/new', methods=['GET', 'POST']) def inventory_new(): if request.method == 'POST': name = request.form['name'] category_id = request.form['category_id'] base_price = request.form['base_price'] season_type = request.form['season_type'] inv = Inventory(name=name, category_id=category_id, base_price=base_price, season_type=season_type) db.session.add(inv) db.session.commit() flash('Инвентарь добавлен', 'success') return redirect(url_for('main.inventory')) categories = Categories.query.all() return render_template('inventory_form.html', categories=categories) @bp.route('/inventory//edit', methods=['GET', 'POST']) def inventory_edit(id): inv = Inventory.query.get_or_404(id) if request.method == 'POST': inv.name = request.form['name'] inv.category_id = request.form['category_id'] inv.base_price = request.form['base_price'] inv.season_type = request.form['season_type'] db.session.commit() flash('Изменения сохранены', 'success') return redirect(url_for('main.inventory')) categories = Categories.query.all() return render_template('inventory_form.html', item=inv, categories=categories) @bp.route('/inventory//delete', methods=['POST']) def inventory_delete(id): inv = Inventory.query.get_or_404(id) if IssueOrderItems.query.filter_by(inventory_id=id).first() or ReturnOrderItems.query.filter_by( inventory_id=id).first(): flash('Нельзя удалить инвентарь, который был выдан', 'danger') else: db.session.delete(inv) db.session.commit() flash('Инвентарь удалён', 'success') return redirect(url_for('main.inventory')) # Категории @bp.route('/categories') def categories(): cats = Categories.query.all() return render_template('categories.html', categories=cats) @bp.route('/categories/new', methods=['GET', 'POST']) def category_new(): if request.method == 'POST': name = request.form['name'] description = request.form['description'] cat = Categories(name=name, description=description) db.session.add(cat) db.session.commit() flash('Категория добавлена', 'success') return redirect(url_for('main.categories')) return render_template('category_form.html') @bp.route('/categories//edit', methods=['GET', 'POST']) def category_edit(id): cat = Categories.query.get_or_404(id) if request.method == 'POST': cat.name = request.form['name'] cat.description = request.form['description'] db.session.commit() flash('Категория обновлена', 'success') return redirect(url_for('main.categories')) return render_template('category_form.html', category=cat) @bp.route('/categories//delete', methods=['POST']) def category_delete(id): cat = Categories.query.get_or_404(id) if Inventory.query.filter_by(category_id=id).first(): flash('Нельзя удалить категорию, в которой есть инвентарь', 'danger') else: db.session.delete(cat) db.session.commit() flash('Категория удалена', 'success') return redirect(url_for('main.categories')) # Выдачи @bp.route('/issues') def issues(): issues = IssueOrders.query.all() return render_template('issues.html', issues=issues) @bp.route('/issues/new', methods=['GET', 'POST']) def issue_new(): if request.method == 'POST': partner_id = request.form['partner_id'] notes = request.form.get('notes', '') issue = IssueOrders(partner_id=partner_id, notes=notes) db.session.add(issue) db.session.commit() inventory_ids = request.form.getlist('inventory_id[]') quantities = request.form.getlist('quantity[]') for inv_id, qty in zip(inventory_ids, quantities): if inv_id and qty: item = IssueOrderItems(issue_order_id=issue.id, inventory_id=int(inv_id), quantity=int(qty)) db.session.add(item) db.session.commit() flash('Выдача оформлена', 'success') return redirect(url_for('main.issues')) partners = Partners.query.all() inventory = Inventory.query.all() return render_template('issue_form.html', partners=partners, inventory=inventory) @bp.route('/issues/') def issue_detail(id): issue = IssueOrders.query.get_or_404(id) return render_template('issue_detail.html', issue=issue) @bp.route('/issues//edit', methods=['GET', 'POST']) def issue_edit(id): issue = IssueOrders.query.get_or_404(id) if issue.return_order: flash('Нельзя редактировать закрытую выдачу', 'danger') return redirect(url_for('main.issue_detail', id=id)) if request.method == 'POST': issue.notes = request.form.get('notes', '') IssueOrderItems.query.filter_by(issue_order_id=id).delete() inventory_ids = request.form.getlist('inventory_id[]') quantities = request.form.getlist('quantity[]') for inv_id, qty in zip(inventory_ids, quantities): if inv_id and qty: item = IssueOrderItems(issue_order_id=id, inventory_id=int(inv_id), quantity=int(qty)) db.session.add(item) db.session.commit() flash('Выдача обновлена', 'success') return redirect(url_for('main.issue_detail', id=id)) partners = Partners.query.all() inventory = Inventory.query.all() return render_template('issue_form.html', issue=issue, partners=partners, inventory=inventory) # Возвраты @bp.route('/returns') def returns(): returns = ReturnOrders.query.all() return render_template('returns.html', returns=returns) @bp.route('/returns/new', methods=['GET', 'POST']) def return_new(): if request.method == 'POST': issue_id = request.form['issue_id'] # Проверка на существующий возврат existing_return = ReturnOrders.query.filter_by(issue_order_id=issue_id).first() if existing_return: flash('Возврат по этой выдаче уже оформлен', 'danger') return redirect(url_for('main.return_new')) # Получаем список ID позиций items_data = request.form.getlist('item_id[]') if not items_data: flash('Нет позиций для возврата', 'danger') return redirect(url_for('main.return_new')) # Собираем степени износа для каждой позиции wear_dict = {} for item_id in items_data: wear = request.form.get(f'wear_{item_id}') if not wear: flash(f'Не указана степень износа для позиции', 'danger') return redirect(url_for('main.return_new')) wear_dict[item_id] = wear return_date = datetime.now() notes = request.form.get('notes', '') # Создаём возврат (получаем ID, но не коммитим) ret = ReturnOrders(issue_order_id=issue_id, return_date=return_date, notes=notes) db.session.add(ret) db.session.flush() # чтобы получить ID возврата total = 0 for item_id in items_data: issue_item = IssueOrderItems.query.get(int(item_id)) wear = wear_dict[item_id] price = calculate_item_price( base_price=issue_item.inventory.base_price, quantity=issue_item.quantity, wear_level=wear, season_type=issue_item.inventory.season_type, return_date=return_date ) ret_item = ReturnOrderItems( return_order_id=ret.id, inventory_id=issue_item.inventory_id, quantity=issue_item.quantity, wear_level=wear, calculated_price=price ) db.session.add(ret_item) total += price ret.total_amount = round(total, 2) db.session.commit() flash('Возврат оформлен', 'success') return redirect(url_for('main.returns')) open_issues = IssueOrders.query.filter(~IssueOrders.return_order.has()).all() current_season = get_current_season_name() return render_template('return_select_issue.html', issues=open_issues, current_season=current_season) @bp.route('/returns/get_issue_items/') def get_issue_items(issue_id): issue = IssueOrders.query.get_or_404(issue_id) items = [] for item in issue.items: items.append({ 'id': item.id, 'inventory_id': item.inventory_id, 'inventory_name': item.inventory.name, 'quantity': item.quantity, 'base_price': float(item.inventory.base_price), 'season_type': item.inventory.season_type }) return jsonify(items) @csrf.exempt @bp.route('/returns/calculate_preview', methods=['POST']) def calculate_preview(): data = request.get_json() base_price = float(data['base_price']) quantity = int(data['quantity']) wear_level = data['wear_level'] season_type = data['season_type'] return_date = datetime.now() price = calculate_item_price(base_price, quantity, wear_level, season_type, return_date) return jsonify({'calculated_price': price})