diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -28,9 +28,6 @@ HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") STORE_ADDRESS = "Город Алматы, Рынок Олжа, ряд VIP, Бутик 7" -CURRENCY_CODE = 'USD' -CURRENCY_NAME = 'доллар' - DOWNLOAD_RETRIES = 3 DOWNLOAD_DELAY = 5 @@ -38,10 +35,11 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %( translations = { 'ru': { - 'site_title': "Manolisa - Каталог", - 'brand_name': "Manolisa", + 'site_title': "ManolisA - Каталог", + 'brand_name': "ManolisA", 'our_address': "Наш адрес:", 'all_categories': "Все категории", + 'all_seasons': "Все сезоны", 'search_placeholder': "Поиск по названию или описанию...", 'no_products_added': "Товары пока не добавлены.", 'no_products_found': "По вашему запросу товары не найдены.", @@ -52,7 +50,7 @@ translations = { 'details': "Подробнее", 'add_to_cart': "В корзину", 'loading': "Загрузка...", - 'specify_quantity_color': "Укажите количество и цвет", + 'specify_quantity_color': "Укажите количество", 'quantity': "Количество (линеек):", 'color_variant': "Цвет/Вариант:", 'confirm_add_to_cart': "Добавить в корзину", @@ -73,6 +71,8 @@ translations = { 'order_creation_error': "Ошибка при формировании заказа:", 'category': "Категория:", 'no_category': "Без категории", + 'season': "Сезон:", + 'no_season': "Без сезона", 'price': "Цена:", 'description': "Описание:", 'no_description': "Описание отсутствует.", @@ -86,7 +86,7 @@ translations = { 'order_status_desc': "Этот заказ был оформлен без входа в систему. Пожалуйста, свяжитесь с нами по WhatsApp для подтверждения и уточнения деталей.", 'send_order': "Отправить заказ", 'back_to_catalog': "← Вернуться в каталог", - 'whatsapp_greeting': "Здравствуйте! Хочу подтвердить свой заказ на Manolisa:", + 'whatsapp_greeting': "Здравствуйте! Хочу подтвердить свой заказ на ManolisA:", 'whatsapp_order_number': "Номер заказа:", 'whatsapp_order_link': "Ссылка на заказ:", 'whatsapp_contact_me': "Пожалуйста, свяжитесь со мной для уточнения деталей оплаты и доставки.", @@ -101,12 +101,15 @@ translations = { 'online_order': 'Онлайн', 'address_1_title': 'Адрес:', 'address_1_detail': 'Город Алматы, Рынок Олжа, ряд VIP, Бутик 7', + 'currency_usd': '$', + 'currency_kzt': '₸', }, 'kk': { - 'site_title': "Manolisa - Каталог", - 'brand_name': "Manolisa", + 'site_title': "ManolisA - Каталог", + 'brand_name': "ManolisA", 'our_address': "Біздің мекенжайымыз:", 'all_categories': "Барлық санаттар", + 'all_seasons': "Барлық маусымдар", 'search_placeholder': "Аты немесе сипаттамасы бойынша іздеу...", 'no_products_added': "Тауарлар әлі қосылмаған.", 'no_products_found': "Сіздің сұранысыңыз бойынша тауарлар табылмады.", @@ -117,7 +120,7 @@ translations = { 'details': "Толығырақ", 'add_to_cart': "Себетке қосу", 'loading': "Жүктелуде...", - 'specify_quantity_color': "Саны мен түсін көрсетіңіз", + 'specify_quantity_color': "Санын көрсетіңіз", 'quantity': "Саны (лента):", 'color_variant': "Түсі/нұсқасы:", 'confirm_add_to_cart': "Себетке қосу", @@ -138,6 +141,8 @@ translations = { 'order_creation_error': "Тапсырысты рәсімдеу кезінде қате:", 'category': "Санат:", 'no_category': "Санатсыз", + 'season': "Маусым:", + 'no_season': "Маусымсыз", 'price': "Бағасы:", 'description': "Сипаттамасы:", 'no_description': "Сипаттамасы жоқ.", @@ -151,7 +156,7 @@ translations = { 'order_status_desc': "Бұл тапсырыс жүйеге кірмей рәсімделді. Растау және мәліметтерді нақтылау үшін WhatsApp арқылы бізбен хабарласыңыз.", 'send_order': "Тапсырысты жіберу", 'back_to_catalog': "← Каталогқа оралу", - 'whatsapp_greeting': "Сәлеметсіз бе! Мен Manolisa-дегі тапсырысымды растағым келеді:", + 'whatsapp_greeting': "Сәлеметсіз бе! Мен ManolisA-дегі тапсырысымды растағым келеді:", 'whatsapp_order_number': "Тапсырыс нөмірі:", 'whatsapp_order_link': "Тапсырысқа сілтеме:", 'whatsapp_contact_me': "Төлем және жеткізу мәліметтерін нақтылау үшін менімен хабарласыңыз.", @@ -166,6 +171,8 @@ translations = { 'online_order': 'Онлайн', 'address_1_title': 'Мекенжай:', 'address_1_detail': 'Алматы қаласы, Олжа базары, VIP қатары, 7 бутик', + 'currency_usd': '$', + 'currency_kzt': '₸', } } @@ -225,7 +232,7 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN try: if file_name == DATA_FILE: with open(file_name, 'w', encoding='utf-8') as f: - json.dump({'products': [], 'categories': [], 'orders': {}, 'employees': []}, f) + json.dump({'products': [], 'categories': [], 'seasons': [], 'orders': {}, 'employees': [], 'settings': {'usd_kzt_rate': 450.0}}, f) logging.info(f"Created empty local file {file_name} because it was not found on HF.") except Exception as create_e: logging.error(f"Failed to create empty local file {file_name}: {create_e}") @@ -286,10 +293,28 @@ def periodic_backup(): upload_db_to_hf() logging.info("Periodic backup finished.") - +def migrate_data_structure(data): + migrated = False + if 'products' in data and isinstance(data['products'], list): + for product in data['products']: + if 'photos' in product and 'variants' not in product: + migrated = True + colors = product.pop('colors', []) + photos = product.pop('photos', []) + product['variants'] = [] + + if colors and any(c.strip() for c in colors): + for color in colors: + if color.strip(): + product['variants'].append({"color": color.strip(), "photos": list(photos)}) + else: + product['variants'].append({"color": "Default", "photos": photos}) + if migrated: + logging.info("Migrated old product data structure to new variant-based structure.") + return data, migrated def load_data(): - default_data = {'products': [], 'categories': [], 'orders': {}, 'employees': []} + default_data = {'products': [], 'categories': [], 'seasons': [], 'orders': {}, 'employees': [], 'settings': {'usd_kzt_rate': 450.0}} try: with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) @@ -297,11 +322,21 @@ def load_data(): if not isinstance(data, dict): logging.warning(f"Local {DATA_FILE} is not a dictionary. Attempting download.") raise FileNotFoundError + + # Check for all keys if 'products' not in data: data['products'] = [] if 'categories' not in data: data['categories'] = [] + if 'seasons' not in data: data['seasons'] = [] if 'orders' not in data: data['orders'] = {} if 'employees' not in data: data['employees'] = [] + if 'settings' not in data: data['settings'] = {'usd_kzt_rate': 450.0} + if 'usd_kzt_rate' not in data['settings']: data['settings']['usd_kzt_rate'] = 450.0 + + data, migrated = migrate_data_structure(data) + if migrated: + save_data(data) return data + except FileNotFoundError: logging.warning(f"Local file {DATA_FILE} not found. Attempting download from HF.") @@ -313,11 +348,20 @@ def load_data(): if not isinstance(data, dict): logging.error(f"Downloaded {DATA_FILE} is not a dictionary. Using default.") return default_data + if 'products' not in data: data['products'] = [] if 'categories' not in data: data['categories'] = [] + if 'seasons' not in data: data['seasons'] = [] if 'orders' not in data: data['orders'] = {} if 'employees' not in data: data['employees'] = [] + if 'settings' not in data: data['settings'] = {'usd_kzt_rate': 450.0} + if 'usd_kzt_rate' not in data['settings']: data['settings']['usd_kzt_rate'] = 450.0 + + data, migrated = migrate_data_structure(data) + if migrated: + save_data(data) return data + except FileNotFoundError: logging.error(f"File {DATA_FILE} still not found even after download reported success. Using default.") return default_data @@ -343,11 +387,7 @@ def save_data(data): if not isinstance(data, dict): logging.error("Attempted to save invalid data structure (not a dict). Aborting save.") 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'] = [] - + with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) logging.info(f"Data successfully saved to {DATA_FILE}") @@ -365,84 +405,90 @@ CATALOG_TEMPLATE = '''
{{ _('total_to_pay') }}: {{ "%.2f"|format(order.total_price) }} {{ currency_code }}
+{{ _('total_to_pay') }}: {{ "%.2f"|format(order.total_price) }} USD
{{ _('order_not_found') }}
{{ _('back_to_catalog') }} {% endif %} @@ -1200,383 +1296,236 @@ ADMIN_TEMPLATE = ''' -Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.
Категорий пока нет.
- {% endif %} -Категорий нет.
{% endfor %}Сотрудников пока нет.
- {% endif %} -Сезонов нет.
{% endfor %}Сотрудников нет.
{% endfor %}
- {% endif %}
+
{% endif %}
Категория: {{ product.get('category', 'Без категории') }}
-Цена за линейку: {{ "%.2f"|format(product['price']) }} {{ currency_code }}
-Шт. в линейке: {{ product.get('items_per_line', 'N/A') }}
-Описание: {{ product.get('description', 'N/A')[:150] }}{% if product.get('description', '')|length > 150 %}...{% endif %}
- {% set colors = product.get('colors', []) %} -Цвета/Вар-ты: {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}
- {% if product.get('photos') and product['photos']|length > 1 %} -(Всего фото: {{ product['photos']|length }})
- {% endif %} +Категория: {{ product.get('category', 'N/A') }}
+Сезон: {{ product.get('season', 'N/A') }}
+Цена: {{ "%.2f"|format(product['price']) }} USD ({{ product.get('items_per_line', 'N/A') }} шт.)
+Цвета: {{ product.variants|map(attribute='color')|join(', ') }}
Товаров пока нет.
- {% endif %}