diff --git a/app.py b/app.py index 5afb36c3c69d59b38a4590bae9009fe292b73c85..7dd244771cc73a56a08965d899d9e359ab8907a5 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,4 @@ import os -import base64 import json import threading import time @@ -8,21 +7,20 @@ from uuid import uuid4 import random import string import tempfile -import io -import math -from PIL import Image, ImageOps +import smtplib +from email.message import EmailMessage +import mimetypes from flask import Flask, render_template_string, request, redirect, url_for, flash, jsonify, session, Response 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_gippo_env' +app.secret_key = 'super_secret_key_mail_client_123' app.config.update( SESSION_COOKIE_SAMESITE='None', SESSION_COOKIE_SECURE=True, @@ -36,148 +34,30 @@ REPO_ID = os.getenv("REPO_ID", "Kgshop/fullmeta") HF_TOKEN_WRITE = os.getenv("HF_TOKEN") HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") -DEFAULT_WHATSAPP_NUMBER = "+77470623684" DEFAULT_LOGO_URL = "https://huggingface.co/spaces/Metapp/Tech/resolve/main/1776929812446-019db944-b5db-7524-8f44-73942d70a0f8.png" data_lock = threading.Lock() TRANSLATIONS = { 'ru': {}, - 'kk': { - 'Каталог': 'Каталог', - 'Поиск товаров...': 'Тауарларды іздеу...', - 'Поиск': 'Іздеу', - 'Ничего не найдено': 'Ештеңе табылмады', - 'В этой категории пока нет товаров': 'Бұл санатта әзірге тауарлар жоқ', - 'В упаковке:': 'Қаптамада:', - 'В коробке:': 'Қорапта:', - 'Мин. заказ:': 'Мин. тапсырыс:', - 'Нет в наличии': 'Қолжетімсіз', - 'Остаток:': 'Қалдық:', - 'От': 'Бастап', - 'шт': 'дана', - 'кор.': 'қор.', - 'уп.': 'қап.', - '+ Упаковка': '+ Қаптама', - '+ Коробка': '+ Қорап', - 'Сумма заказа:': 'Тапсырыс сомасы:', - 'Корзина': 'Себет', - 'Ваш заказ': 'Сіздің тапсырысыңыз', - 'Имя клиента (необязательно)': 'Клиенттің аты (міндетті емес)', - 'Ваше Имя': 'Сіздің атыңыз', - 'Номер телефона': 'Телефон нөмірі', - 'Город': 'Қала', - 'Адрес доставки': 'Жеткізу мекенжайы', - 'Индекс': 'Индекс', - 'Оформить заказ': 'Тапсырыс беру', - 'Оформление...': 'Рәсімдеу...', - 'Админ-панель': 'Әкімші тақтасы', - 'Отчеты': 'Есептер', - 'Остатки': 'Қалдықтар', - 'История накладных и заказов': 'Жүкқұжаттар мен тапсырыстар тарихы', - 'Сборка': 'Құрастыру', - 'В каталог': 'Каталогқа', - 'Выход': 'Шығу', - 'Сохранить на сервер': 'Серверге сақтау', - 'Скачать с сервера': 'Серверден жүктеу', - 'Онлайн заказы': 'Онлайн тапсырыстар', - 'Пользователи (Закрытый каталог)': 'Пайдаланушылар (Жабық каталог)', - 'Персонал': 'Қызметкерлер', - 'Настройка магазина': 'Дүкен параметрлері', - 'Управление категориями': 'Санаттарды басқару', - 'Добавить товар': 'Тауар қосу', - 'Название товара': 'Тауар атауы', - 'Наличие': 'Болуы', - 'Штрих-код': 'Штрих-код', - 'Цена за ед.': 'Бір данасының бағасы', - 'В уп. (шт)': 'Қаптамада (дана)', - 'В кор. (шт)': 'Қорапта (дана)', - 'Цена за упаковку': 'Қаптама бағасы', - 'Мин. заказ (шт)': 'Мин. тапсырыс (дана)', - 'Остаток на складе': 'Қоймадағы қалдық', - 'Описание товара': 'Тауар сипаттамасы', - 'Фотографии товара (до 10 шт)': 'Тауар фотосуреттері (10 дейін)', - 'Сохранить товар': 'Тауарды сақтау', - 'Да': 'Иә', - 'Нет': 'Жоқ', - 'Варианты товара': 'Тауар нұсқалары', - 'Язык интерфейса': 'Интерфейс тілі', - 'Учет коробок': 'Қораптар есебі', - 'Включить учет больших коробок': 'Үлкен қораптар есебін қосу', - 'Общая скидка на чек (сумма)': 'Чектегі жалпы жеңілдік (сома)', - 'WhatsApp клиента (напр. +77001234567) необязательно': 'Клиенттің WhatsApp (мыс. +77001234567) міндетті емес', - 'Режим кассы: Быстрое оформление': 'Касса режимі: Тез рәсімдеу', - 'Сохраненные кассы': 'Сақталған кассалар', - 'Новая касса': 'Жаңа касса', - 'Свой товар': 'Өз тауарыңыз' - }, - 'uz': { - 'Каталог': 'Katalog', - 'Поиск товаров...': 'Tovarlarni qidirish...', - 'Поиск': 'Qidirish', - 'Ничего не найдено': 'Hech narsa topilmadi', - 'В этой категории пока нет товаров': 'Ushbu toifada hozircha tovarlar yoʻq', - 'В упаковке:': 'Oʻramda:', - 'В коробке:': 'Qutida:', - 'Мин. заказ:': 'Min. buyurtma:', - 'Нет в наличии': 'Mavjud emas', - 'Остаток:': 'Qoldiq:', - 'От': 'Dan', - 'шт': 'dona', - 'кор.': 'quti', - 'уп.': 'oʻram', - '+ Упаковка': '+ Oʻram', - '+ Коробка': '+ Quti', - 'Сумма заказа:': 'Buyurtma summasi:', - 'Корзина': 'Savat', - 'Ваш заказ': 'Sizning buyurtmangiz', - 'Имя клиента (необязательно)': 'Mijoz ismi (majburiy emas)', - 'Ваше Имя': 'Ismingiz', - 'Номер телефона': 'Telefon raqami', - 'Город': 'Shahar', - 'Адрес доставки': 'Yetkazib berish manzili', - 'Индекс': 'Indeks', - 'Оформить заказ': 'Buyurtma berish', - 'Оформление...': 'Rasmiylashtirilmoqda...', - 'Админ-панель': 'Admin panel', - 'Отчеты': 'Hisobotlar', - 'Остатки': 'Qoldiqlar', - 'История накладных и заказов': 'Yukxat va buyurtmalar tarixi', - 'Сборка': 'Yigʻish', - 'В каталог': 'Katalogga', - 'Выход': 'Chiqish', - 'Сохранить на сервер': 'Serverga saqlash', - 'Скачать с сервера': 'Serverdan yuklab olish', - 'Онлайн заказы': 'Onlayn buyurtmalar', - 'Пользователи (Закрытый каталог)': 'Foydalanuvchilar (Yopiq katalog)', - 'Персонал': 'Xodimlar', - 'Настройка магазина': 'Do\'kon sozlamalari', - 'Управление категориями': 'Toifalarni boshqarish', - 'Добавить товар': 'Tovar qo\'shish', - 'Название товара': 'Tovar nomi', - 'Наличие': 'Mavjudligi', - 'Штрих-код': 'Shtrix-kod', - 'Цена за ед.': 'Dona narxi', - 'В уп. (шт)': 'O\'ramda (dona)', - 'В кор. (шт)': 'Qutida (dona)', - 'Цена за упаковку': 'O\'ram narxi', - 'Мин. заказ (шт)': 'Min. buyurtma (dona)', - 'Остаток на складе': 'Ombordagi qoldiq', - 'Описание товара': 'Tovar tavsifi', - 'Фотографии товара (до 10 шт)': 'Tovar rasmlari (10 tagacha)', - 'Сохранить товар': 'Tovarni saqlash', - 'Да': 'Ha', - 'Нет': 'Yo\'q', - 'Варианты товара': 'Tovar variantlari', - 'Язык интерфейса': 'Interfeys tili', - 'Учет коробок': 'Qutilar hisobi', - 'Включить учет больших коробок': 'Katta qutilar hisobini yoqish', - 'Общая скидка на чек (сумма)': 'Chek uchun umumiy chegirma (summa)', - 'WhatsApp клиента (напр. +77001234567) необязательно': 'Mijoz WhatsApp (mas. +77001234567) majburiy emas', - 'Режим кассы: Быстрое оформление': 'Kassa rejimi: Tez rasmiylashtirish', - 'Сохраненные кассы': 'Saqlangan kassalar', - 'Новая касса': 'Yangi kassa', - 'Свой товар': 'O\'z tovaringiz' + 'en': { + 'Почта': 'Mail', + 'Входящие': 'Inbox', + 'Отправленные': 'Sent', + 'Написать': 'Compose', + 'Переслать': 'Forward', + 'Ответить': 'Reply', + 'Отправить': 'Send', + 'Кому': 'To', + 'Тема': 'Subject', + 'Текст письма': 'Message body', + 'Прикрепить файлы': 'Attach files', + 'Настройки': 'Settings', + 'Выход': 'Logout', + 'Панель управления': 'Control Panel', + 'Ничего не найдено': 'Nothing found', + 'Удалить': 'Delete', + 'Отмена': 'Cancel' } } @@ -263,29 +143,6 @@ def upload_db_to_hf(specific_file=None): except Exception: pass -def process_and_upload_image(file_obj, repo_path, size=(512, 512)): - if not HF_TOKEN_WRITE: - return None - try: - img = Image.open(file_obj).convert('RGB') - img = ImageOps.fit(img, size, Image.Resampling.LANCZOS) - buf = io.BytesIO() - img.save(buf, format='JPEG', quality=85) - buf.seek(0) - - filename = f"{uuid4().hex}.jpg" - api = HfApi() - api.upload_file( - path_or_fileobj=buf, - path_in_repo=f"{repo_path}/{filename}", - repo_id=REPO_ID, - repo_type="dataset", - token=HF_TOKEN_WRITE - ) - return filename - except Exception: - return None - def periodic_backup(): while True: time.sleep(1800) @@ -309,158 +166,47 @@ def load_data(): else: data = {} - if 'products' in data or 'categories' in data: + if 'emails' in data or 'contacts' in data: data = { 'default_env': { - 'products': data.get('products', []), - 'categories': data.get('categories',[]), - 'category_photos': data.get('category_photos', {}), - 'orders': data.get('orders', {}), - 'staff': [], - 'catalog_users': [], - 'inventory_history':[], - 'purchase_orders': [], - 'debt_history': [], + 'emails': data.get('emails', []), + 'contacts': data.get('contacts', []), 'settings': { - 'organization_name': 'Default Shop', + 'app_name': 'Default Mail', 'admin_password_enabled': False, 'admin_password': '', 'logo_url': DEFAULT_LOGO_URL, - 'qr_code_1_url': '', - 'qr_code_2_url': '', - 'catalog_subtitle': '', - 'whatsapp_number': DEFAULT_WHATSAPP_NUMBER, - 'order_messenger': 'wa', - 'order_contact': DEFAULT_WHATSAPP_NUMBER, - 'invoice_contacts': '', - 'currency': '₸', - 'track_inventory': False, - 'use_barcodes': False, - 'use_master_boxes': False, - 'margin_tracking': False, + 'smtp_server': '', + 'smtp_port': 587, + 'smtp_user': '', + 'smtp_pass': '', 'language': 'ru', - 'business_type': 'mixed', - 'system_mode': 'both', - 'hide_stock_online': False, - 'closed_catalog_enabled': False, - 'commission_enabled': False, - 'commission_percent': 0.0, 'theme': 'light', - 'is_deleted': False, - 'customer_fields': { - 'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False - }, - 'socials': { - 'wa': {'enabled': True, 'url': 'https://wa.me/77011333885'}, - 'ig': {'enabled': True, 'url': 'https://instagram.com/14sklad_baisat'}, - 'tg': {'enabled': True, 'url': 'https://t.me/posuda15konteiner'}, - 'kaspi': {'enabled': False, 'url': ''} - } + 'is_deleted': False } } } changed = False for env_id, env_data in data.items(): - if 'products' not in env_data: env_data['products'] =[] - if 'categories' not in env_data: env_data['categories'] =[] - if 'category_photos' not in env_data: env_data['category_photos'] = {} - if 'orders' not in env_data: env_data['orders'] = {} - if 'staff' not in env_data: env_data['staff'] = [] - if 'catalog_users' not in env_data: env_data['catalog_users'] = [] - if 'inventory_history' not in env_data: env_data['inventory_history'] = [] - if 'purchase_orders' not in env_data: env_data['purchase_orders'] = []; changed = True - if 'debt_history' not in env_data: env_data['debt_history'] = []; changed = True + if 'emails' not in env_data: env_data['emails'] = []; changed = True + if 'contacts' not in env_data: env_data['contacts'] = []; changed = True if 'settings' not in env_data: env_data['settings'] = {} changed = True settings = env_data['settings'] - if 'organization_name' not in settings: settings['organization_name'] = f'Shop {env_id}'; changed = True + if 'app_name' not in settings: settings['app_name'] = f'Mail {env_id}'; changed = True if 'admin_password_enabled' not in settings: settings['admin_password_enabled'] = False; changed = True if 'admin_password' not in settings: settings['admin_password'] = ''; changed = True if 'logo_url' not in settings: settings['logo_url'] = DEFAULT_LOGO_URL; changed = True - if 'qr_code_1_url' not in settings: settings['qr_code_1_url'] = ''; changed = True - if 'qr_code_2_url' not in settings: settings['qr_code_2_url'] = ''; changed = True - if 'catalog_subtitle' not in settings: settings['catalog_subtitle'] = ''; changed = True - if 'whatsapp_number' not in settings: settings['whatsapp_number'] = DEFAULT_WHATSAPP_NUMBER; changed = True - if 'order_messenger' not in settings: settings['order_messenger'] = 'wa'; changed = True - if 'order_contact' not in settings: settings['order_contact'] = settings.get('whatsapp_number', DEFAULT_WHATSAPP_NUMBER); changed = True - if 'invoice_contacts' not in settings: settings['invoice_contacts'] = ''; changed = True - if 'is_deleted' not in settings: settings['is_deleted'] = False; changed = True - - if 'currency' not in settings: settings['currency'] = '₸'; changed = True - elif settings['currency'] == 'T': settings['currency'] = '₸'; changed = True - - if 'track_inventory' not in settings: settings['track_inventory'] = False; changed = True - if 'use_barcodes' not in settings: settings['use_barcodes'] = False; changed = True - if 'use_master_boxes' not in settings: settings['use_master_boxes'] = False; changed = True - if 'margin_tracking' not in settings: settings['margin_tracking'] = False; changed = True + if 'smtp_server' not in settings: settings['smtp_server'] = ''; changed = True + if 'smtp_port' not in settings: settings['smtp_port'] = 587; changed = True + if 'smtp_user' not in settings: settings['smtp_user'] = ''; changed = True + if 'smtp_pass' not in settings: settings['smtp_pass'] = ''; changed = True if 'language' not in settings: settings['language'] = 'ru'; changed = True - if 'business_type' not in settings: settings['business_type'] = 'mixed'; changed = True - if 'system_mode' not in settings: settings['system_mode'] = 'both'; changed = True - if 'hide_stock_online' not in settings: settings['hide_stock_online'] = False; changed = True - if 'closed_catalog_enabled' not in settings: settings['closed_catalog_enabled'] = False; changed = True - if 'commission_enabled' not in settings: settings['commission_enabled'] = False; changed = True - if 'commission_percent' not in settings: settings['commission_percent'] = 0.0; changed = True if 'theme' not in settings: settings['theme'] = 'light'; changed = True - if 'customer_fields' not in settings: - settings['customer_fields'] = {'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False} - changed = True - if 'socials' not in settings: - settings['socials'] = { - 'wa': {'enabled': True, 'url': 'https://wa.me/77011333885'}, - 'ig': {'enabled': True, 'url': 'https://instagram.com/14sklad_baisat'}, - 'tg': {'enabled': True, 'url': 'https://t.me/posuda15konteiner'}, - 'kaspi': {'enabled': False, 'url': ''} - } - changed = True - else: - if 'kaspi' not in settings['socials']: - settings['socials']['kaspi'] = {'enabled': False, 'url': ''} - changed = True - - for product in env_data['products']: - if 'created_at' not in product: product['created_at'] = '2000-01-01 00:00:00'; changed = True - if 'product_id' not in product: product['product_id'] = uuid4().hex; changed = True - if 'pieces_per_box' not in product: product['pieces_per_box'] = ""; changed = True - if 'pieces_per_master_box' not in product: product['pieces_per_master_box'] = ""; changed = True - if 'box_price' not in product: product['box_price'] = ""; changed = True - if 'min_order' not in product: product['min_order'] = ""; changed = True - if 'barcode' not in product: product['barcode'] = ""; changed = True - if 'cost_price' not in product: product['cost_price'] = ""; changed = True - if 'variants' not in product: product['variants'] =[]; changed = True - if 'has_variant_prices' not in product: product['has_variant_prices'] = False; changed = True - if 'stock' not in product: product['stock'] = ""; changed = True - if 'is_available' not in product: product['is_available'] = True; changed = True - if 'wholesale_tiers' not in product: product['wholesale_tiers'] = []; changed = True - - for v in product['variants']: - if 'stock' not in v: v['stock'] = ""; changed = True - if 'box_price' not in v: v['box_price'] = ""; changed = True - if 'cost_price' not in v: v['cost_price'] = ""; changed = True - if 'barcode' not in v: v['barcode'] = ""; changed = True - if 'pieces_per_box' not in v: v['pieces_per_box'] = product.get('pieces_per_box', ""); changed = True - if 'pieces_per_master_box' not in v: v['pieces_per_master_box'] = product.get('pieces_per_master_box', ""); changed = True - if 'is_available' not in v: v['is_available'] = True; changed = True - if 'wholesale_tiers' not in v: v['wholesale_tiers'] = []; changed = True - if 'photo' not in v: v['photo'] = ""; changed = True - - for order_id, order in env_data['orders'].items(): - if 'status' not in order: order['status'] = 'confirmed'; changed = True - if 'staff_name' not in order: order['staff_name'] = ''; changed = True - if 'assembled' not in order: order['assembled'] = {}; changed = True - if 'global_discount' not in order: order['global_discount'] = 0; changed = True - if 'commission_amount' not in order: order['commission_amount'] = 0; changed = True - if 'is_debt' not in order: order['is_debt'] = False; changed = True - if 'paid_amount' not in order: order['paid_amount'] = order.get('total_price', 0); changed = True - if 'debt_amount' not in order: order['debt_amount'] = 0; changed = True - for item in order.get('cart', []): - if 'discount' not in item: item['discount'] = 0; changed = True - if 'cost_price' not in item: item['cost_price'] = 0; changed = True - if 'category' not in item: item['category'] = 'Без категории'; changed = True - if 'wholesale_tiers' not in item: item['wholesale_tiers'] = []; changed = True - if 'pieces_per_master_box' not in item: item['pieces_per_master_box'] = 1; changed = True + if 'is_deleted' not in settings: settings['is_deleted'] = False; changed = True if changed or not os.path.exists(DATA_FILE): try: @@ -490,48 +236,29 @@ def get_env_data(env_id): all_data = load_data() if env_id not in all_data: all_data[env_id] = { - 'products': [], - 'categories':[], - 'category_photos': {}, - 'orders': {}, - 'staff': [], - 'catalog_users': [], - 'inventory_history':[], - 'purchase_orders': [], - 'debt_history': [], + 'emails': [{ + 'id': uuid4().hex, + 'type': 'inbox', + 'from': 'system@mail.local', + 'to': 'you', + 'subject': 'Добро пожаловать!', + 'body': 'Это тестовое приветственное письмо. Вы можете переслать его или удалить.', + 'date': get_almaty_time(), + 'attachments': [] + }], + 'contacts': [], 'settings': { - 'organization_name': f'Shop {env_id}', + 'app_name': f'Mail {env_id}', 'admin_password_enabled': False, 'admin_password': '', 'logo_url': DEFAULT_LOGO_URL, - 'qr_code_1_url': '', - 'qr_code_2_url': '', - 'catalog_subtitle': '', - 'whatsapp_number': DEFAULT_WHATSAPP_NUMBER if 'DEFAULT_WHATSAPP_NUMBER' in globals() else "+77470623684", - 'order_messenger': 'wa', - 'order_contact': DEFAULT_WHATSAPP_NUMBER if 'DEFAULT_WHATSAPP_NUMBER' in globals() else "+77470623684", - 'invoice_contacts': '', - 'currency': '₸', - 'track_inventory': False, - 'use_barcodes': False, - 'use_master_boxes': False, - 'margin_tracking': False, + 'smtp_server': '', + 'smtp_port': 587, + 'smtp_user': '', + 'smtp_pass': '', 'language': 'ru', - 'business_type': 'mixed', - 'system_mode': 'both', - 'hide_stock_online': False, - 'closed_catalog_enabled': False, - 'commission_enabled': False, - 'commission_percent': 0.0, 'theme': 'light', - 'is_deleted': False, - 'customer_fields': {'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False}, - 'socials': { - 'wa': {'enabled': True, 'url': ''}, - 'ig': {'enabled': True, 'url': ''}, - 'tg': {'enabled': True, 'url': ''}, - 'kaspi': {'enabled': False, 'url': ''} - } + 'is_deleted': False } } save_data(all_data) @@ -550,118 +277,7 @@ def check_deleted_env(): return all_data = load_data() if env_id in all_data and all_data[env_id].get('settings', {}).get('is_deleted', False): - return "
Создавайте независимые почтовые интерфейсы для ваших нужд.
+ Войти в управление +