import os import base64 import json import threading import time from datetime import datetime, timezone, timedelta from uuid import uuid4 from flask import Flask, render_template_string, request, redirect, url_for, flash, jsonify, send_from_directory from huggingface_hub import HfApi, hf_hub_download from huggingface_hub.utils import RepositoryNotFoundError, HfHubHTTPError from werkzeug.utils import secure_filename from dotenv import load_dotenv import requests load_dotenv() app = Flask(__name__) app.secret_key = 'super_secret_key_store_app_123' DATA_FILE = 'data.json' SYNC_FILES = [DATA_FILE, 'logo.png'] REPO_ID = os.getenv("REPO_ID", "Kgshop/sova") HF_TOKEN_WRITE = os.getenv("HF_TOKEN") HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") CURRENCY_CODE = 'сум' tashkent_tz = timezone(timedelta(hours=5), name='Asia/Tashkent') def get_current_time(): return datetime.now(tashkent_tz).strftime('%Y-%m-%d %H:%M:%S') def get_current_date(): return datetime.now(tashkent_tz).strftime('%Y-%m-%d') def download_db_from_hf(specific_file=None, retries=3, delay=5): token_to_use = HF_TOKEN_READ if HF_TOKEN_READ else HF_TOKEN_WRITE files_to_download = [specific_file] if specific_file else SYNC_FILES all_successful = True for file_name in files_to_download: success = False for attempt in range(retries + 1): try: hf_hub_download( repo_id=REPO_ID, filename=file_name, repo_type="dataset", token=token_to_use, local_dir=".", local_dir_use_symlinks=False, force_download=True, resume_download=False ) success = True break except RepositoryNotFoundError: return False except HfHubHTTPError as e: if e.response.status_code == 404: if attempt == 0 and not os.path.exists(file_name): try: if file_name == DATA_FILE: with open(file_name, 'w', encoding='utf-8') as f: json.dump({'products': [], 'categories': [], 'orders': {}, 'employees': [], 'workdays': {}, 'fines': [], 'settings': { 'cafe_name': 'HongKong', 'wa_shift1': '+77470623684', 'wa_shift2': '+77470623684', 'active_shift': 1, 'logo_version': '1' }}, f) except Exception: pass success = False break except requests.exceptions.RequestException: pass except Exception: pass if attempt < retries: time.sleep(delay) if not success: all_successful = False return all_successful def upload_db_to_hf(specific_file=None): if not HF_TOKEN_WRITE: return try: api = HfApi() files_to_upload = [specific_file] if specific_file else SYNC_FILES for file_name in files_to_upload: if os.path.exists(file_name): try: api.upload_file( path_or_fileobj=file_name, path_in_repo=file_name, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, commit_message=f"Sync {file_name} {get_current_time()}" ) except Exception: pass except Exception: pass def periodic_backup(): while True: time.sleep(1800) upload_db_to_hf() def load_data(): default_data = { 'products': [], 'categories': [], 'orders': {}, 'employees': [], 'workdays': {}, 'fines': [], 'settings': { 'cafe_name': 'Sova', 'wa_shift1': '+77470623684', 'wa_shift2': '+77470623684', 'active_shift': 1, 'logo_version': '1' } } data = default_data try: with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) if not isinstance(data, dict): raise FileNotFoundError if 'products' not in data: data['products'] = [] if 'categories' not in data: data['categories'] = [] if 'orders' not in data: data['orders'] = {} if 'employees' not in data: data['employees'] = [] if 'workdays' not in data: data['workdays'] = {} if 'fines' not in data: data['fines'] = [] if 'settings' not in data: data['settings'] = default_data['settings'] if 'logo_version' not in data['settings']: data['settings']['logo_version'] = '1' except (FileNotFoundError, json.JSONDecodeError): if download_db_from_hf(specific_file=DATA_FILE): try: with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) if 'products' not in data: data['products'] = [] if 'categories' not in data: data['categories'] = [] if 'orders' not in data: data['orders'] = {} if 'employees' not in data: data['employees'] = [] if 'workdays' not in data: data['workdays'] = {} if 'fines' not in data: data['fines'] = [] if 'settings' not in data: data['settings'] = default_data['settings'] if 'logo_version' not in data['settings']: data['settings']['logo_version'] = '1' except Exception: data = default_data else: data = default_data except Exception: data = default_data migrated_cats = [] for c in data.get('categories', []): if isinstance(c, str): migrated_cats.append({'name': c, 'icon': 'fas fa-utensils'}) else: if 'icon' not in c: c['icon'] = 'fas fa-utensils' migrated_cats.append(c) data['categories'] = migrated_cats for product in data['products']: if 'product_id' not in product: product['product_id'] = uuid4().hex for emp in data['employees']: if 'pin' not in emp: emp['pin'] = '0000' if 'daily_rate' not in emp: emp['daily_rate'] = 230000 if 'target_amount' not in emp: emp['target_amount'] = 1500000 if 'bonus_percentage' not in emp: emp['bonus_percentage'] = 10 if not os.path.exists(DATA_FILE): try: with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump(default_data, f) except Exception: pass return data def save_data(data): try: if not isinstance(data, dict): return if 'products' not in data: data['products'] = [] if 'categories' not in data: data['categories'] = [] if 'orders' not in data: data['orders'] = {} if 'employees' not in data: data['employees'] = [] if 'workdays' not in data: data['workdays'] = {} if 'fines' not in data: data['fines'] = [] if 'settings' not in data: data['settings'] = { 'cafe_name': 'Sova', 'wa_shift1': '', 'wa_shift2': '', 'active_shift': 1, 'logo_version': '1' } with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) upload_db_to_hf(specific_file=DATA_FILE) except Exception: pass CATALOG_TEMPLATE = ''' {{ settings.cafe_name }} | POS

