Kgshop commited on
Commit
8d3d600
·
verified ·
1 Parent(s): 4b3e108

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +34 -14
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'] = []; changed = True
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 ['mixed', 'wholesale'] and c_box_price > 0 and ppb > 1 and qty >= ppb:
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 ['confirmed', 'pos']:
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 = trackInventory && v.stock !== "" && v.stock !== null ? `<div class="variant-stock">Остаток: ${v.stock} шт</div>` : '';
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 = trackInventory && p.stock !== "" && p.stock !== null ? `<div style="font-size:0.8rem; color:#0984e3; margin-top:4px;">Остаток: ${p.stock} шт</div>` : '';
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 ['confirmed', 'pos']
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 = [o for o in orders.values() if o.get('status') == 'pending']
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)