Kgshop commited on
Commit
5e0eaad
·
verified ·
1 Parent(s): 33f82ae

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -732
app.py CHANGED
@@ -3572,738 +3572,7 @@ def inventory_action(env_id):
3572
  'id': uuid4().hex,
3573
  'product_id': p_id,
3574
  'tag_id': t_id,
3575
- 'type': action,for batch in tag['stock_batches']:
3576
- if batch['qty'] > 0:
3577
- if batch['qty'] >= remaining_to_deduct:
3578
- batch['qty'] -= remaining_to_deduct
3579
- remaining_to_deduct = 0
3580
- break
3581
- else:
3582
- remaining_to_deduct -= batch['qty']
3583
- batch['qty'] = 0
3584
-
3585
- tag['stock'] -= qty
3586
- history_entry['details'] = 'Ручное списание'
3587
- update_tag_price_from_batches(tag)
3588
-
3589
- else:
3590
- return jsonify({"error": "Неизвестное действие"}), 400
3591
-
3592
- if 'inventory_history' not in data:
3593
- data['inventory_history'] = []
3594
- data['inventory_history'].append(history_entry)
3595
-
3596
- save_env_data(env_id, data)
3597
- return jsonify({"success": True})
3598
-
3599
- @app.route('/<env_id>/inventory_history/<p_id>/<t_id>')
3600
- def inventory_history(env_id, p_id, t_id):
3601
- data = get_env_data(env_id)
3602
- history = data.get('inventory_history',[])
3603
- item_history =[h for h in history if h.get('product_id') == p_id and h.get('tag_id') == t_id]
3604
- item_history.sort(key=lambda x: x['timestamp'], reverse=True)
3605
- return jsonify(item_history)
3606
-
3607
- @app.route('/<env_id>/reports')
3608
- def reports_page(env_id):
3609
- data = get_env_data(env_id)
3610
- settings = data.get('settings', {})
3611
- if settings.get('env_mode') != '2in1':
3612
- return "Отчеты доступны только в режиме '2 в 1'", 403
3613
- if settings.get('admin_password_enabled') and not session.get(f'admin_auth_{env_id}'):
3614
- return redirect(url_for('admin_login', env_id=env_id))
3615
-
3616
- now = datetime.now(ALMATY_TZ)
3617
- default_start = now.replace(day=1).strftime('%Y-%m-%d')
3618
- default_end = now.strftime('%Y-%m-%d')
3619
-
3620
- start_date = request.args.get('start_date', default_start)
3621
- end_date = request.args.get('end_date', default_end)
3622
-
3623
- orders = data.get('orders', {}).values()
3624
- filtered_orders =[]
3625
-
3626
- for o in orders:
3627
- created_at = o.get('created_at', '')
3628
- if created_at:
3629
- date_part = created_at.split(' ')[0]
3630
- if start_date <= date_part <= end_date:
3631
- filtered_orders.append(o)
3632
-
3633
- total_orders = len(filtered_orders)
3634
- total_revenue = sum(o.get('total_price', 0) for o in filtered_orders)
3635
- pos_orders = sum(1 for o in filtered_orders if o.get('source') == 'pos')
3636
- online_orders = total_orders - pos_orders
3637
-
3638
- emp_stats = {}
3639
- product_sales = {}
3640
-
3641
- for o in filtered_orders:
3642
- emp = o.get('employee_name') or 'Прямой заказ'
3643
- if emp not in emp_stats:
3644
- emp_stats[emp] = {'count': 0, 'revenue': 0}
3645
- emp_stats[emp]['count'] += 1
3646
- emp_stats[emp]['revenue'] += o.get('total_price', 0)
3647
-
3648
- for item in o.get('cart',[]):
3649
- name = item.get('name', 'Неизвестно')
3650
- qty = item.get('quantity', 0)
3651
- if name not in product_sales:
3652
- product_sales[name] = 0
3653
- product_sales[name] += qty
3654
-
3655
- top_products =[{'name': k, 'qty': v} for k, v in product_sales.items()]
3656
- top_products.sort(key=lambda x: x['qty'], reverse=True)
3657
-
3658
- return render_template_string(
3659
- REPORTS_TEMPLATE, env_id=env_id, settings=settings, currency_code=settings.get('currency_code', 'KGS'),
3660
- total_orders=total_orders, total_revenue=total_revenue, pos_orders=pos_orders, online_orders=online_orders,
3661
- emp_stats=emp_stats, top_products=top_products[:20], start_date=start_date, end_date=end_date
3662
- )
3663
-
3664
- @app.route('/<env_id>/track_view/<product_id>', methods=['POST'])
3665
- def track_view(env_id, product_id):
3666
- data = get_env_data(env_id)
3667
- for p in data['products']:
3668
- if p.get('product_id') == product_id:
3669
- p['views'] = p.get('views', 0) + 1
3670
- break
3671
- save_env_data(env_id, data)
3672
- return jsonify({"status": "ok"})
3673
-
3674
- @app.route('/<env_id>/product/<product_id>')
3675
- def product_detail(env_id, product_id):
3676
- data = get_env_data(env_id)
3677
- all_products_raw = data.get('products',[])
3678
- settings = data.get('settings', {})
3679
- env_mode = settings.get('env_mode', 'external')
3680
-
3681
- products_in_stock =[]
3682
- for p in all_products_raw:
3683
- if not p.get('in_stock', True):
3684
- continue
3685
- if env_mode == '2in1':
3686
- valid_tags =[t for t in p.get('tags', []) if t.get('stock', 0) > 0]
3687
- if not valid_tags and p.get('tags',[]):
3688
- continue
3689
- p_copy = p.copy()
3690
- p_copy['tags'] = valid_tags
3691
- products_in_stock.append(p_copy)
3692
- else:
3693
- products_in_stock.append(p)
3694
-
3695
- products_sorted = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
3696
-
3697
- product = next((p for p in products_sorted if p.get('product_id') == product_id), None)
3698
- if not product:
3699
- return "Товар не найден или отсутствует в наличии.", 404
3700
-
3701
- return render_template_string(
3702
- PRODUCT_DETAIL_TEMPLATE, product=product, repo_id=REPO_ID,
3703
- currency_code=settings.get('currency_code', 'KGS'), settings=settings, env_id=env_id
3704
- )
3705
-
3706
- @app.route('/<env_id>/create_order', methods=['POST'])
3707
- def create_order(env_id):
3708
- order_data = request.get_json()
3709
- if not order_data or 'cart' not in order_data or not order_data['cart']:
3710
- return jsonify({"error": "Корзина пуста или не передана."}), 400
3711
-
3712
- data = get_env_data(env_id)
3713
- settings = data.get('settings', {})
3714
- products = data.get('products',[])
3715
- env_mode = settings.get('env_mode', 'external')
3716
-
3717
- cart_items = order_data['cart']
3718
- customer_data = order_data.get('customer_data', {})
3719
- emp_id = order_data.get('emp_id')
3720
- source = order_data.get('source', 'catalog')
3721
- emp_name = None
3722
- emp_whatsapp = None
3723
-
3724
- if emp_id:
3725
- employees = data.get('employees',[])
3726
- for emp in employees:
3727
- if emp.get('id') == emp_id:
3728
- emp_name = emp.get('name')
3729
- emp_whatsapp = emp.get('whatsapp')
3730
- break
3731
-
3732
- total_price = 0
3733
- processed_cart =[]
3734
- order_timestamp = datetime.now(ALMATY_TZ).strftime('%Y-%m-%d %H:%M:%S')
3735
-
3736
- for item in cart_items:
3737
- if not all(k in item for k in ('name', 'quantity')):
3738
- return jsonify({"error": "Неверный формат товара в корзине."}), 400
3739
- try:
3740
- quantity = int(item['quantity'])
3741
- if quantity <= 0:
3742
- raise ValueError("Invalid quantity")
3743
-
3744
- p_id = item.get('product_id')
3745
- c_color = item.get('color', 'N/A')
3746
- tx = item.get('tag_x')
3747
- ty = item.get('tag_y')
3748
- u_type = item.get('unit_type', 'piece')
3749
-
3750
- product_ref = next((p for p in products if p.get('product_id') == p_id), None)
3751
- if not product_ref:
3752
- return jsonify({"error": f"Товар {p_id} не найден."}), 400
3753
-
3754
- tag_ref = None
3755
- if 'TAG_' in c_color:
3756
- tag_id = c_color.split('_VAR_')[0].replace('TAG_', '')
3757
- tag_ref = next((t for t in product_ref.get('tags',[]) if t.get('id') == tag_id), None)
3758
- elif item.get('id') and len(item['id'].split('-')) >= 2:
3759
- tag_id = item['id'].split('-')[1]
3760
- tag_ref = next((t for t in product_ref.get('tags',[]) if t.get('id') == tag_id), None)
3761
-
3762
- price = float(item.get('price', 0))
3763
- discount_applied = False
3764
-
3765
- if tag_ref:
3766
- orig_price = float(tag_ref.get('price', 0))
3767
- box_price = float(tag_ref.get('box_price', orig_price))
3768
- box_qty = int(tag_ref.get('box_qty', 1))
3769
-
3770
- if u_type == 'piece' and box_qty > 1 and quantity >= box_qty:
3771
- price = box_price / box_qty
3772
- discount_applied = True
3773
- elif u_type == 'box':
3774
- price = box_price
3775
- else:
3776
- price = orig_price
3777
-
3778
- if env_mode == '2in1':
3779
- deduction = quantity
3780
- if u_type == 'box':
3781
- deduction = quantity * box_qty
3782
-
3783
- if tag_ref.get('stock', 0) < deduction:
3784
- return jsonify({"error": f"Недостаточно остатков для товара {item['name']}."}), 400
3785
-
3786
- if 'stock_batches' not in tag_ref:
3787
- tag_ref['stock_batches'] =[{"qty": tag_ref.get('stock', 0), "price": tag_ref.get('price', 0), "box_price": tag_ref.get('box_price', 0)}]
3788
-
3789
- remaining_to_deduct = deduction
3790
- for batch in tag_ref['stock_batches']:
3791
- if batch['qty'] > 0:
3792
- if batch['qty'] >= remaining_to_deduct:
3793
- batch['qty'] -= remaining_to_deduct
3794
- remaining_to_deduct = 0
3795
- break
3796
- else:
3797
- remaining_to_deduct -= batch['qty']
3798
- batch['qty'] = 0
3799
-
3800
- tag_ref['stock'] -= deduction
3801
- update_tag_price_from_batches(tag_ref)
3802
-
3803
- if 'inventory_history' not in data:
3804
- data['inventory_history'] = []
3805
- data['inventory_history'].append({
3806
- 'id': uuid4().hex,
3807
- 'product_id': p_id,
3808
- 'tag_id': tag_ref['id'],
3809
- 'type': 'sale',
3810
- 'qty': deduction,
3811
- 'timestamp': order_timestamp,
3812
- 'details': f"Продажа ({source})"
3813
- })
3814
-
3815
- processed_cart.append({
3816
- "product_id": p_id, "name": item['name'], "price": price, "quantity": quantity,
3817
- "color": c_color, "photo": item.get('photo'), "tag_x": tx, "tag_y": ty, "unit_type": u_type,
3818
- "discount_applied": discount_applied,
3819
- "photo_url": f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{item['photo']}" if item.get('photo') else "https://via.placeholder.com/60x60.png?text=N/A"
3820
- })
3821
- total_price += price * quantity
3822
- except (ValueError, TypeError) as e:
3823
- return jsonify({"error": "Неверная цена или количество в товаре."}), 400
3824
-
3825
- order_id = f"{datetime.now(ALMATY_TZ).strftime('%Y%m%d%H%M%S')}-{uuid4().hex[:6]}"
3826
-
3827
- new_order = {
3828
- "id": order_id, "created_at": order_timestamp, "cart": processed_cart,
3829
- "total_price": round(total_price, 2), "status": "new",
3830
- "employee_id": emp_id, "employee_name": emp_name, "employee_whatsapp": emp_whatsapp,
3831
- "customer_data": customer_data, "source": source
3832
- }
3833
-
3834
- try:
3835
- if 'orders' not in data or not isinstance(data.get('orders'), dict):
3836
- data['orders'] = {}
3837
- data['orders'][order_id] = new_order
3838
- data['products'] = products
3839
- save_env_data(env_id, data)
3840
- return jsonify({"order_id": order_id}), 201
3841
- except Exception:
3842
- return jsonify({"error": "Ошибка сервера при сохранении заказа."}), 500
3843
-
3844
- @app.route('/<env_id>/update_order/<order_id>', methods=['POST'])
3845
- def update_order(env_id, order_id):
3846
- data = get_env_data(env_id)
3847
- order = data.get('orders', {}).get(order_id)
3848
- if not order:
3849
- return jsonify({"error": "Заказ не найден."}), 404
3850
-
3851
- req = request.get_json()
3852
- idx = req.get('index')
3853
- action = req.get('action')
3854
-
3855
- if idx is None or action not in['inc', 'dec', 'set', 'remove']:
3856
- return jsonify({"error": "Некорректный запрос."}), 400
3857
-
3858
- try:
3859
- idx = int(idx)
3860
- cart = order.get('cart',[])
3861
- if idx < 0 or idx >= len(cart):
3862
- return jsonify({"error": "Товар не найден."}), 404
3863
-
3864
- if action == 'inc':
3865
- cart[idx]['quantity'] += 1
3866
- elif action == 'dec':
3867
- cart[idx]['quantity'] -= 1
3868
- if cart[idx]['quantity'] <= 0:
3869
- cart.pop(idx)
3870
- elif action == 'set':
3871
- val = int(req.get('value', 1))
3872
- if val <= 0:
3873
- cart.pop(idx)
3874
- else:
3875
- cart[idx]['quantity'] = val
3876
- elif action == 'remove':
3877
- cart.pop(idx)
3878
-
3879
- total = sum(float(item['price']) * int(item['quantity']) for item in cart)
3880
- order['total_price'] = round(total, 2)
3881
- order['cart'] = cart
3882
-
3883
- save_env_data(env_id, data)
3884
- return jsonify({"success": True})
3885
- except Exception as e:
3886
- return jsonify({"error": str(e)}), 500
3887
-
3888
- @app.route('/<env_id>/delete_order/<order_id>', methods=['POST'])
3889
- def delete_order(env_id, order_id):
3890
- data = get_env_data(env_id)
3891
- if 'orders' in data and order_id in data['orders']:
3892
- del data['orders'][order_id]
3893
- save_env_data(env_id, data)
3894
- flash("Заказ успешно удален.", "success")
3895
- else:
3896
- flash("Заказ не найден.", "error")
3897
- return redirect(url_for('history_page', env_id=env_id))
3898
-
3899
- @app.route('/<env_id>/order/<order_id>')
3900
- def view_order(env_id, order_id):
3901
- data = get_env_data(env_id)
3902
- order = data.get('orders', {}).get(order_id)
3903
- settings = data.get('settings', {})
3904
- return render_template_string(ORDER_TEMPLATE, order=order, repo_id=REPO_ID, currency_code=settings.get('currency_code', 'KGS'), settings=settings, env_id=env_id)
3905
-
3906
- @app.route('/<env_id>/history')
3907
- def history_page(env_id):
3908
- data = get_env_data(env_id)
3909
- settings = data.get('settings', {})
3910
- if settings.get('admin_password_enabled') and not session.get(f'admin_auth_{env_id}'):
3911
- return redirect(url_for('admin_login', env_id=env_id))
3912
-
3913
- orders = list(data.get('orders', {}).values())
3914
- orders.sort(key=lambda x: x.get('created_at', ''), reverse=True)
3915
- employees = data.get('employees',[])
3916
- return render_template_string(HISTORY_TEMPLATE, orders=orders, employees=employees, settings=settings, currency_code=settings.get('currency_code', 'KGS'), env_id=env_id)
3917
-
3918
- @app.route('/<env_id>/admin_ai_chat', methods=['POST'])
3919
- def admin_ai_chat(env_id):
3920
- data = get_env_data(env_id)
3921
- settings = data.get('settings', {})
3922
- if settings.get('admin_password_enabled') and not session.get(f'admin_auth_{env_id}'):
3923
- return jsonify({"text": "Доступ запрещен."})
3924
-
3925
- if not configure_gemini():
3926
- return jsonify({"text": "AI не настроен."})
3927
-
3928
- req = request.get_json()
3929
- message = req.get('message')
3930
- history = req.get('history',[])
3931
-
3932
- orders = data.get('orders', {})
3933
- products = data.get('products',[])
3934
-
3935
- now = datetime.now(ALMATY_TZ)
3936
- current_month = now.strftime('%Y-%m')
3937
-
3938
- monthly_revenue = 0
3939
- product_sales_counts = {}
3940
-
3941
- for o in orders.values():
3942
- if o.get('created_at', '').startswith(current_month):
3943
- monthly_revenue += o.get('total_price', 0)
3944
- for item in o.get('cart',[]):
3945
- pid = item.get('product_id')
3946
- product_sales_counts[pid] = product_sales_counts.get(pid, 0) + item.get('quantity', 0)
3947
-
3948
- sorted_views = sorted(products, key=lambda x: x.get('views', 0), reverse=True)[:5]
3949
- views_str_list = [f"[POST: {p['product_id']} Название: {p['name']}] (просмотров: {p.get('views', 0)})" for p in sorted_views if p.get('views', 0) > 0]
3950
- views_str = ", ".join(views_str_list) if views_str_list else "Нет просмотров"
3951
-
3952
- sorted_sales_pids = sorted(product_sales_counts.items(), key=lambda x: x[1], reverse=True)[:5]
3953
- sales_str_list =[]
3954
- for pid, qty in sorted_sales_pids:
3955
- p = next((x for x in products if x['product_id'] == pid), None)
3956
- if p:
3957
- sales_str_list.append(f"[POST: {pid} Название: {p['name']}] (продано: {qty} шт)")
3958
- sales_str = ", ".join(sales_str_list) if sales_str_list else "Пока нет продаж"
3959
-
3960
- currency = data['settings'].get('currency_code', 'KGS')
3961
-
3962
- sys_prompt = f"""Ты — умный AI-ассистент администратора магазина.
3963
- Текущее время (Алматы): {now.strftime('%Y-%m-%d %H:%M:%S')}.
3964
- Выручка за этот месяц: {monthly_revenue} {currency}.
3965
- Самые просматриваемые товары (Топ-5): {views_str}.
3966
- Самые продаваемые товары (Топ-5): {sales_str}.
3967
- Если упоминаешь товар, используй точный формат:[POST: <product_id> Название: <product_name>].
3968
- Помогай владельцу анализировать продажи и отвечать на бизнес-вопросы."""
3969
-
3970
- try:
3971
- model = genai.GenerativeModel('gemma-3-27b-it')
3972
- messages =[{'role': 'user', 'parts':[{'text': sys_prompt}]}]
3973
- for h in history:
3974
- messages.append({'role': 'model' if h['role'] == 'ai' else 'user', 'parts':[{'text': h['text']}]})
3975
- chat = model.start_chat(history=messages)
3976
- resp = chat.send_message(message)
3977
- return jsonify({'text': resp.text})
3978
- except Exception as e:
3979
- return jsonify({'text': f"Ошибка AI: {str(e)}"})
3980
-
3981
- @app.route('/<env_id>/admin', methods=['GET', 'POST'])
3982
- def admin(env_id):
3983
- data = get_env_data(env_id)
3984
- settings = data.get('settings', {})
3985
-
3986
- if settings.get('admin_password_enabled') and not session.get(f'admin_auth_{env_id}'):
3987
- return redirect(url_for('admin_login', env_id=env_id))
3988
-
3989
- products = data.get('products',[])
3990
- categories = data.get('categories',[])
3991
- organization_info = data.get('organization_info', {})
3992
- employees = data.get('employees',[])
3993
- blocks = data.get('blocks',[])
3994
-
3995
- page = request.args.get('p', 1, type=int)
3996
- search_q = request.args.get('q', '').strip()
3997
-
3998
- if 'orders' not in data or not isinstance(data.get('orders'), dict):
3999
- data['orders'] = {}
4000
-
4001
- if request.method == 'POST':
4002
- action = request.form.get('action')
4003
- try:
4004
- if action == 'add_block':
4005
- b_type = request.form.get('block_type')
4006
- b_title = request.form.get('block_title', '').strip()
4007
- b_url = request.form.get('block_url', '').strip()
4008
- if b_url and not b_url.startswith(('http://', 'https://')):
4009
- b_url = 'https://' + b_url
4010
- b_content = request.form.get('block_content', '').strip()
4011
- blocks.append({
4012
- 'id': uuid4().hex[:8],
4013
- 'type': b_type,
4014
- 'title': b_title,
4015
- 'url': b_url,
4016
- 'content': b_content
4017
- })
4018
- data['blocks'] = blocks
4019
- save_env_data(env_id, data)
4020
- flash("Блок добавлен.", "success")
4021
- elif action == 'delete_block':
4022
- b_id = request.form.get('block_id')
4023
- data['blocks'] =[b for b in blocks if b.get('id') != b_id]
4024
- save_env_data(env_id, data)
4025
- flash("Блок удален.", "success")
4026
- elif action == 'move_block_up':
4027
- b_id = request.form.get('block_id')
4028
- idx = next((i for i, b in enumerate(blocks) if b.get('id') == b_id), -1)
4029
- if idx > 0:
4030
- blocks[idx], blocks[idx-1] = blocks[idx-1], blocks[idx]
4031
- data['blocks'] = blocks
4032
- save_env_data(env_id, data)
4033
- flash("Блок перемещен выше.", "success")
4034
- elif action == 'move_block_down':
4035
- b_id = request.form.get('block_id')
4036
- idx = next((i for i, b in enumerate(blocks) if b.get('id') == b_id), -1)
4037
- if idx != -1 and idx < len(blocks) - 1:
4038
- blocks[idx], blocks[idx+1] = blocks[idx+1], blocks[idx]
4039
- data['blocks'] = blocks
4040
- save_env_data(env_id, data)
4041
- flash("Блок перемещен ниже.", "success")
4042
- elif action == 'add_employee':
4043
- emp_name = request.form.get('emp_name', '').strip()
4044
- emp_whatsapp = request.form.get('emp_whatsapp', '').strip()
4045
- if emp_name and emp_whatsapp:
4046
- emp_id = uuid4().hex[:8]
4047
- employees.append({'id': emp_id, 'name': emp_name, 'whatsapp': emp_whatsapp})
4048
- data['employees'] = employees
4049
- save_env_data(env_id, data)
4050
- flash("Сотрудник добавлен.", "success")
4051
- elif action == 'delete_employee':
4052
- emp_id = request.form.get('emp_id')
4053
- employees =[e for e in employees if e.get('id') != emp_id]
4054
- data['employees'] = employees
4055
- save_env_data(env_id, data)
4056
- flash("Сотрудник удален.", "success")
4057
- elif action == 'add_category':
4058
- category_name = request.form.get('category_name', '').strip()
4059
- if category_name and category_name not in categories:
4060
- categories.append(category_name)
4061
- data['categories'] = categories
4062
- save_env_data(env_id, data)
4063
- flash(f"Категория '{category_name}' успешно добавлена.", 'success')
4064
- elif not category_name:
4065
- flash("Название категории не может быть пустым.", 'error')
4066
- else:
4067
- flash(f"Категория '{category_name}' уже существует.", 'error')
4068
-
4069
- elif action == 'delete_category':
4070
- category_to_delete = request.form.get('category_name')
4071
- if category_to_delete and category_to_delete in categories:
4072
- categories.remove(category_to_delete)
4073
- updated_count = 0
4074
- for product in products:
4075
- if product.get('category') == category_to_delete:
4076
- product['category'] = 'Без категории'
4077
- updated_count += 1
4078
- data['categories'] = categories
4079
- data['products'] = products
4080
- save_env_data(env_id, data)
4081
- flash(f"Категория '{category_to_delete}' удалена. {updated_count} товаров обновлено.", 'success')
4082
- else:
4083
- flash(f"Не удалось удалить категорию '{category_to_delete}'.", 'error')
4084
-
4085
- elif action == 'update_org_info':
4086
- organization_info['about_us'] = request.form.get('about_us', '').strip()
4087
- organization_info['shipping'] = request.form.get('shipping', '').strip()
4088
- organization_info['returns'] = request.form.get('returns', '').strip()
4089
- organization_info['contact'] = request.form.get('contact', '').strip()
4090
- data['organization_info'] = organization_info
4091
- save_env_data(env_id, data)
4092
- flash("Информация о магазине успешно обновлена.", 'success')
4093
-
4094
- elif action == 'update_settings':
4095
- settings['admin_password_enabled'] = 'admin_password_enabled' in request.form
4096
- settings['admin_password'] = request.form.get('admin_password', '').strip()
4097
-
4098
- settings['organization_name'] = request.form.get('organization_name', 'Gippo312').strip()
4099
- settings['whatsapp_number'] = request.form.get('whatsapp_number', '').strip()
4100
- settings['currency_code'] = request.form.get('currency_code', 'KGS')
4101
- settings['business_type'] = request.form.get('business_type', 'retail')
4102
- settings['color_scheme'] = request.form.get('color_scheme', 'default')
4103
-
4104
- settings['checkout_fields_enabled'] = 'checkout_fields_enabled' in request.form
4105
- settings['checkout_fields'] = {
4106
- 'name': 'cf_name' in request.form,
4107
- 'phone': 'cf_phone' in request.form,
4108
- 'city': 'cf_city' in request.form,
4109
- 'address': 'cf_address' in request.form,
4110
- 'zip': 'cf_zip' in request.form
4111
- }
4112
- settings['categories_as_lines'] = 'categories_as_lines' in request.form
4113
-
4114
- avatar_file = request.files.get('chat_avatar')
4115
- if avatar_file and avatar_file.filename:
4116
- if HF_TOKEN_WRITE:
4117
- try:
4118
- api = HfApi()
4119
- old_avatar = settings.get('chat_avatar')
4120
- if old_avatar:
4121
- try: api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"avatars/{old_avatar}"], repo_type="dataset", token=HF_TOKEN_WRITE)
4122
- except Exception: pass
4123
- ext = os.path.splitext(avatar_file.filename)[1].lower()
4124
- avatar_filename = f"avatar_{env_id}_{int(time.time())}{ext}"
4125
- uploads_dir = 'uploads_temp'
4126
- os.makedirs(uploads_dir, exist_ok=True)
4127
- temp_path = os.path.join(uploads_dir, avatar_filename)
4128
- avatar_file.save(temp_path)
4129
- api.upload_file(path_or_fileobj=temp_path, path_in_repo=f"avatars/{avatar_filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE)
4130
- settings['chat_avatar'] = avatar_filename
4131
- os.remove(temp_path)
4132
- flash("Аватар успешно обновлен.", 'success')
4133
- except Exception as e:
4134
- flash(f"Ошибка при загрузке аватара: {e}", 'error')
4135
- else:
4136
- flash("HF_TOKEN (write) не настроен. Аватар не был загружен.", "warning")
4137
-
4138
- data['settings'] = settings
4139
- save_env_data(env_id, data)
4140
- flash("Настройки успешно обновлены.", 'success')
4141
-
4142
- elif action == 'add_product' or action == 'edit_product':
4143
- product_id = request.form.get('product_id')
4144
- product_data = {}
4145
- is_edit = action == 'edit_product'
4146
-
4147
- if is_edit:
4148
- product_data = next((p for p in products if p.get('product_id') == product_id), None)
4149
- if not product_data:
4150
- flash(f"Ошибка: товар с ID {product_id} не найден.", 'error')
4151
- return redirect(url_for('admin', env_id=env_id, p=page, q=search_q))
4152
- else:
4153
- product_data['views'] = 0
4154
-
4155
- product_data['name'] = request.form.get('name', '').strip()
4156
- product_data['description'] = request.form.get('description', '').strip()
4157
- category = request.form.get('category')
4158
- product_data['category'] = category if category in categories else 'Без категории'
4159
-
4160
- tags_raw = request.form.get('tags_json', '[]')
4161
- try:
4162
- parsed_tags = json.loads(tags_raw)
4163
- for t in parsed_tags:
4164
- if 'stock_batches' not in t:
4165
- t['stock_batches'] =[{"qty": t.get('stock', 0), "price": t.get('price', 0), "box_price": t.get('box_price', 0)}]
4166
- product_data['tags'] = parsed_tags
4167
- except:
4168
- product_data['tags'] =[]
4169
-
4170
- product_data['in_stock'] = 'in_stock' in request.form
4171
- product_data['is_top'] = 'is_top' in request.form
4172
-
4173
- if not product_data['name']:
4174
- flash("Название товара обязательно.", 'error')
4175
- return redirect(url_for('admin', env_id=env_id, p=page, q=search_q))
4176
-
4177
- photos_files = request.files.getlist('photos')
4178
- if photos_files and any(f.filename for f in photos_files):
4179
- if HF_TOKEN_WRITE:
4180
- uploads_dir = 'uploads_temp'
4181
- os.makedirs(uploads_dir, exist_ok=True)
4182
- api = HfApi()
4183
- new_photos_list =[]
4184
- photo_limit = 10
4185
- uploaded_count = 0
4186
- for photo in photos_files:
4187
- if uploaded_count >= photo_limit: break
4188
- if photo and photo.filename:
4189
- try:
4190
- ext = os.path.splitext(photo.filename)[1].lower()
4191
- if ext not in['.jpg', '.jpeg', '.png', '.gif', '.webp']: continue
4192
- safe_name = secure_filename(product_data['name'].replace(' ', '_'))[:50]
4193
- photo_filename = f"{safe_name}_{uuid4().hex[:8]}{ext}"
4194
- temp_path = os.path.join(uploads_dir, photo_filename)
4195
- photo.save(temp_path)
4196
- 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)
4197
- new_photos_list.append(photo_filename)
4198
- os.remove(temp_path)
4199
- uploaded_count += 1
4200
- except Exception as e:
4201
- flash(f"Ошибка при загрузке фото {photo.filename}: {e}", 'error')
4202
- if new_photos_list and is_edit and product_data.get('photos'):
4203
- try: api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"photos/{p}" for p in product_data['photos']], repo_type="dataset", token=HF_TOKEN_WRITE)
4204
- except Exception: pass
4205
- if new_photos_list:
4206
- product_data['photos'] = new_photos_list
4207
- else:
4208
- flash("HF_TOKEN не настроен. Фотографии не загружены.", "warning")
4209
-
4210
- if is_edit:
4211
- product_index = next((i for i, p in enumerate(products) if p.get('product_id') == product_id), -1)
4212
- if product_index != -1:
4213
- products[product_index] = product_data
4214
- flash(f"Товар '{product_data['name']}' обновлен.", 'success')
4215
- else:
4216
- product_data['product_id'] = uuid4().hex
4217
- products.append(product_data)
4218
- flash(f"Товар '{product_data['name']}' добавлен.", 'success')
4219
-
4220
- data['products'] = products
4221
- save_env_data(env_id, data)
4222
-
4223
- elif action == 'delete_product':
4224
- product_id = request.form.get('product_id')
4225
- product_index = next((i for i, p in enumerate(products) if p.get('product_id') == product_id), -1)
4226
- if product_index == -1:
4227
- flash(f"Ошибка удаления: товар не найден.", 'error')
4228
- return redirect(url_for('admin', env_id=env_id, p=page, q=search_q))
4229
- deleted_product = products.pop(product_index)
4230
- product_name = deleted_product.get('name', 'N/A')
4231
- photos_to_delete = deleted_product.get('photos',[])
4232
- if photos_to_delete and HF_TOKEN_WRITE:
4233
- try:
4234
- api = HfApi()
4235
- api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"photos/{p}" for p in photos_to_delete], repo_type="dataset", token=HF_TOKEN_WRITE)
4236
- except Exception: pass
4237
- data['products'] = products
4238
- save_env_data(env_id, data)
4239
- flash(f"Товар '{product_name}' удален.", 'success')
4240
- else:
4241
- flash(f"Неизвестное действие: {action}", 'warning')
4242
- return redirect(url_for('admin', env_id=env_id, p=page, q=search_q))
4243
- except Exception as e:
4244
- flash(f"Ошибка при выполнении действия.", 'error')
4245
- return redirect(url_for('admin', env_id=env_id, p=page, q=search_q))
4246
-
4247
- filtered_products = products
4248
- if search_q:
4249
- q_lower = search_q.lower()
4250
- filtered_products =[p for p in products if q_lower in p.get('name', '').lower() or q_lower in p.get('description', '').lower()]
4251
-
4252
- filtered_products = sorted(filtered_products, key=lambda p: p.get('name', '').lower())
4253
-
4254
- PER_PAGE = 20
4255
- total_items = len(filtered_products)
4256
- total_pages = math.ceil(total_items / PER_PAGE) if total_items > 0 else 1
4257
-
4258
- if page < 1: page = 1
4259
- if page > total_pages: page = total_pages
4260
-
4261
- start_idx = (page - 1) * PER_PAGE
4262
- end_idx = start_idx + PER_PAGE
4263
- paginated_products = filtered_products[start_idx:end_idx]
4264
-
4265
- display_categories = sorted(categories)
4266
- display_organization_info = organization_info
4267
- display_settings = settings
4268
- chat_status = { "active": False, "expires_soon": False, "expires_date": "N/A" }
4269
- chat_avatar_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/avatars/{display_settings['chat_avatar']}" if display_settings.get('chat_avatar') else "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png"
4270
-
4271
- low_stock_count = 0
4272
- if settings.get('env_mode') == '2in1':
4273
- for p in products:
4274
- for t in p.get('tags',[]):
4275
- if t.get('stock', 0) <= 50:
4276
- low_stock_count += 1
4277
-
4278
- return render_template_string(
4279
- ADMIN_TEMPLATE, paginated_products=paginated_products, total_pages=total_pages, page=page, search_q=search_q, categories=display_categories,
4280
- organization_info=display_organization_info, chats={}, settings=display_settings, employees=employees,
4281
- blocks=blocks, repo_id=REPO_ID, currency_code=display_settings.get('currency_code', 'KGS'), chat_avatar_url=chat_avatar_url,
4282
- currencies=CURRENCIES, color_schemes=COLOR_SCHEMES, env_id=env_id, chat_status=chat_status, low_stock_count=low_stock_count
4283
- )
4284
-
4285
- @app.route('/generate_description_ai', methods=['POST'])
4286
- def handle_generate_description_ai():
4287
- request_data = request.get_json()
4288
- base64_image = request_data.get('image')
4289
- language = request_data.get('language', 'Русский')
4290
- if not base64_image: return jsonify({"error": "Изображение не найдено в запросе."}), 400
4291
- try:
4292
- image_data = base64.b64decode(base64_image)
4293
- result_text = generate_ai_description_from_image(image_data, language)
4294
- return jsonify({"text": result_text})
4295
- except ValueError as ve: return jsonify({"error": str(ve)}), 400
4296
- except Exception as e: return jsonify({"error": f"Внутренняя ошибка сервера: {e}"}), 500
4297
-
4298
- if __name__ == '__main__':
4299
- configure_gemini()
4300
- download_db_from_hf()
4301
- load_data()
4302
- if HF_TOKEN_WRITE:
4303
- backup_thread = threading.Thread(target=periodic_backup, daemon=True)
4304
- backup_thread.start()
4305
- port = int(os.environ.get('PORT', 7860))
4306
- app.run(debug=False, host='0.0.0.0', port=port)
4307
  'qty': qty,
4308
  'timestamp': datetime.now(ALMATY_TZ).strftime('%Y-%m-%d %H:%M:%S'),
4309
  'details': ''
 
3572
  'id': uuid4().hex,
3573
  'product_id': p_id,
3574
  'tag_id': t_id,
3575
+ 'type': action,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3576
  'qty': qty,
3577
  'timestamp': datetime.now(ALMATY_TZ).strftime('%Y-%m-%d %H:%M:%S'),
3578
  'details': ''