Меню (В заведении)

Сумма заказа: 0 {{ currency_code }}
''' ORDER_TEMPLATE = ''' Чек №{{ order.id }}
Logo
{{ settings.cafe_name }}
Чек: {{ order.id }}
Дата: {{ order.created_at }}
Столик: {{ order.table_number }}
Кассир: {{ order.employee_name|default('Не указан') }}
{% set raw_total = 0 %} {% for item in order.cart %} {% set item_sum = item.price * item.quantity %} {% set raw_total = raw_total + item_sum %}
{{ item.name }}
{{ item.quantity }} x {{ item.price }}
{{ item_sum }}
{% endfor %}
Итого: {{ raw_total }}
{% set discount = order.discount|default(0)|float %} {% if discount > 0 %}
Скидка: -{{ discount }}
{% endif %}
К ОПЛАТЕ: {{ order.total_price }} {{ currency_code }}
Оплата: {% if order.payment_method == 'cash' %}Наличка {% elif order.payment_method == 'card' %}Карточка {% elif order.payment_method == 'click' %}Click {% elif order.payment_method == 'payme' %}Payme {% elif order.payment_method == 'paynet' %}Paynet {% elif order.payment_method == 'qr' %}QR {% else %}{{ order.payment_method }}{% endif %}
СПАСИБО ЗА ВАШ ВИЗИТ!
Назад в меню
''' REPORTS_TEMPLATE = ''' Отчеты по продажам

Отчеты по продажам

Назад в админку
Общая выручка: 0

По категориям

По позициям

По сотрудникам

По способам оплаты

''' SALARY_TEMPLATE = ''' Отчет по ЗП и Штрафам

Отчет по ЗП

Назад в админку

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

История штрафов

{% for fine in fines %} {% endfor %} {% if not fines %} {% endif %}
СотрудникДатаПричинаСуммаДействия
{% for emp in employees %} {% if emp.id == fine.employee_id %}{{ emp.name }}{% endif %} {% endfor %} {{ fine.date }} {{ fine.reason }} -{{ fine.amount }} {{ currency_code }}
Нет штрафов

Генерация Зарплаты

''' ADMIN_TEMPLATE = ''' Админ-панель

Админ-панель

Отчеты Зарплата В заведение
Настройки
Сотрудники

Добавить сотрудника

Список сотрудников

{% for emp in employees %}
{% endfor %}
История заказов
{% for order in orders.values()|sort(attribute='created_at', reverse=True) %} {% endfor %}
ID / Дата Детали заказа Сумма Оплата Действия
{{ order.id }}
{{ order.created_at }}
Столик: {{ order.table_number }}
Сотрудник: {{ order.employee_name|default('Не указан') }}
{{ order.total_price }} {{ currency_code }} {% if order.payment_method == 'cash' %}Наличка {% elif order.payment_method == 'card' %}Карточка {% elif order.payment_method == 'click' %}Click {% elif order.payment_method == 'payme' %}Payme {% elif order.payment_method == 'paynet' %}Paynet {% elif order.payment_method == 'qr' %}QR {% else %}{{ order.payment_method }}{% endif %}

