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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +17 -361
app.py CHANGED
@@ -37,7 +37,7 @@ def get_almaty_time():
37
 
38
  def download_db_from_hf(specific_file=None, retries=3, delay=5):
39
  token_to_use = HF_TOKEN_READ if HF_TOKEN_READ else HF_TOKEN_WRITE
40
- files_to_download =[specific_file] if specific_file else SYNC_FILES
41
  all_successful = True
42
 
43
  for file_name in files_to_download:
@@ -90,7 +90,7 @@ def upload_db_to_hf(specific_file=None):
90
  return
91
  try:
92
  api = HfApi()
93
- files_to_upload =[specific_file] if specific_file else SYNC_FILES
94
 
95
  for file_name in files_to_upload:
96
  if os.path.exists(file_name):
@@ -137,7 +137,7 @@ def load_data():
137
  'products': data.get('products', []),
138
  'categories': data.get('categories',[]),
139
  'orders': data.get('orders', {}),
140
- 'staff':[],
141
  'inventory_history':[],
142
  'settings': {
143
  'organization_name': 'Default Shop',
@@ -147,7 +147,6 @@ def load_data():
147
  'whatsapp_number': DEFAULT_WHATSAPP_NUMBER,
148
  'currency': 'T',
149
  'track_inventory': False,
150
- 'hide_stock_from_clients': False,
151
  'use_barcodes': False,
152
  'business_type': 'mixed',
153
  'system_mode': 'both',
@@ -165,7 +164,7 @@ def load_data():
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'] =[]
@@ -182,7 +181,6 @@ def load_data():
182
  if 'whatsapp_number' not in settings: settings['whatsapp_number'] = DEFAULT_WHATSAPP_NUMBER; changed = True
183
  if 'currency' not in settings: settings['currency'] = 'T'; changed = True
184
  if 'track_inventory' not in settings: settings['track_inventory'] = False; changed = True
185
- if 'hide_stock_from_clients' not in settings: settings['hide_stock_from_clients'] = False; changed = True
186
  if 'use_barcodes' not in settings: settings['use_barcodes'] = False; changed = True
187
  if 'business_type' not in settings: settings['business_type'] = 'mixed'; changed = True
188
  if 'system_mode' not in settings: settings['system_mode'] = 'both'; changed = True
@@ -203,7 +201,7 @@ def load_data():
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']:
@@ -252,7 +250,7 @@ def get_env_data(env_id):
252
  'products':[],
253
  'categories': [],
254
  'orders': {},
255
- 'staff':[],
256
  'inventory_history':[],
257
  'settings': {
258
  'organization_name': f'Shop {env_id}',
@@ -262,7 +260,6 @@ def get_env_data(env_id):
262
  'whatsapp_number': DEFAULT_WHATSAPP_NUMBER,
263
  'currency': 'T',
264
  'track_inventory': False,
265
- 'hide_stock_from_clients': False,
266
  'use_barcodes': False,
267
  'business_type': 'mixed',
268
  'system_mode': 'both',
@@ -294,7 +291,7 @@ def update_order_totals(order, business_type):
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
@@ -793,7 +790,6 @@ CATALOG_TEMPLATE = '''
793
  const mode = '{{ mode }}';
794
  const staffId = '{{ staff_id }}';
795
  const trackInventory = {{ 'true' if settings.track_inventory else 'false' }};
796
- const hideStock = {{ 'true' if settings.hide_stock_from_clients else 'false' }};
797
  const businessType = '{{ settings.business_type }}';
798
  const cFields = {{ settings.customer_fields|tojson }};
799
 
@@ -914,14 +910,13 @@ CATALOG_TEMPLATE = '''
914
  let mainControlsHtml = '';
915
 
916
  let moq = (businessType === 'wholesale' && parseInt(p.min_order) > 0) ? parseInt(p.min_order) : 1;
917
- let showStock = trackInventory && !(hideStock && mode !== 'pos');
918
 
919
  if (p.variants && p.variants.length > 0) {
920
  variantsHtml = `<div class="variants-list">`;
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,7 +951,7 @@ CATALOG_TEMPLATE = '''
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 = '';
@@ -2348,14 +2343,6 @@ ADMIN_TEMPLATE = '''
2348
  <label>Название магазина:</label>
2349
  <input type="text" name="organization_name" value="{{ settings.organization_name }}" required>
2350
  </div>
2351
-
2352
- <div class="settings-row">
2353
- <label>Пароль админ-панели:</label>
2354
- <div style="display:flex; align-items:center; gap:10px; flex:3;">
2355
- <label style="min-width:auto; cursor:pointer;"><input type="checkbox" name="admin_password_enabled" {% if settings.admin_password_enabled %}checked{% endif %}> Включить</label>
2356
- <input type="text" name="admin_password" value="{{ settings.admin_password }}" placeholder="Пароль">
2357
- </div>
2358
- </div>
2359
 
2360
  <div class="settings-row">
2361
  <label>Тип бизнеса:</label>
@@ -2400,9 +2387,6 @@ ADMIN_TEMPLATE = '''
2400
  {% if sys_mode != 'external' %}
2401
  <div style="font-weight: 600; margin-bottom: 5px; border-top: 1px solid var(--border); padding-top: 15px;">Учет товара:</div>
2402
  <label><input type="checkbox" name="track_inventory" {% if settings.track_inventory %}checked{% endif %}> Включить остатки на складе</label>
2403
- {% if sys_mode == 'both' %}
2404
- <label><input type="checkbox" name="hide_stock_from_clients" {% if settings.hide_stock_from_clients %}checked{% endif %}> Клиент не видит остатков (в онлайн каталоге)</label>
2405
- {% endif %}
2406
  <label><input type="checkbox" name="use_barcodes" {% if settings.use_barcodes %}checked{% endif %}> Использовать штрих-коды</label>
2407
  {% endif %}
2408
 
@@ -3613,7 +3597,7 @@ def create_environment():
3613
  if new_id not in all_data:
3614
  break
3615
  all_data[new_id] = {
3616
- 'products':[], 'categories':[], 'orders': {}, 'staff': [], 'inventory_history':[],
3617
  'settings': {
3618
  "organization_name": f"Shop {new_id}",
3619
  "admin_password_enabled": False,
@@ -3622,7 +3606,6 @@ def create_environment():
3622
  "whatsapp_number": DEFAULT_WHATSAPP_NUMBER,
3623
  "currency": "T",
3624
  "track_inventory": False,
3625
- "hide_stock_from_clients": False,
3626
  "use_barcodes": False,
3627
  "business_type": "mixed",
3628
  "system_mode": "both",
@@ -4002,7 +3985,7 @@ def apply_discount(env_id, order_id):
4002
  except ValueError:
4003
  order['global_discount'] = 0
4004
 
4005
- for item in order.get('cart',[]):
4006
  item_disc = request.form.get(f"item_discount_{item['c_key']}", 0)
4007
  try:
4008
  item['discount'] = float(item_disc)
@@ -4127,7 +4110,7 @@ def api_inventory(env_id):
4127
  if p['product_id'] == pid:
4128
  name = p['name']
4129
  v_name = ""
4130
- if vidx != -1 and vidx < len(p.get('variants',[])):
4131
  v_name = p['variants'][vidx]['name']
4132
  curr = p['variants'][vidx].get('stock', 0)
4133
  curr = int(curr) if str(curr).lstrip('-').strip() != "" and curr is not None else 0
@@ -4157,12 +4140,12 @@ def admin(env_id):
4157
  if settings.get('admin_password_enabled') and not session.get(f'admin_auth_{env_id}'):
4158
  return redirect(url_for('admin_login', env_id=env_id))
4159
 
4160
- products = data.get('products',[])
4161
  categories = data.get('categories',[])
4162
  staff = data.get('staff',[])
4163
  orders = data.get('orders', {})
4164
 
4165
- pending_orders =[o for o in orders.values() if o.get('status') == 'pending']
4166
  pending_orders.sort(key=lambda x: x.get('created_at', ''), reverse=True)
4167
 
4168
  unassembled_count = len([o for o in orders.values() if not is_order_fully_assembled(o)])
@@ -4175,330 +4158,7 @@ def admin(env_id):
4175
  staff_name = request.form.get('staff_name', '').strip()
4176
  staff_wa = request.form.get('staff_whatsapp', '').strip()
4177
  if staff_name and staff_wa:
4178
- staff.append({'id':'zip':'cf_zip' in request.form
4179
- }
4180
-
4181
- logo_file = request.files.get('logo')
4182
- if logo_file and logo_file.filename and HF_TOKEN_WRITE:
4183
- uploads_dir = 'uploads_temp'
4184
- os.makedirs(uploads_dir, exist_ok=True)
4185
- ext = os.path.splitext(logo_file.filename)[1].lower()
4186
- if ext in ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.svg']:
4187
- logo_filename = f"logo_{uuid4().hex}{ext}"
4188
- temp_path = os.path.join(uploads_dir, logo_filename)
4189
- logo_file.save(temp_path)
4190
- try:
4191
- api = HfApi()
4192
- api.upload_file(
4193
- path_or_fileobj=temp_path,
4194
- path_in_repo=f"logos/{logo_filename}",
4195
- repo_id=REPO_ID,
4196
- repo_type="dataset",
4197
- token=HF_TOKEN_WRITE
4198
- )
4199
- settings['logo_url'] = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/logos/{logo_filename}"
4200
- except Exception:
4201
- pass
4202
- finally:
4203
- if os.path.exists(temp_path):
4204
- os.remove(temp_path)
4205
-
4206
- settings['socials']['wa']['enabled'] = 'wa_enabled' in request.form
4207
- settings['socials']['wa']['url'] = request.form.get('wa_url', '').strip()
4208
- settings['socials']['ig']['enabled'] = 'ig_enabled' in request.form
4209
- settings['socials']['ig']['url'] = request.form.get('ig_url', '').strip()
4210
- settings['socials']['tg']['enabled'] = 'tg_enabled' in request.form
4211
- settings['socials']['tg']['url'] = request.form.get('tg_url', '').strip()
4212
-
4213
- data['settings'] = settings
4214
- save_env_data(env_id, data)
4215
-
4216
- elif action == 'add_category':
4217
- cat_name = request.form.get('category_name', '').strip()
4218
- if cat_name and cat_name not in categories:
4219
- categories.append(cat_name)
4220
- data['categories'] = categories
4221
- save_env_data(env_id, data)
4222
-
4223
- elif action == 'edit_category':
4224
- old_cat = request.form.get('old_category_name', '')
4225
- new_cat = request.form.get('new_category_name', '').strip()
4226
- if old_cat and new_cat and old_cat in categories and new_cat not in categories:
4227
- idx = categories.index(old_cat)
4228
- categories[idx] = new_cat
4229
- for p in products:
4230
- if p.get('category') == old_cat:
4231
- p['category'] = new_cat
4232
- data['categories'] = categories
4233
- data['products'] = products
4234
- save_env_data(env_id, data)
4235
-
4236
- elif action == 'delete_category':
4237
- cat_name = request.form.get('category_name')
4238
- if cat_name in categories:
4239
- categories.remove(cat_name)
4240
- data['products'] =[p for p in products if p.get('category') != cat_name]
4241
- data['categories'] = categories
4242
- save_env_data(env_id, data)
4243
-
4244
- elif action == 'add_product':
4245
- name = request.form.get('name', '').strip()
4246
- barcode = request.form.get('barcode', '').strip()
4247
- price_str = request.form.get('price', '')
4248
- price = float(price_str) if price_str else ""
4249
-
4250
- ppb_str = request.form.get('pieces_per_box', '')
4251
- pieces_per_box = int(ppb_str) if ppb_str else ""
4252
-
4253
- bp_str = request.form.get('box_price', '')
4254
- box_price = float(bp_str) if bp_str else ""
4255
-
4256
- moq_str = request.form.get('min_order', '')
4257
- min_order = int(moq_str) if moq_str else ""
4258
-
4259
- stock_str = request.form.get('stock', '')
4260
- main_stock = int(stock_str) if stock_str else ""
4261
-
4262
- description = request.form.get('description', '').strip()
4263
- category = request.form.get('category')
4264
- has_variant_prices = 'has_variant_prices' in request.form
4265
-
4266
- variant_names = request.form.getlist('variant_name[]')
4267
- variant_barcodes = request.form.getlist('variant_barcode[]')
4268
- variant_prices = request.form.getlist('variant_price[]')
4269
- variant_box_prices = request.form.getlist('variant_box_price[]')
4270
- variant_stocks = request.form.getlist('variant_stock[]')
4271
- variant_ppbs = request.form.getlist('variant_pieces_per_box[]')
4272
- variants =[]
4273
-
4274
- for i in range(len(variant_names)):
4275
- v_name = variant_names[i].strip()
4276
- if v_name:
4277
- v_price = price
4278
- v_box_price = box_price
4279
- if has_variant_prices:
4280
- if i < len(variant_prices) and variant_prices[i]:
4281
- v_price = float(variant_prices[i])
4282
- if i < len(variant_box_prices) and variant_box_prices[i]:
4283
- v_box_price = float(variant_box_prices[i])
4284
-
4285
- v_stock = ""
4286
- if i < len(variant_stocks) and variant_stocks[i]:
4287
- v_stock = int(variant_stocks[i])
4288
-
4289
- v_barcode = ""
4290
- if i < len(variant_barcodes) and variant_barcodes[i]:
4291
- v_barcode = variant_barcodes[i].strip()
4292
-
4293
- v_ppb = pieces_per_box
4294
- if i < len(variant_ppbs) and variant_ppbs[i]:
4295
- v_ppb = int(variant_ppbs[i])
4296
-
4297
- variants.append({
4298
- "name": v_name,
4299
- "barcode": v_barcode,
4300
- "price": v_price,
4301
- "box_price": v_box_price,
4302
- "stock": v_stock,
4303
- "pieces_per_box": v_ppb
4304
- })
4305
-
4306
- uploaded_photos = request.files.getlist('photos')[:10]
4307
-
4308
- photos_list =[]
4309
- if uploaded_photos and HF_TOKEN_WRITE:
4310
- uploads_dir = 'uploads_temp'
4311
- os.makedirs(uploads_dir, exist_ok=True)
4312
- api = HfApi()
4313
- for photo in uploaded_photos:
4314
- if photo and photo.filename:
4315
- ext = os.path.splitext(photo.filename)[1].lower()
4316
- if ext not in['.jpg', '.jpeg', '.png', '.webp', '.gif']:
4317
- continue
4318
- photo_filename = f"{uuid4().hex}{ext}"
4319
- temp_path = os.path.join(uploads_dir, photo_filename)
4320
- photo.save(temp_path)
4321
- try:
4322
- api.upload_file(
4323
- path_or_fileobj=temp_path,
4324
- path_in_repo=f"photos/{photo_filename}",
4325
- repo_id=REPO_ID,
4326
- repo_type="dataset",
4327
- token=HF_TOKEN_WRITE
4328
- )
4329
- photos_list.append(photo_filename)
4330
- except Exception:
4331
- pass
4332
- finally:
4333
- if os.path.exists(temp_path):
4334
- os.remove(temp_path)
4335
-
4336
- new_product = {
4337
- 'product_id': uuid4().hex,
4338
- 'name': name,
4339
- 'barcode': barcode,
4340
- 'price': price,
4341
- 'pieces_per_box': pieces_per_box,
4342
- 'box_price': box_price,
4343
- 'min_order': min_order,
4344
- 'stock': main_stock,
4345
- 'description': description,
4346
- 'category': category,
4347
- 'photos': photos_list,
4348
- 'variants': variants,
4349
- 'has_variant_prices': has_variant_prices
4350
- }
4351
- products.append(new_product)
4352
- data['products'] = products
4353
- save_env_data(env_id, data)
4354
-
4355
- elif action == 'edit_product':
4356
- pid = request.form.get('product_id')
4357
- name = request.form.get('name', '').strip()
4358
- barcode = request.form.get('barcode', '').strip()
4359
-
4360
- price_str = request.form.get('price', '')
4361
- price = float(price_str) if price_str else ""
4362
-
4363
- ppb_str = request.form.get('pieces_per_box', '')
4364
- pieces_per_box = int(ppb_str) if ppb_str else ""
4365
-
4366
- bp_str = request.form.get('box_price', '')
4367
- box_price = float(bp_str) if bp_str else ""
4368
-
4369
- moq_str = request.form.get('min_order', '')
4370
- min_order = int(moq_str) if moq_str else ""
4371
-
4372
- stock_str = request.form.get('stock', '')
4373
- main_stock = int(stock_str) if stock_str else ""
4374
-
4375
- description = request.form.get('description', '').strip()
4376
- has_variant_prices = 'has_variant_prices' in request.form
4377
-
4378
- variant_names = request.form.getlist('variant_name[]')
4379
- variant_barcodes = request.form.getlist('variant_barcode[]')
4380
- variant_prices = request.form.getlist('variant_price[]')
4381
- variant_box_prices = request.form.getlist('variant_box_price[]')
4382
- variant_stocks = request.form.getlist('variant_stock[]')
4383
- variant_ppbs = request.form.getlist('variant_pieces_per_box[]')
4384
- variants =[]
4385
-
4386
- for i in range(len(variant_names)):
4387
- v_name = variant_names[i].strip()
4388
- if v_name:
4389
- v_price = price
4390
- v_box_price = box_price
4391
- if has_variant_prices:
4392
- if i < len(variant_prices) and variant_prices[i]:
4393
- v_price = float(variant_prices[i])
4394
- if i < len(variant_box_prices) and variant_box_prices[i]:
4395
- v_box_price = float(variant_box_prices[i])
4396
- v_stock = ""
4397
- if i < len(variant_stocks) and variant_stocks[i]:
4398
- v_stock = int(variant_stocks[i])
4399
- v_barcode = ""
4400
- if i < len(variant_barcodes) and variant_barcodes[i]:
4401
- v_barcode = variant_barcodes[i].strip()
4402
- v_ppb = pieces_per_box
4403
- if i < len(variant_ppbs) and variant_ppbs[i]:
4404
- v_ppb = int(variant_ppbs[i])
4405
- variants.append({
4406
- "name": v_name,
4407
- "barcode": v_barcode,
4408
- "price": v_price,
4409
- "box_price": v_box_price,
4410
- "stock": v_stock,
4411
- "pieces_per_box": v_ppb
4412
- })
4413
-
4414
- uploaded_photos = request.files.getlist('photos')[:10]
4415
-
4416
- photos_list =[]
4417
- if uploaded_photos and uploaded_photos[0].filename and HF_TOKEN_WRITE:
4418
- uploads_dir = 'uploads_temp'
4419
- os.makedirs(uploads_dir, exist_ok=True)
4420
- api = HfApi()
4421
- for photo in uploaded_photos:
4422
- if photo and photo.filename:
4423
- ext = os.path.splitext(photo.filename)[1].lower()
4424
- if ext not in['.jpg', '.jpeg', '.png', '.webp', '.gif']:
4425
- continue
4426
- photo_filename = f"{uuid4().hex}{ext}"
4427
- temp_path = os.path.join(uploads_dir, photo_filename)
4428
- photo.save(temp_path)
4429
- try:
4430
- api.upload_file(
4431
- path_or_fileobj=temp_path,
4432
- path_in_repo=f"photos/{photo_filename}",
4433
- repo_id=REPO_ID,
4434
- repo_type="dataset",
4435
- token=HF_TOKEN_WRITE
4436
- )
4437
- photos_list.append(photo_filename)
4438
- except Exception:
4439
- pass
4440
- finally:
4441
- if os.path.exists(temp_path):
4442
- os.remove(temp_path)
4443
-
4444
- for p in products:
4445
- if p.get('product_id') == pid:
4446
- p['name'] = name
4447
- p['barcode'] = barcode
4448
- p['price'] = price
4449
- p['pieces_per_box'] = pieces_per_box
4450
- p['box_price'] = box_price
4451
- p['min_order'] = min_order
4452
- p['stock'] = main_stock
4453
- p['description'] = description
4454
- p['variants'] = variants
4455
- p['has_variant_prices'] = has_variant_prices
4456
- if photos_list:
4457
- p['photos'] = photos_list
4458
- break
4459
- data['products'] = products
4460
- save_env_data(env_id, data)
4461
-
4462
- elif action == 'delete_product':
4463
- pid = request.form.get('product_id')
4464
- data['products'] =[p for p in products if p.get('product_id') != pid]
4465
- save_env_data(env_id, data)
4466
-
4467
- return redirect(url_for('admin', env_id=env_id))
4468
-
4469
- return render_template_string(
4470
- ADMIN_TEMPLATE,
4471
- products=products,
4472
- categories=categories,
4473
- repo_id=REPO_ID,
4474
- currency_code=settings.get('currency', 'T'),
4475
- env_id=env_id,
4476
- settings=settings,
4477
- staff=staff,
4478
- pending_orders=pending_orders,
4479
- unassembled_count=unassembled_count,
4480
- low_stock_count=low_stock_count
4481
- )
4482
-
4483
- @app.route('/<env_id>/force_upload', methods=['POST'])
4484
- def force_upload(env_id):
4485
- upload_db_to_hf()
4486
- return redirect(url_for('admin', env_id=env_id))
4487
-
4488
- @app.route('/<env_id>/force_download', methods=['POST'])
4489
- def force_download(env_id):
4490
- download_db_from_hf()
4491
- return redirect(url_for('admin', env_id=env_id))
4492
-
4493
- if __name__ == '__main__':
4494
- download_db_from_hf()
4495
- load_data()
4496
-
4497
- if HF_TOKEN_WRITE:
4498
- threading.Thread(target=periodic_backup, daemon=True).start()
4499
-
4500
- port = int(os.environ.get('PORT', 7860))
4501
- app.run(host='0.0.0.0', port=port) uuid4().hex, 'name': staff_name, 'whatsapp': staff_wa})
4502
  data['staff'] = staff
4503
  save_env_data(env_id, data)
4504
 
@@ -4509,8 +4169,6 @@ if __name__ == '__main__':
4509
 
4510
  elif action == 'update_settings':
4511
  settings['organization_name'] = request.form.get('organization_name', '').strip()
4512
- settings['admin_password_enabled'] = 'admin_password_enabled' in request.form
4513
- settings['admin_password'] = request.form.get('admin_password', '').strip()
4514
  settings['business_type'] = request.form.get('business_type', 'mixed')
4515
  settings['whatsapp_number'] = request.form.get('whatsapp_number', '').strip()
4516
  settings['currency'] = request.form.get('currency', 'T')
@@ -4521,15 +4179,13 @@ if __name__ == '__main__':
4521
  else:
4522
  settings['track_inventory'] = 'track_inventory' in request.form
4523
  settings['use_barcodes'] = 'use_barcodes' in request.form
4524
-
4525
- settings['hide_stock_from_clients'] = 'hide_stock_from_clients' in request.form
4526
 
4527
  settings['customer_fields'] = {
4528
  'name': 'cf_name' in request.form,
4529
  'phone': 'cf_phone' in request.form,
4530
  'city': 'cf_city' in request.form,
4531
  'address': 'cf_address' in request.form,
4532
- 'zip': 'cf_zip' in request.form
4533
  }
4534
 
4535
  logo_file = request.files.get('logo')
@@ -4537,7 +4193,7 @@ if __name__ == '__main__':
4537
  uploads_dir = 'uploads_temp'
4538
  os.makedirs(uploads_dir, exist_ok=True)
4539
  ext = os.path.splitext(logo_file.filename)[1].lower()
4540
- if ext in ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.svg']:
4541
  logo_filename = f"logo_{uuid4().hex}{ext}"
4542
  temp_path = os.path.join(uploads_dir, logo_filename)
4543
  logo_file.save(temp_path)
 
37
 
38
  def download_db_from_hf(specific_file=None, retries=3, delay=5):
39
  token_to_use = HF_TOKEN_READ if HF_TOKEN_READ else HF_TOKEN_WRITE
40
+ files_to_download = [specific_file] if specific_file else SYNC_FILES
41
  all_successful = True
42
 
43
  for file_name in files_to_download:
 
90
  return
91
  try:
92
  api = HfApi()
93
+ files_to_upload = [specific_file] if specific_file else SYNC_FILES
94
 
95
  for file_name in files_to_upload:
96
  if os.path.exists(file_name):
 
137
  'products': data.get('products', []),
138
  'categories': data.get('categories',[]),
139
  'orders': data.get('orders', {}),
140
+ 'staff': [],
141
  'inventory_history':[],
142
  'settings': {
143
  'organization_name': 'Default Shop',
 
147
  'whatsapp_number': DEFAULT_WHATSAPP_NUMBER,
148
  'currency': 'T',
149
  'track_inventory': False,
 
150
  'use_barcodes': False,
151
  'business_type': 'mixed',
152
  'system_mode': 'both',
 
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'] =[]
 
181
  if 'whatsapp_number' not in settings: settings['whatsapp_number'] = DEFAULT_WHATSAPP_NUMBER; changed = True
182
  if 'currency' not in settings: settings['currency'] = 'T'; changed = True
183
  if 'track_inventory' not in settings: settings['track_inventory'] = False; changed = True
 
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
 
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']:
 
250
  'products':[],
251
  'categories': [],
252
  'orders': {},
253
+ 'staff': [],
254
  'inventory_history':[],
255
  'settings': {
256
  'organization_name': f'Shop {env_id}',
 
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',
 
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
 
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
 
 
910
  let mainControlsHtml = '';
911
 
912
  let moq = (businessType === 'wholesale' && parseInt(p.min_order) > 0) ? parseInt(p.min_order) : 1;
 
913
 
914
  if (p.variants && p.variants.length > 0) {
915
  variantsHtml = `<div class="variants-list">`;
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
  });
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 = '';
 
2343
  <label>Название магазина:</label>
2344
  <input type="text" name="organization_name" value="{{ settings.organization_name }}" required>
2345
  </div>
 
 
 
 
 
 
 
 
2346
 
2347
  <div class="settings-row">
2348
  <label>Тип бизнеса:</label>
 
2387
  {% if sys_mode != 'external' %}
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
 
 
3597
  if new_id not in all_data:
3598
  break
3599
  all_data[new_id] = {
3600
+ 'products':[], 'categories': [], 'orders': {}, 'staff': [], 'inventory_history':[],
3601
  'settings': {
3602
  "organization_name": f"Shop {new_id}",
3603
  "admin_password_enabled": False,
 
3606
  "whatsapp_number": DEFAULT_WHATSAPP_NUMBER,
3607
  "currency": "T",
3608
  "track_inventory": False,
 
3609
  "use_barcodes": False,
3610
  "business_type": "mixed",
3611
  "system_mode": "both",
 
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)
 
4110
  if p['product_id'] == pid:
4111
  name = p['name']
4112
  v_name = ""
4113
+ if vidx != -1 and vidx < len(p.get('variants', [])):
4114
  v_name = p['variants'][vidx]['name']
4115
  curr = p['variants'][vidx].get('stock', 0)
4116
  curr = int(curr) if str(curr).lstrip('-').strip() != "" and curr is not None else 0
 
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)])
 
4158
  staff_name = request.form.get('staff_name', '').strip()
4159
  staff_wa = request.form.get('staff_whatsapp', '').strip()
4160
  if staff_name and staff_wa:
4161
+ staff.append({'id': uuid4().hex, 'name': staff_name, 'whatsapp': staff_wa})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4162
  data['staff'] = staff
4163
  save_env_data(env_id, data)
4164
 
 
4169
 
4170
  elif action == 'update_settings':
4171
  settings['organization_name'] = request.form.get('organization_name', '').strip()
 
 
4172
  settings['business_type'] = request.form.get('business_type', 'mixed')
4173
  settings['whatsapp_number'] = request.form.get('whatsapp_number', '').strip()
4174
  settings['currency'] = request.form.get('currency', 'T')
 
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,
4185
  'phone': 'cf_phone' in request.form,
4186
  'city': 'cf_city' in request.form,
4187
  'address': 'cf_address' in request.form,
4188
+ 'zip': 'cf_zip' in request.form
4189
  }
4190
 
4191
  logo_file = request.files.get('logo')
 
4193
  uploads_dir = 'uploads_temp'
4194
  os.makedirs(uploads_dir, exist_ok=True)
4195
  ext = os.path.splitext(logo_file.filename)[1].lower()
4196
+ if ext in['.jpg', '.jpeg', '.png', '.webp', '.gif', '.svg']:
4197
  logo_filename = f"logo_{uuid4().hex}{ext}"
4198
  temp_path = os.path.join(uploads_dir, logo_filename)
4199
  logo_file.save(temp_path)