Update app.py
Browse files
app.py
CHANGED
|
@@ -150,6 +150,7 @@ def load_data():
|
|
| 150 |
'use_barcodes': False,
|
| 151 |
'business_type': 'mixed',
|
| 152 |
'system_mode': 'both',
|
|
|
|
| 153 |
'customer_fields': {
|
| 154 |
'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False
|
| 155 |
},
|
|
@@ -164,7 +165,7 @@ def load_data():
|
|
| 164 |
|
| 165 |
changed = False
|
| 166 |
for env_id, env_data in data.items():
|
| 167 |
-
if 'products' not in env_data: env_data['products'] =
|
| 168 |
if 'categories' not in env_data: env_data['categories'] =[]
|
| 169 |
if 'orders' not in env_data: env_data['orders'] = {}
|
| 170 |
if 'staff' not in env_data: env_data['staff'] =[]
|
|
@@ -184,6 +185,7 @@ def load_data():
|
|
| 184 |
if 'use_barcodes' not in settings: settings['use_barcodes'] = False; changed = True
|
| 185 |
if 'business_type' not in settings: settings['business_type'] = 'mixed'; changed = True
|
| 186 |
if 'system_mode' not in settings: settings['system_mode'] = 'both'; changed = True
|
|
|
|
| 187 |
if 'customer_fields' not in settings:
|
| 188 |
settings['customer_fields'] = {'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False}
|
| 189 |
changed = True
|
|
@@ -201,7 +203,7 @@ def load_data():
|
|
| 201 |
if 'box_price' not in product: product['box_price'] = ""; changed = True
|
| 202 |
if 'min_order' not in product: product['min_order'] = ""; changed = True
|
| 203 |
if 'barcode' not in product: product['barcode'] = ""; changed = True
|
| 204 |
-
if 'variants' not in product: product['variants'] =
|
| 205 |
if 'has_variant_prices' not in product: product['has_variant_prices'] = False; changed = True
|
| 206 |
if 'stock' not in product: product['stock'] = ""; changed = True
|
| 207 |
for v in product['variants']:
|
|
@@ -248,7 +250,7 @@ def get_env_data(env_id):
|
|
| 248 |
if env_id not in all_data:
|
| 249 |
all_data[env_id] = {
|
| 250 |
'products':[],
|
| 251 |
-
'categories':
|
| 252 |
'orders': {},
|
| 253 |
'staff': [],
|
| 254 |
'inventory_history':[],
|
|
@@ -257,12 +259,13 @@ def get_env_data(env_id):
|
|
| 257 |
'admin_password_enabled': False,
|
| 258 |
'admin_password': '',
|
| 259 |
'logo_url': DEFAULT_LOGO_URL,
|
| 260 |
-
'whatsapp_number': DEFAULT_WHATSAPP_NUMBER,
|
| 261 |
'currency': 'T',
|
| 262 |
'track_inventory': False,
|
| 263 |
'use_barcodes': False,
|
| 264 |
'business_type': 'mixed',
|
| 265 |
'system_mode': 'both',
|
|
|
|
| 266 |
'customer_fields': {'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False},
|
| 267 |
'socials': {
|
| 268 |
'wa': {'enabled': True, 'url': ''},
|
|
@@ -291,7 +294,7 @@ def update_order_totals(order, business_type):
|
|
| 291 |
c_box_price = float(i.get('cart_box_price', 0))
|
| 292 |
item_discount = float(i.get('discount', 0))
|
| 293 |
|
| 294 |
-
if business_type in
|
| 295 |
base_price = c_box_price / ppb
|
| 296 |
else:
|
| 297 |
base_price = c_price
|
|
@@ -305,7 +308,7 @@ def update_order_totals(order, business_type):
|
|
| 305 |
order['total_price'] = round(total, 2)
|
| 306 |
|
| 307 |
def is_order_fully_assembled(order):
|
| 308 |
-
if order.get('status') not in
|
| 309 |
return True
|
| 310 |
assembled_data = order.get('assembled', {})
|
| 311 |
for item in order.get('cart',[]):
|
|
@@ -790,6 +793,7 @@ CATALOG_TEMPLATE = '''
|
|
| 790 |
const mode = '{{ mode }}';
|
| 791 |
const staffId = '{{ staff_id }}';
|
| 792 |
const trackInventory = {{ 'true' if settings.track_inventory else 'false' }};
|
|
|
|
| 793 |
const businessType = '{{ settings.business_type }}';
|
| 794 |
const cFields = {{ settings.customer_fields|tojson }};
|
| 795 |
|
|
@@ -906,6 +910,7 @@ CATALOG_TEMPLATE = '''
|
|
| 906 |
if (minO > 1) boxInfoHtml += `<div style="font-size:0.8rem; color:#e17055; font-weight:600;">Мин. заказ: ${minO} шт</div>`;
|
| 907 |
}
|
| 908 |
|
|
|
|
| 909 |
let variantsHtml = '';
|
| 910 |
let mainControlsHtml = '';
|
| 911 |
|
|
@@ -916,7 +921,7 @@ CATALOG_TEMPLATE = '''
|
|
| 916 |
p.variants.forEach((v, idx) => {
|
| 917 |
let vPrice = p.has_variant_prices ? v.price : p.price;
|
| 918 |
let vBoxPrice = p.has_variant_prices ? (v.box_price || '') : (p.box_price || '');
|
| 919 |
-
let vStockHtml =
|
| 920 |
let cKey = getCartKey(p.product_id, idx);
|
| 921 |
let qty = cart[cKey] ? cart[cKey].quantity : 0;
|
| 922 |
let vPpb = parseInt(v.pieces_per_box) || ppb;
|
|
@@ -951,7 +956,7 @@ CATALOG_TEMPLATE = '''
|
|
| 951 |
});
|
| 952 |
variantsHtml += `</div>`;
|
| 953 |
} else {
|
| 954 |
-
let mStockHtml =
|
| 955 |
let qty = cart[p.product_id] ? cart[p.product_id].quantity : 0;
|
| 956 |
|
| 957 |
let addBoxBtn = '';
|
|
@@ -2388,7 +2393,16 @@ ADMIN_TEMPLATE = '''
|
|
| 2388 |
<div style="font-weight: 600; margin-bottom: 5px; border-top: 1px solid var(--border); padding-top: 15px;">Учет товара:</div>
|
| 2389 |
<label><input type="checkbox" name="track_inventory" {% if settings.track_inventory %}checked{% endif %}> Включить остатки на складе</label>
|
| 2390 |
<label><input type="checkbox" name="use_barcodes" {% if settings.use_barcodes %}checked{% endif %}> Использовать штрих-коды</label>
|
|
|
|
|
|
|
| 2391 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2392 |
|
| 2393 |
<div style="font-weight: 600; margin-bottom: 5px; border-top: 1px solid var(--border); padding-top: 15px; margin-top: 10px;">Социальные сети:</div>
|
| 2394 |
<div class="social-item">
|
|
@@ -3609,6 +3623,7 @@ def create_environment():
|
|
| 3609 |
"use_barcodes": False,
|
| 3610 |
"business_type": "mixed",
|
| 3611 |
"system_mode": "both",
|
|
|
|
| 3612 |
"customer_fields": {'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False},
|
| 3613 |
"socials": {
|
| 3614 |
'wa': {'enabled': True, 'url': ''},
|
|
@@ -3698,7 +3713,7 @@ def admin_logout(env_id):
|
|
| 3698 |
@app.route('/<env_id>/catalog')
|
| 3699 |
def catalog(env_id):
|
| 3700 |
data = get_env_data(env_id)
|
| 3701 |
-
all_products = data.get('products',
|
| 3702 |
categories = data.get('categories',[])
|
| 3703 |
settings = data.get('settings', {})
|
| 3704 |
|
|
@@ -3765,7 +3780,7 @@ def finish_assembly(env_id, order_id):
|
|
| 3765 |
new_cart = []
|
| 3766 |
|
| 3767 |
track_inv = data['settings'].get('track_inventory', False) and data['settings'].get('system_mode', 'both') != 'external'
|
| 3768 |
-
is_stock_deducted = order.get('status') in
|
| 3769 |
|
| 3770 |
for item in order.get('cart',[]):
|
| 3771 |
c_key = item.get('c_key')
|
|
@@ -3985,7 +4000,7 @@ def apply_discount(env_id, order_id):
|
|
| 3985 |
except ValueError:
|
| 3986 |
order['global_discount'] = 0
|
| 3987 |
|
| 3988 |
-
for item in order.get('cart',
|
| 3989 |
item_disc = request.form.get(f"item_discount_{item['c_key']}", 0)
|
| 3990 |
try:
|
| 3991 |
item['discount'] = float(item_disc)
|
|
@@ -4140,12 +4155,12 @@ def admin(env_id):
|
|
| 4140 |
if settings.get('admin_password_enabled') and not session.get(f'admin_auth_{env_id}'):
|
| 4141 |
return redirect(url_for('admin_login', env_id=env_id))
|
| 4142 |
|
| 4143 |
-
products = data.get('products',
|
| 4144 |
categories = data.get('categories',[])
|
| 4145 |
staff = data.get('staff',[])
|
| 4146 |
orders = data.get('orders', {})
|
| 4147 |
|
| 4148 |
-
pending_orders =
|
| 4149 |
pending_orders.sort(key=lambda x: x.get('created_at', ''), reverse=True)
|
| 4150 |
|
| 4151 |
unassembled_count = len([o for o in orders.values() if not is_order_fully_assembled(o)])
|
|
@@ -4176,9 +4191,14 @@ def admin(env_id):
|
|
| 4176 |
if settings.get('system_mode', 'both') == 'external':
|
| 4177 |
settings['track_inventory'] = False
|
| 4178 |
settings['use_barcodes'] = False
|
|
|
|
| 4179 |
else:
|
| 4180 |
settings['track_inventory'] = 'track_inventory' in request.form
|
| 4181 |
settings['use_barcodes'] = 'use_barcodes' in request.form
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4182 |
|
| 4183 |
settings['customer_fields'] = {
|
| 4184 |
'name': 'cf_name' in request.form,
|
|
@@ -4508,4 +4528,4 @@ if __name__ == '__main__':
|
|
| 4508 |
threading.Thread(target=periodic_backup, daemon=True).start()
|
| 4509 |
|
| 4510 |
port = int(os.environ.get('PORT', 7860))
|
| 4511 |
-
app.run(host='0.0.0.0', port=port)
|
|
|
|
| 150 |
'use_barcodes': False,
|
| 151 |
'business_type': 'mixed',
|
| 152 |
'system_mode': 'both',
|
| 153 |
+
'hide_stock_online': False,
|
| 154 |
'customer_fields': {
|
| 155 |
'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False
|
| 156 |
},
|
|
|
|
| 165 |
|
| 166 |
changed = False
|
| 167 |
for env_id, env_data in data.items():
|
| 168 |
+
if 'products' not in env_data: env_data['products'] =[]
|
| 169 |
if 'categories' not in env_data: env_data['categories'] =[]
|
| 170 |
if 'orders' not in env_data: env_data['orders'] = {}
|
| 171 |
if 'staff' not in env_data: env_data['staff'] =[]
|
|
|
|
| 185 |
if 'use_barcodes' not in settings: settings['use_barcodes'] = False; changed = True
|
| 186 |
if 'business_type' not in settings: settings['business_type'] = 'mixed'; changed = True
|
| 187 |
if 'system_mode' not in settings: settings['system_mode'] = 'both'; changed = True
|
| 188 |
+
if 'hide_stock_online' not in settings: settings['hide_stock_online'] = False; changed = True
|
| 189 |
if 'customer_fields' not in settings:
|
| 190 |
settings['customer_fields'] = {'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False}
|
| 191 |
changed = True
|
|
|
|
| 203 |
if 'box_price' not in product: product['box_price'] = ""; changed = True
|
| 204 |
if 'min_order' not in product: product['min_order'] = ""; changed = True
|
| 205 |
if 'barcode' not in product: product['barcode'] = ""; changed = True
|
| 206 |
+
if 'variants' not in product: product['variants'] =[]; changed = True
|
| 207 |
if 'has_variant_prices' not in product: product['has_variant_prices'] = False; changed = True
|
| 208 |
if 'stock' not in product: product['stock'] = ""; changed = True
|
| 209 |
for v in product['variants']:
|
|
|
|
| 250 |
if env_id not in all_data:
|
| 251 |
all_data[env_id] = {
|
| 252 |
'products':[],
|
| 253 |
+
'categories':[],
|
| 254 |
'orders': {},
|
| 255 |
'staff': [],
|
| 256 |
'inventory_history':[],
|
|
|
|
| 259 |
'admin_password_enabled': False,
|
| 260 |
'admin_password': '',
|
| 261 |
'logo_url': DEFAULT_LOGO_URL,
|
| 262 |
+
'whatsapp_number': DEFAULT_WHAPP_NUMBER if 'DEFAULT_WHATSAPP_NUMBER' in globals() else DEFAULT_WHATSAPP_NUMBER,
|
| 263 |
'currency': 'T',
|
| 264 |
'track_inventory': False,
|
| 265 |
'use_barcodes': False,
|
| 266 |
'business_type': 'mixed',
|
| 267 |
'system_mode': 'both',
|
| 268 |
+
'hide_stock_online': False,
|
| 269 |
'customer_fields': {'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False},
|
| 270 |
'socials': {
|
| 271 |
'wa': {'enabled': True, 'url': ''},
|
|
|
|
| 294 |
c_box_price = float(i.get('cart_box_price', 0))
|
| 295 |
item_discount = float(i.get('discount', 0))
|
| 296 |
|
| 297 |
+
if business_type in['mixed', 'wholesale'] and c_box_price > 0 and ppb > 1 and qty >= ppb:
|
| 298 |
base_price = c_box_price / ppb
|
| 299 |
else:
|
| 300 |
base_price = c_price
|
|
|
|
| 308 |
order['total_price'] = round(total, 2)
|
| 309 |
|
| 310 |
def is_order_fully_assembled(order):
|
| 311 |
+
if order.get('status') not in['confirmed', 'pos']:
|
| 312 |
return True
|
| 313 |
assembled_data = order.get('assembled', {})
|
| 314 |
for item in order.get('cart',[]):
|
|
|
|
| 793 |
const mode = '{{ mode }}';
|
| 794 |
const staffId = '{{ staff_id }}';
|
| 795 |
const trackInventory = {{ 'true' if settings.track_inventory else 'false' }};
|
| 796 |
+
const hideStockOnline = {{ 'true' if settings.hide_stock_online else 'false' }};
|
| 797 |
const businessType = '{{ settings.business_type }}';
|
| 798 |
const cFields = {{ settings.customer_fields|tojson }};
|
| 799 |
|
|
|
|
| 910 |
if (minO > 1) boxInfoHtml += `<div style="font-size:0.8rem; color:#e17055; font-weight:600;">Мин. заказ: ${minO} шт</div>`;
|
| 911 |
}
|
| 912 |
|
| 913 |
+
let showStock = trackInventory && !(mode !== 'pos' && hideStockOnline);
|
| 914 |
let variantsHtml = '';
|
| 915 |
let mainControlsHtml = '';
|
| 916 |
|
|
|
|
| 921 |
p.variants.forEach((v, idx) => {
|
| 922 |
let vPrice = p.has_variant_prices ? v.price : p.price;
|
| 923 |
let vBoxPrice = p.has_variant_prices ? (v.box_price || '') : (p.box_price || '');
|
| 924 |
+
let vStockHtml = showStock && v.stock !== "" && v.stock !== null ? `<div class="variant-stock">Остаток: ${v.stock} шт</div>` : '';
|
| 925 |
let cKey = getCartKey(p.product_id, idx);
|
| 926 |
let qty = cart[cKey] ? cart[cKey].quantity : 0;
|
| 927 |
let vPpb = parseInt(v.pieces_per_box) || ppb;
|
|
|
|
| 956 |
});
|
| 957 |
variantsHtml += `</div>`;
|
| 958 |
} else {
|
| 959 |
+
let mStockHtml = showStock && p.stock !== "" && p.stock !== null ? `<div style="font-size:0.8rem; color:#0984e3; margin-top:4px;">Остаток: ${p.stock} шт</div>` : '';
|
| 960 |
let qty = cart[p.product_id] ? cart[p.product_id].quantity : 0;
|
| 961 |
|
| 962 |
let addBoxBtn = '';
|
|
|
|
| 2393 |
<div style="font-weight: 600; margin-bottom: 5px; border-top: 1px solid var(--border); padding-top: 15px;">Учет товара:</div>
|
| 2394 |
<label><input type="checkbox" name="track_inventory" {% if settings.track_inventory %}checked{% endif %}> Включить остатки на складе</label>
|
| 2395 |
<label><input type="checkbox" name="use_barcodes" {% if settings.use_barcodes %}checked{% endif %}> Использовать штрих-коды</label>
|
| 2396 |
+
{% if sys_mode == 'both' %}
|
| 2397 |
+
<label><input type="checkbox" name="hide_stock_online" {% if settings.hide_stock_online %}checked{% endif %}> Клиент не видит остатков (в каталоге для онлайн заказов)</label>
|
| 2398 |
{% endif %}
|
| 2399 |
+
{% endif %}
|
| 2400 |
+
|
| 2401 |
+
<div style="font-weight: 600; margin-bottom: 5px; border-top: 1px solid var(--border); padding-top: 15px; margin-top: 10px;">Безопасность (Админ-панель):</div>
|
| 2402 |
+
<div class="social-item" style="margin-bottom: 10px;">
|
| 2403 |
+
<label style="width: auto; margin-right: 15px;"><input type="checkbox" name="admin_password_enabled" {% if settings.admin_password_enabled %}checked{% endif %}> Пароль на вход</label>
|
| 2404 |
+
<input type="text" name="admin_password" value="{{ settings.admin_password }}" placeholder="Установите пароль" style="flex: 1; min-width: 150px;">
|
| 2405 |
+
</div>
|
| 2406 |
|
| 2407 |
<div style="font-weight: 600; margin-bottom: 5px; border-top: 1px solid var(--border); padding-top: 15px; margin-top: 10px;">Социальные сети:</div>
|
| 2408 |
<div class="social-item">
|
|
|
|
| 3623 |
"use_barcodes": False,
|
| 3624 |
"business_type": "mixed",
|
| 3625 |
"system_mode": "both",
|
| 3626 |
+
"hide_stock_online": False,
|
| 3627 |
"customer_fields": {'name': True, 'phone': True, 'city': True, 'address': False, 'zip': False},
|
| 3628 |
"socials": {
|
| 3629 |
'wa': {'enabled': True, 'url': ''},
|
|
|
|
| 3713 |
@app.route('/<env_id>/catalog')
|
| 3714 |
def catalog(env_id):
|
| 3715 |
data = get_env_data(env_id)
|
| 3716 |
+
all_products = data.get('products',[])
|
| 3717 |
categories = data.get('categories',[])
|
| 3718 |
settings = data.get('settings', {})
|
| 3719 |
|
|
|
|
| 3780 |
new_cart = []
|
| 3781 |
|
| 3782 |
track_inv = data['settings'].get('track_inventory', False) and data['settings'].get('system_mode', 'both') != 'external'
|
| 3783 |
+
is_stock_deducted = order.get('status') in['confirmed', 'pos']
|
| 3784 |
|
| 3785 |
for item in order.get('cart',[]):
|
| 3786 |
c_key = item.get('c_key')
|
|
|
|
| 4000 |
except ValueError:
|
| 4001 |
order['global_discount'] = 0
|
| 4002 |
|
| 4003 |
+
for item in order.get('cart',[]):
|
| 4004 |
item_disc = request.form.get(f"item_discount_{item['c_key']}", 0)
|
| 4005 |
try:
|
| 4006 |
item['discount'] = float(item_disc)
|
|
|
|
| 4155 |
if settings.get('admin_password_enabled') and not session.get(f'admin_auth_{env_id}'):
|
| 4156 |
return redirect(url_for('admin_login', env_id=env_id))
|
| 4157 |
|
| 4158 |
+
products = data.get('products',[])
|
| 4159 |
categories = data.get('categories',[])
|
| 4160 |
staff = data.get('staff',[])
|
| 4161 |
orders = data.get('orders', {})
|
| 4162 |
|
| 4163 |
+
pending_orders =[o for o in orders.values() if o.get('status') == 'pending']
|
| 4164 |
pending_orders.sort(key=lambda x: x.get('created_at', ''), reverse=True)
|
| 4165 |
|
| 4166 |
unassembled_count = len([o for o in orders.values() if not is_order_fully_assembled(o)])
|
|
|
|
| 4191 |
if settings.get('system_mode', 'both') == 'external':
|
| 4192 |
settings['track_inventory'] = False
|
| 4193 |
settings['use_barcodes'] = False
|
| 4194 |
+
settings['hide_stock_online'] = False
|
| 4195 |
else:
|
| 4196 |
settings['track_inventory'] = 'track_inventory' in request.form
|
| 4197 |
settings['use_barcodes'] = 'use_barcodes' in request.form
|
| 4198 |
+
settings['hide_stock_online'] = 'hide_stock_online' in request.form
|
| 4199 |
+
|
| 4200 |
+
settings['admin_password_enabled'] = 'admin_password_enabled' in request.form
|
| 4201 |
+
settings['admin_password'] = request.form.get('admin_password', '').strip()
|
| 4202 |
|
| 4203 |
settings['customer_fields'] = {
|
| 4204 |
'name': 'cf_name' in request.form,
|
|
|
|
| 4528 |
threading.Thread(target=periodic_backup, daemon=True).start()
|
| 4529 |
|
| 4530 |
port = int(os.environ.get('PORT', 7860))
|
| 4531 |
+
app.run(host='0.0.0.0', port=port)
|