Управление категориями

{% for category in categories %}
{{ category.name }}
Добавить блюдо
{% for product in products %} {% if product.category == category.name %}
{% if product.photos and product.photos|length > 0 %} {% else %}
{% endif %}
{{ product.name }} {{ product.price }} {{ currency_code }}
{% endif %} {% endfor %}
{% endfor %}
''' @app.route('/logo.png') def serve_logo(): return send_from_directory('.', 'logo.png') @app.route('/') def catalog(): data = load_data() all_products = data.get('products', []) categories = data.get('categories', []) employees = data.get('employees', []) settings = data.get('settings', {}) return render_template_string( CATALOG_TEMPLATE, products_json=json.dumps(all_products), categories_json=json.dumps(categories), employees=employees, repo_id=REPO_ID, currency_code=CURRENCY_CODE, settings=settings ) @app.route('/create_order', methods=['POST']) def create_order(): order_data = request.get_json() if not order_data or 'cart' not in order_data: return jsonify({"error": "Bad request"}), 400 data = load_data() cart_items = order_data['cart'] total_price = sum(float(item['price']) * int(item['quantity']) for item in cart_items) order_type = order_data.get('order_type', 'dine_in') table_number = order_data.get('table_number', 'Не указано') payment_method = order_data.get('payment_method', 'cash') employee_id = order_data.get('employee_id', '') employee_name = order_data.get('employee_name', 'Не указан') processed_cart = [] for item in cart_items: cat_name = "Без категории" for p in data.get('products', []): if p.get('product_id') == item.get('product_id'): cat_name = p.get('category', 'Без категории') break processed_cart.append({ "product_id": item.get('product_id'), "name": item['name'], "price": float(item['price']), "quantity": int(item['quantity']), "category": cat_name }) order_id = f"HK-{datetime.now(tashkent_tz).strftime('%Y%m%d')}-{str(len(data.get('orders', {}))+1).zfill(3)}" new_order = { "id": order_id, "created_at": get_current_time(), "cart": processed_cart, "discount": 0, "total_price": total_price, "order_type": order_type, "table_number": table_number, "payment_method": payment_method, "employee_id": employee_id, "employee_name": employee_name, "status": "confirmed" } data['orders'][order_id] = new_order save_data(data) return jsonify({"order_id": order_id}), 201 @app.route('/order/') def view_order(order_id): data = load_data() order = data.get('orders', {}).get(order_id) settings = data.get('settings', {}) if not order: return "Order not found", 404 return render_template_string( ORDER_TEMPLATE, order=order, currency_code=CURRENCY_CODE, settings=settings ) @app.route('/api/verify_pin', methods=['POST']) def verify_pin(): req = request.get_json() emp_id = req.get('employee_id') pin = req.get('pin') data = load_data() for emp in data.get('employees', []): if emp['id'] == emp_id: if emp.get('pin', '0000') == pin: today_str = get_current_date() workdays = data.get('workdays', {}) if today_str not in workdays: workdays[today_str] = [] if emp_id not in workdays[today_str]: workdays[today_str].append(emp_id) data['workdays'] = workdays save_data(data) return jsonify({"success": True}) else: return jsonify({"success": False, "error": "Неверный пин-код"}) return jsonify({"success": False, "error": "Сотрудник не найден"}) @app.route('/api/employee_report') def employee_report(): emp_id = request.args.get('employee_id') start_date = request.args.get('start_date') end_date = request.args.get('end_date') data = load_data() today_str = get_current_date() if not start_date: start_date = today_str if not end_date: end_date = today_str total_sum = 0 order_count = 0 by_payment = {} by_date = {} by_product = {} for o in data.get('orders', {}).values(): o_date = o.get('created_at', '')[:10] if o.get('employee_id') == emp_id and start_date <= o_date <= end_date: total_sum += o.get('total_price', 0) order_count += 1 pm = o.get('payment_method', 'cash') by_payment[pm] = by_payment.get(pm, 0) + o.get('total_price', 0) by_date[o_date] = by_date.get(o_date, 0) + o.get('total_price', 0) for item in o.get('cart', []): p_name = item.get('name', 'Неизвестно') p_qty = int(item.get('quantity', 0)) p_price = float(item.get('price', 0)) if p_name not in by_product: by_product[p_name] = {'qty': 0, 'sum': 0} by_product[p_name]['qty'] += p_qty by_product[p_name]['sum'] += (p_qty * p_price) return jsonify({ "total_sum": total_sum, "order_count": order_count, "by_payment": by_payment, "by_date": by_date, "by_product": by_product }) @app.route('/admin/reports') def admin_reports(): data = load_data() orders = data.get('orders', {}) return render_template_string( REPORTS_TEMPLATE, orders_json=json.dumps(list(orders.values())), currency_code=CURRENCY_CODE ) @app.route('/admin/salary', methods=['GET', 'POST']) def admin_salary(): data = load_data() if request.method == 'POST': action = request.form.get('action') if action == 'add_fine': emp_id = request.form.get('employee_id', '') date_fine = request.form.get('date', '') try: amount = float(request.form.get('amount', 0)) except (ValueError, TypeError): amount = 0.0 reason = request.form.get('reason', '') if 'fines' not in data: data['fines'] = [] data['fines'].append({ 'id': uuid4().hex, 'employee_id': emp_id, 'date': date_fine, 'amount': amount, 'reason': reason }) save_data(data) return redirect(url_for('admin_salary')) elif action == 'delete_fine': fine_id = request.form.get('fine_id') data['fines'] = [f for f in data.get('fines', []) if f.get('id') != fine_id] save_data(data) return redirect(url_for('admin_salary')) orders = data.get('orders', {}) employees = data.get('employees', []) workdays = data.get('workdays', {}) fines = data.get('fines', []) fines = sorted(fines, key=lambda x: x.get('date', ''), reverse=True) return render_template_string( SALARY_TEMPLATE, orders_json=json.dumps(list(orders.values())), employees_json=json.dumps(employees), workdays_json=json.dumps(workdays), fines_json=json.dumps(fines), fines=fines, employees=employees, currency_code=CURRENCY_CODE ) @app.route('/admin', methods=['GET', 'POST']) def admin(): data = load_data() products = data.get('products', []) categories = data.get('categories', []) orders = data.get('orders', {}) employees = data.get('employees', []) settings = data.get('settings', {}) if request.method == 'POST': action = request.form.get('action') if action == 'update_settings': settings['cafe_name'] = request.form.get('cafe_name', '').strip() logo_file = request.files.get('logo') if logo_file and logo_file.filename: logo_file.save('logo.png') settings['logo_version'] = str(uuid4().hex)[:8] upload_db_to_hf(specific_file='logo.png') data['settings'] = settings save_data(data) elif action == 'add_employee': emp_name = request.form.get('employee_name', '').strip() pin = request.form.get('pin', '0000').strip() try: daily_rate = float(request.form.get('daily_rate', 0)) except: daily_rate = 230000 try: target_amount = float(request.form.get('target_amount', 0)) except: target_amount = 1500000 try: bonus_percentage = float(request.form.get('bonus_percentage', 0)) except: bonus_percentage = 10 if emp_name: employees.append({ 'id': uuid4().hex, 'name': emp_name, 'pin': pin, 'daily_rate': daily_rate, 'target_amount': target_amount, 'bonus_percentage': bonus_percentage }) data['employees'] = employees save_data(data) elif action == 'edit_employee': emp_id = request.form.get('employee_id') for e in employees: if e.get('id') == emp_id: e['name'] = request.form.get('name', '').strip() e['pin'] = request.form.get('pin', '0000').strip() try: e['daily_rate'] = float(request.form.get('daily_rate', 0)) except: e['daily_rate'] = 230000 try: e['target_amount'] = float(request.form.get('target_amount', 0)) except: e['target_amount'] = 1500000 try: e['bonus_percentage'] = float(request.form.get('bonus_percentage', 0)) except: e['bonus_percentage'] = 10 break data['employees'] = employees save_data(data) elif action == 'delete_employee': emp_id = request.form.get('employee_id') data['employees'] = [e for e in employees if e.get('id') != emp_id] save_data(data) elif action == 'delete_order': order_id = request.form.get('order_id') if order_id in orders: del orders[order_id] data['orders'] = orders save_data(data) elif action == 'add_category': cat_name = request.form.get('category_name', '').strip() cat_icon = request.form.get('category_icon', 'fas fa-utensils').strip() if cat_name and not any(c.get('name') == cat_name for c in categories): categories.append({'name': cat_name, 'icon': cat_icon}) data['categories'] = categories save_data(data) elif action == 'edit_category': old_name = request.form.get('old_name') new_name = request.form.get('new_name', '').strip() new_icon = request.form.get('new_icon', 'fas fa-utensils').strip() if new_name: for c in categories: if c.get('name') == old_name: c['name'] = new_name c['icon'] = new_icon break for p in products: if p.get('category') == old_name: p['category'] = new_name data['categories'] = categories data['products'] = products save_data(data) elif action == 'delete_category': cat_name = request.form.get('category_name') data['categories'] = [c for c in categories if c.get('name') != cat_name] data['products'] = [p for p in products if p.get('category') != cat_name] save_data(data) elif action == 'add_product': name = request.form.get('name', '').strip() price = float(request.form.get('price', 0)) description = request.form.get('description', '').strip() category = request.form.get('category') uploaded_photos = request.files.getlist('photos')[:10] photos_list = [] if uploaded_photos and HF_TOKEN_WRITE: uploads_dir = 'uploads_temp' os.makedirs(uploads_dir, exist_ok=True) api = HfApi() for photo in uploaded_photos: if photo and photo.filename: ext = os.path.splitext(photo.filename)[1].lower() if ext not in ['.jpg', '.jpeg', '.png', '.webp', '.gif']: continue photo_filename = f"{uuid4().hex}{ext}" temp_path = os.path.join(uploads_dir, photo_filename) photo.save(temp_path) try: api.upload_file( path_or_fileobj=temp_path, path_in_repo=f"photos/{photo_filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE ) photos_list.append(photo_filename) except Exception: pass finally: if os.path.exists(temp_path): os.remove(temp_path) new_product = { 'product_id': uuid4().hex, 'name': name, 'price': price, 'description': description, 'category': category, 'photos': photos_list } products.append(new_product) data['products'] = products save_data(data) elif action == 'edit_product': pid = request.form.get('product_id') name = request.form.get('name', '').strip() price = float(request.form.get('price', 0)) description = request.form.get('description', '').strip() uploaded_photos = request.files.getlist('photos')[:10] photos_list = [] if uploaded_photos and uploaded_photos[0].filename and HF_TOKEN_WRITE: uploads_dir = 'uploads_temp' os.makedirs(uploads_dir, exist_ok=True) api = HfApi() for photo in uploaded_photos: if photo and photo.filename: ext = os.path.splitext(photo.filename)[1].lower() if ext not in ['.jpg', '.jpeg', '.png', '.webp', '.gif']: continue photo_filename = f"{uuid4().hex}{ext}" temp_path = os.path.join(uploads_dir, photo_filename) photo.save(temp_path) try: api.upload_file( path_or_fileobj=temp_path, path_in_repo=f"photos/{photo_filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE ) photos_list.append(photo_filename) except Exception: pass finally: if os.path.exists(temp_path): os.remove(temp_path) for p in products: if p.get('product_id') == pid: p['name'] = name p['price'] = price p['description'] = description if photos_list: p['photos'] = photos_list break data['products'] = products save_data(data) elif action == 'delete_product': pid = request.form.get('product_id') data['products'] = [p for p in products if p.get('product_id') != pid] save_data(data) return redirect(url_for('admin')) return render_template_string( ADMIN_TEMPLATE, products=products, categories=categories, orders=orders, employees=employees, repo_id=REPO_ID, currency_code=CURRENCY_CODE, settings=settings ) @app.route('/force_upload', methods=['POST']) def force_upload(): upload_db_to_hf() return redirect(url_for('admin')) @app.route('/force_download', methods=['POST']) def force_download(): download_db_from_hf() return redirect(url_for('admin')) if __name__ == '__main__': download_db_from_hf() load_data() if HF_TOKEN_WRITE: threading.Thread(target=periodic_backup, daemon=True).start() port = int(os.environ.get('PORT', 7860)) app.run(host='0.0.0.0', port=port)