Update app.py
Browse files
app.py
CHANGED
|
@@ -235,15 +235,11 @@ def load_data():
|
|
| 235 |
if 'variants' not in product: product['variants'] =[]; changed = True
|
| 236 |
if 'has_variant_prices' not in product: product['has_variant_prices'] = False; changed = True
|
| 237 |
if 'stock' not in product: product['stock'] = ""; changed = True
|
| 238 |
-
if 'is_available' not in product: product['is_available'] = True; changed = True
|
| 239 |
-
if 'volume_prices' not in product: product['volume_prices'] = []; changed = True
|
| 240 |
for v in product['variants']:
|
| 241 |
if 'stock' not in v: v['stock'] = ""; changed = True
|
| 242 |
if 'box_price' not in v: v['box_price'] = ""; changed = True
|
| 243 |
if 'barcode' not in v: v['barcode'] = ""; changed = True
|
| 244 |
if 'pieces_per_box' not in v: v['pieces_per_box'] = product.get('pieces_per_box', ""); changed = True
|
| 245 |
-
if 'is_available' not in v: v['is_available'] = True; changed = True
|
| 246 |
-
if 'volume_prices' not in v: v['volume_prices'] = []; changed = True
|
| 247 |
|
| 248 |
for order_id, order in env_data['orders'].items():
|
| 249 |
if 'status' not in order: order['status'] = 'confirmed'; changed = True
|
|
@@ -253,7 +249,6 @@ def load_data():
|
|
| 253 |
for item in order.get('cart', []):
|
| 254 |
if 'discount' not in item: item['discount'] = 0; changed = True
|
| 255 |
if 'category' not in item: item['category'] = 'Без категории'; changed = True
|
| 256 |
-
if 'volume_prices' not in item: item['volume_prices'] = []; changed = True
|
| 257 |
|
| 258 |
if changed or not os.path.exists(DATA_FILE):
|
| 259 |
try:
|
|
@@ -318,14 +313,6 @@ def save_env_data(env_id, env_data):
|
|
| 318 |
all_data[env_id] = env_data
|
| 319 |
save_data(all_data)
|
| 320 |
|
| 321 |
-
def get_applicable_price(base_price, volume_prices, qty):
|
| 322 |
-
if not volume_prices:
|
| 323 |
-
return base_price
|
| 324 |
-
for vp in sorted(volume_prices, key=lambda x: x['qty'], reverse=True):
|
| 325 |
-
if qty >= vp['qty']:
|
| 326 |
-
return vp['price']
|
| 327 |
-
return base_price
|
| 328 |
-
|
| 329 |
def update_order_totals(order, business_type):
|
| 330 |
total = 0
|
| 331 |
global_discount = float(order.get('global_discount', 0))
|
|
@@ -337,15 +324,13 @@ def update_order_totals(order, business_type):
|
|
| 337 |
c_price = float(i.get('price', 0))
|
| 338 |
c_box_price = float(i.get('cart_box_price', 0))
|
| 339 |
item_discount = float(i.get('discount', 0))
|
| 340 |
-
volume_prices = i.get('volume_prices', [])
|
| 341 |
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
unit = c_box_price / ppb
|
| 347 |
|
| 348 |
-
discounted_price = max(0,
|
| 349 |
item_total = discounted_price * qty
|
| 350 |
i['calculated_price'] = round(discounted_price, 2)
|
| 351 |
total += item_total
|
|
@@ -692,7 +677,6 @@ CATALOG_TEMPLATE = '''
|
|
| 692 |
.process-return-btn { background: #e17055; color: white; border: none; padding: 8px 15px; border-radius: 8px; cursor: pointer; margin-top: 10px; width: 100%; font-weight: 600; }
|
| 693 |
|
| 694 |
.history-btn { background: #0984e3; color: white; border: none; padding: 8px 15px; border-radius: 8px; cursor: pointer; margin-top: 10px; width: 100%; font-weight: 600; text-decoration: none; display: block; text-align: center; box-sizing: border-box; }
|
| 695 |
-
.unavailable-item { opacity: 0.5; filter: grayscale(1); }
|
| 696 |
|
| 697 |
@media (min-width: 768px) {
|
| 698 |
.categories-container { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
|
|
@@ -987,7 +971,6 @@ CATALOG_TEMPLATE = '''
|
|
| 987 |
if (p.variants && p.variants.length > 0) {
|
| 988 |
variantsHtml = `<div class="variants-list">`;
|
| 989 |
p.variants.forEach((v, idx) => {
|
| 990 |
-
let vClass = v.is_available === false ? 'unavailable-item' : '';
|
| 991 |
let vPrice = p.has_variant_prices ? v.price : p.price;
|
| 992 |
let vBoxPrice = p.has_variant_prices ? (v.box_price || '') : (p.box_price || '');
|
| 993 |
let vStockHtml = showStock && v.stock !== "" && v.stock !== null ? `<div class="variant-stock">Остаток: ${v.stock} шт</div>` : '';
|
|
@@ -1000,39 +983,25 @@ CATALOG_TEMPLATE = '''
|
|
| 1000 |
priceText += `<br><span style="font-size:0.8rem; color:#636e72;">Упаковка: ${vBoxPrice} ${currency}</span>`;
|
| 1001 |
}
|
| 1002 |
|
| 1003 |
-
let
|
| 1004 |
-
if (businessType ==
|
| 1005 |
-
|
| 1006 |
-
}
|
| 1007 |
-
|
| 1008 |
-
let controlsHtml = '';
|
| 1009 |
-
if (v.is_available === false) {
|
| 1010 |
-
controlsHtml = `<span style="color:var(--danger); font-weight:bold; font-size:0.85rem;">Нет в наличии</span>`;
|
| 1011 |
-
} else {
|
| 1012 |
-
let addBoxBtnVariant = '';
|
| 1013 |
-
if (businessType !== 'retail' && vPpb > 1) {
|
| 1014 |
-
addBoxBtnVariant = `<button class="box-btn" style="height:32px; margin-right:5px;" onclick="updateCart('${p.product_id}', ${vPpb}, null, false, '${cKey}', ${moq})">+ Упаковка</button>`;
|
| 1015 |
-
}
|
| 1016 |
-
controlsHtml = `
|
| 1017 |
-
${addBoxBtnVariant}
|
| 1018 |
-
<div class="quantity-control" style="border:none; background:var(--surface);">
|
| 1019 |
-
<button onclick="updateCart('${p.product_id}', -1, null, false, '${cKey}', ${moq})"><i class="fas fa-minus" style="font-size:0.8rem;"></i></button>
|
| 1020 |
-
<input type="number" id="qty-${cKey}" value="${qty}" onchange="manualUpdateCart('${cKey}', this.value, ${moq})">
|
| 1021 |
-
<button onclick="updateCart('${p.product_id}', 1, null, false, '${cKey}', ${moq})"><i class="fas fa-plus" style="font-size:0.8rem;"></i></button>
|
| 1022 |
-
</div>
|
| 1023 |
-
`;
|
| 1024 |
}
|
| 1025 |
|
| 1026 |
variantsHtml += `
|
| 1027 |
-
<div class="variant-item
|
| 1028 |
<div class="variant-info">
|
| 1029 |
<span class="variant-name">${v.name}</span>
|
| 1030 |
<span class="variant-price">${priceText}</span>
|
| 1031 |
-
${vVolHtml}
|
| 1032 |
${vStockHtml}
|
| 1033 |
</div>
|
| 1034 |
<div style="display:flex; align-items:center;">
|
| 1035 |
-
${
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1036 |
</div>
|
| 1037 |
</div>
|
| 1038 |
`;
|
|
@@ -1042,58 +1011,36 @@ CATALOG_TEMPLATE = '''
|
|
| 1042 |
let mStockHtml = showStock && p.stock !== "" && p.stock !== null ? `<div style="font-size:0.8rem; color:#0984e3; margin-top:4px;">Остаток: ${p.stock} шт</div>` : '';
|
| 1043 |
let qty = cart[p.product_id] ? cart[p.product_id].quantity : 0;
|
| 1044 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1045 |
let priceText = `${p.price} ${currency}`;
|
| 1046 |
if (businessType === 'mixed' && p.box_price && ppb > 1) {
|
| 1047 |
priceText += `<br><span style="font-size:0.8rem; color:#636e72;">Упаковка: ${p.box_price} ${currency}</span>`;
|
| 1048 |
}
|
| 1049 |
-
|
| 1050 |
-
let volHtml = '';
|
| 1051 |
-
if (businessType === 'wholesale' && p.volume_prices && p.volume_prices.length > 0) {
|
| 1052 |
-
volHtml = `<div style="font-size:0.8rem; color:#636e72; margin-top:4px;">Опт: ` + p.volume_prices.map(vp => `от ${vp.qty} шт - ${vp.price} ${currency}`).join(', ') + `</div>`;
|
| 1053 |
-
}
|
| 1054 |
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
<div
|
| 1058 |
-
<div
|
| 1059 |
-
|
| 1060 |
-
${volHtml}
|
| 1061 |
-
${mStockHtml}
|
| 1062 |
-
</div>
|
| 1063 |
-
<div class="controls-wrapper">
|
| 1064 |
-
<span style="color:var(--danger); font-weight:bold;">Нет в наличии</span>
|
| 1065 |
-
</div>
|
| 1066 |
</div>
|
| 1067 |
-
|
| 1068 |
-
|
| 1069 |
-
|
| 1070 |
-
|
| 1071 |
-
|
| 1072 |
-
|
| 1073 |
-
mainControlsHtml = `
|
| 1074 |
-
<div class="product-bottom">
|
| 1075 |
-
<div style="display:flex; flex-direction:column;">
|
| 1076 |
-
<div class="product-price">${priceText}</div>
|
| 1077 |
-
${volHtml}
|
| 1078 |
-
${mStockHtml}
|
| 1079 |
-
</div>
|
| 1080 |
-
<div class="controls-wrapper">
|
| 1081 |
-
${addBoxBtn}
|
| 1082 |
-
<div class="quantity-control">
|
| 1083 |
-
<button onclick="updateCart('${p.product_id}', -1, null, false, null, ${moq})"><i class="fas fa-minus" style="font-size:0.8rem;"></i></button>
|
| 1084 |
-
<input type="number" id="qty-${p.product_id}" value="${qty}" onchange="manualUpdateCart('${p.product_id}', this.value, ${moq})">
|
| 1085 |
-
<button onclick="updateCart('${p.product_id}', 1, null, false, null, ${moq})"><i class="fas fa-plus" style="font-size:0.8rem;"></i></button>
|
| 1086 |
-
</div>
|
| 1087 |
</div>
|
| 1088 |
</div>
|
| 1089 |
-
|
| 1090 |
-
|
| 1091 |
}
|
| 1092 |
|
| 1093 |
-
let pClass = p.is_available === false && (!p.variants || p.variants.length === 0) ? 'unavailable-item' : '';
|
| 1094 |
-
|
| 1095 |
const div = document.createElement('div');
|
| 1096 |
-
div.className =
|
| 1097 |
div.innerHTML = `
|
| 1098 |
<div class="product-main-content">
|
| 1099 |
<div class="product-img-wrapper" ${imgClick}>
|
|
@@ -1140,9 +1087,6 @@ CATALOG_TEMPLATE = '''
|
|
| 1140 |
if (cKey.includes('___')) {
|
| 1141 |
varIdx = parseInt(cKey.split('___')[1]);
|
| 1142 |
}
|
| 1143 |
-
|
| 1144 |
-
if (varIdx !== -1 && p.variants[varIdx] && p.variants[varIdx].is_available === false) return;
|
| 1145 |
-
if (varIdx === -1 && p.is_available === false) return;
|
| 1146 |
|
| 1147 |
let pStock = "";
|
| 1148 |
let pPpb = parseInt(p.pieces_per_box) || 1;
|
|
@@ -1160,16 +1104,14 @@ CATALOG_TEMPLATE = '''
|
|
| 1160 |
let price = p.price;
|
| 1161 |
let bPrice = p.box_price || 0;
|
| 1162 |
let vName = "";
|
| 1163 |
-
let volPrices = p.volume_prices || [];
|
| 1164 |
if (varIdx !== -1 && p.variants[varIdx]) {
|
| 1165 |
if (p.has_variant_prices) {
|
| 1166 |
price = p.variants[varIdx].price;
|
| 1167 |
bPrice = p.variants[varIdx].box_price || 0;
|
| 1168 |
}
|
| 1169 |
vName = p.variants[varIdx].name;
|
| 1170 |
-
volPrices = p.variants[varIdx].volume_prices || [];
|
| 1171 |
}
|
| 1172 |
-
cart[cKey] = { ...p, quantity: 0, cart_price: price, cart_box_price: bPrice, pieces_per_box: pPpb, variant_name: vName, variant_idx: varIdx, discount: 0
|
| 1173 |
}
|
| 1174 |
|
| 1175 |
let currentQty = cart[cKey].quantity;
|
|
@@ -1226,14 +1168,6 @@ CATALOG_TEMPLATE = '''
|
|
| 1226 |
const pId = cKey.split('___')[0];
|
| 1227 |
updateCart(pId, 0, num, true, cKey, moq);
|
| 1228 |
}
|
| 1229 |
-
|
| 1230 |
-
function getApplicablePrice(basePrice, volumePrices, qty) {
|
| 1231 |
-
if (!volumePrices || volumePrices.length === 0) return basePrice;
|
| 1232 |
-
for (let i = 0; i < volumePrices.length; i++) {
|
| 1233 |
-
if (qty >= volumePrices[i].qty) return volumePrices[i].price;
|
| 1234 |
-
}
|
| 1235 |
-
return basePrice;
|
| 1236 |
-
}
|
| 1237 |
|
| 1238 |
function calculateItemPrice(item) {
|
| 1239 |
let ppb = parseInt(item.pieces_per_box) || 1;
|
|
@@ -1241,12 +1175,9 @@ CATALOG_TEMPLATE = '''
|
|
| 1241 |
let cBoxPrice = parseFloat(item.cart_box_price) || 0;
|
| 1242 |
let cPrice = parseFloat(item.cart_price) || 0;
|
| 1243 |
let disc = parseFloat(item.discount) || 0;
|
| 1244 |
-
let volPrices = item.volume_prices || [];
|
| 1245 |
|
| 1246 |
let unit = cPrice;
|
| 1247 |
-
if (businessType === 'wholesale' &&
|
| 1248 |
-
unit = getApplicablePrice(cPrice, volPrices, qty);
|
| 1249 |
-
} else if ((businessType === 'mixed' || businessType === 'wholesale') && cBoxPrice > 0 && ppb > 1 && qty >= ppb) {
|
| 1250 |
unit = cBoxPrice / ppb;
|
| 1251 |
}
|
| 1252 |
return Math.max(0, unit - disc) * qty;
|
|
@@ -2274,7 +2205,7 @@ ADMIN_TEMPLATE = '''
|
|
| 2274 |
|
| 2275 |
.form-group { display: flex; flex-direction: column; gap: 5px; flex: 1; min-width: 150px; }
|
| 2276 |
.form-group label { font-size: 0.85rem; font-weight: 600; color: #636e72; }
|
| 2277 |
-
.form-row { display: flex; gap: 15px; flex-wrap: wrap;
|
| 2278 |
|
| 2279 |
.file-input-wrapper { position: relative; width: 100%; }
|
| 2280 |
input[type="file"] { width: 100%; padding: 10px; border: 1px dashed #ccc; border-radius: 10px; background: #fafafa; font-size: 0.9rem; }
|
|
@@ -2288,11 +2219,9 @@ ADMIN_TEMPLATE = '''
|
|
| 2288 |
.social-item label { display: flex; align-items: center; gap: 5px; width: 150px; cursor: pointer; }
|
| 2289 |
|
| 2290 |
.variants-container { background: #f4f6f9; padding: 15px; border-radius: 10px; border: 1px dashed var(--border); display: flex; flex-direction: column; gap: 10px; }
|
| 2291 |
-
.variant-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-
|
| 2292 |
.variant-row .form-group { flex: 1 1 30%; min-width: 120px; }
|
| 2293 |
-
.remove-variant-btn { color: var(--danger); background: none; border: none; font-size: 1.2rem; cursor: pointer; padding: 5px;
|
| 2294 |
-
.move-variant-btns { position: absolute; top: 10px; right: 40px; display: flex; gap: 5px; }
|
| 2295 |
-
.move-variant-btns button { background: none; border: 1px solid var(--border); border-radius: 4px; cursor: pointer; font-size: 0.8rem; padding: 2px 5px; color: #636e72; }
|
| 2296 |
|
| 2297 |
.order-item { background: #fff; padding: 15px; border-radius: 10px; border: 1px solid var(--border); margin-bottom: 10px; }
|
| 2298 |
.order-header { display: flex; justify-content: space-between; font-weight: bold; margin-bottom: 10px; }
|
|
@@ -2301,18 +2230,15 @@ ADMIN_TEMPLATE = '''
|
|
| 2301 |
.staff-item { display: flex; flex-direction: column; gap: 10px; background: #fff; padding: 15px; border-radius: 10px; border: 1px solid var(--border); margin-bottom: 10px; }
|
| 2302 |
|
| 2303 |
.badge { background: var(--danger); color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.8rem; margin-left: 5px; }
|
| 2304 |
-
.vol-price-row { display: flex; gap: 5px; align-items: center; margin-top: 5px; }
|
| 2305 |
-
.vol-price-row input { padding: 6px; border: 1px solid var(--border); border-radius: 4px; font-size: 0.85rem; width: 80px; }
|
| 2306 |
-
.vol-price-row button { background: var(--danger); color: white; border: none; border-radius: 4px; cursor: pointer; padding: 6px 10px; font-size: 0.8rem; }
|
| 2307 |
-
.unavailable-item { opacity: 0.5; filter: grayscale(1); }
|
| 2308 |
|
| 2309 |
@media (max-width: 600px) {
|
| 2310 |
.header-panel { flex-direction: column; align-items: stretch; text-align: center; }
|
| 2311 |
.product-item { flex-direction: column; align-items: stretch; }
|
| 2312 |
.product-info { width: 100%; }
|
| 2313 |
.product-actions { align-self: flex-end; }
|
| 2314 |
-
.form-row { flex-direction: column; gap: 10px;
|
| 2315 |
-
.variant-row { flex-direction: column; align-items: stretch;
|
|
|
|
| 2316 |
}
|
| 2317 |
</style>
|
| 2318 |
</head>
|
|
@@ -2601,15 +2527,11 @@ ADMIN_TEMPLATE = '''
|
|
| 2601 |
<i class="fas fa-chevron-down" id="icon-cat-{{ loop.index }}" style="color: #636e72;"></i>
|
| 2602 |
<span class="cat-title-text"><i class="fas fa-folder-open" style="color:var(--info); margin-right:5px;"></i> {{ category }}</span>
|
| 2603 |
</div>
|
| 2604 |
-
<
|
| 2605 |
-
<
|
| 2606 |
-
<
|
| 2607 |
-
<
|
| 2608 |
-
|
| 2609 |
-
<input type="hidden" name="category_name" value="{{ category }}">
|
| 2610 |
-
<button type="submit" class="btn btn-danger"><i class="fas fa-trash-alt"></i></button>
|
| 2611 |
-
</form>
|
| 2612 |
-
</div>
|
| 2613 |
</div>
|
| 2614 |
<div class="category-content" id="cat-{{ loop.index }}">
|
| 2615 |
|
|
@@ -2650,11 +2572,6 @@ ADMIN_TEMPLATE = '''
|
|
| 2650 |
<input type="text" name="name" placeholder="Введите название" required autocomplete="off">
|
| 2651 |
</div>
|
| 2652 |
|
| 2653 |
-
<div class="form-group" style="flex:0;">
|
| 2654 |
-
<label> </label>
|
| 2655 |
-
<label style="display:flex; align-items:center; gap:5px; margin-top:10px; cursor:pointer;"><input type="checkbox" name="main_is_available" value="1" checked> В наличии</label>
|
| 2656 |
-
</div>
|
| 2657 |
-
|
| 2658 |
{% if settings.use_barcodes and sys_mode not in ['external', 'light_external'] %}
|
| 2659 |
<div class="form-group main-barcode-container">
|
| 2660 |
<label>Штрих-код</label>
|
|
@@ -2699,14 +2616,6 @@ ADMIN_TEMPLATE = '''
|
|
| 2699 |
{% endif %}
|
| 2700 |
</div>
|
| 2701 |
|
| 2702 |
-
{% if settings.business_type == 'wholesale' %}
|
| 2703 |
-
<div class="form-group main-vol-prices-container" style="background: #f9f9f9; padding: 10px; border-radius: 8px; border: 1px dashed #ccc; margin-top: 10px;">
|
| 2704 |
-
<label style="font-size:0.85rem; font-weight:bold; margin-bottom:5px; display:block;">Оптовые цены от количества (только без вариантов)</label>
|
| 2705 |
-
<div id="main_vol_prices_add_{{ loop.index }}"></div>
|
| 2706 |
-
<button type="button" class="btn btn-outline" style="padding:5px 10px; font-size:0.8rem; margin-top:5px;" onclick="addVolPriceRow('main_vol_prices_add_{{ loop.index }}', 'main')"><i class="fas fa-plus"></i> Добавить вариант цены</button>
|
| 2707 |
-
</div>
|
| 2708 |
-
{% endif %}
|
| 2709 |
-
|
| 2710 |
<div class="variants-container" id="variants-container-add-{{ loop.index }}">
|
| 2711 |
<div style="display:flex; justify-content:space-between; align-items:center;">
|
| 2712 |
<label style="font-weight:600; font-size:0.9rem;"><i class="fas fa-tags"></i> Варианты товара</label>
|
|
@@ -2731,7 +2640,7 @@ ADMIN_TEMPLATE = '''
|
|
| 2731 |
|
| 2732 |
{% for product in products %}
|
| 2733 |
{% if product.category == category %}
|
| 2734 |
-
<div class="product-item
|
| 2735 |
<div class="product-info">
|
| 2736 |
{% if product.photos and product.photos|length > 0 %}
|
| 2737 |
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product.photos[0] }}" class="product-img">
|
|
@@ -2767,16 +2676,10 @@ ADMIN_TEMPLATE = '''
|
|
| 2767 |
• Остаток по вариантам
|
| 2768 |
{% endif %}
|
| 2769 |
{% endif %}
|
| 2770 |
-
|
| 2771 |
-
{% if product.is_available == False %}
|
| 2772 |
-
<span style="color:var(--danger); font-weight:bold; margin-left:5px;">(Нет в наличии)</span>
|
| 2773 |
-
{% endif %}
|
| 2774 |
</span>
|
| 2775 |
</div>
|
| 2776 |
</div>
|
| 2777 |
-
<div class="product-actions"
|
| 2778 |
-
<form method="POST" style="margin:0;"><input type="hidden" name="action" value="move_product_up"><input type="hidden" name="product_id" value="{{ product.product_id }}"><button type="submit" class="btn btn-outline" style="padding:4px 8px; font-size:0.8rem;">▲</button></form>
|
| 2779 |
-
<form method="POST" style="margin:0; margin-right:10px;"><input type="hidden" name="action" value="move_product_down"><input type="hidden" name="product_id" value="{{ product.product_id }}"><button type="submit" class="btn btn-outline" style="padding:4px 8px; font-size:0.8rem;">▼</button></form>
|
| 2780 |
<button class="btn btn-warning" onclick="toggleEditProduct('edit-prod-{{ product.product_id }}')"><i class="fas fa-edit"></i></button>
|
| 2781 |
<form method="POST" style="margin:0;" onsubmit="return confirm('Удалить товар?');">
|
| 2782 |
<input type="hidden" name="action" value="delete_product">
|
|
@@ -2798,11 +2701,6 @@ ADMIN_TEMPLATE = '''
|
|
| 2798 |
<input type="text" name="name" value="{{ product.name }}" required autocomplete="off">
|
| 2799 |
</div>
|
| 2800 |
|
| 2801 |
-
<div class="form-group" style="flex:0;">
|
| 2802 |
-
<label> </label>
|
| 2803 |
-
<label style="display:flex; align-items:center; gap:5px; margin-top:10px; cursor:pointer;"><input type="checkbox" name="main_is_available" value="1" {% if product.is_available != False %}checked{% endif %}> В наличии</label>
|
| 2804 |
-
</div>
|
| 2805 |
-
|
| 2806 |
{% if settings.use_barcodes and sys_mode not in ['external', 'light_external'] %}
|
| 2807 |
<div class="form-group main-barcode-container" {% if product.variants %}style="display:none;"{% endif %}>
|
| 2808 |
<label>Штрих-код</label>
|
|
@@ -2846,22 +2744,6 @@ ADMIN_TEMPLATE = '''
|
|
| 2846 |
</div>
|
| 2847 |
{% endif %}
|
| 2848 |
</div>
|
| 2849 |
-
|
| 2850 |
-
{% if settings.business_type == 'wholesale' %}
|
| 2851 |
-
<div class="form-group main-vol-prices-container" {% if product.variants %}style="display:none;"{% else %}style="background: #f9f9f9; padding: 10px; border-radius: 8px; border: 1px dashed #ccc; margin-top: 10px;"{% endif %}>
|
| 2852 |
-
<label style="font-size:0.85rem; font-weight:bold; margin-bottom:5px; display:block;">Оптовые цены от количества (только без вариантов)</label>
|
| 2853 |
-
<div id="main_vol_prices_edit_{{ product.product_id }}">
|
| 2854 |
-
{% for vp in product.volume_prices %}
|
| 2855 |
-
<div class="vol-price-row">
|
| 2856 |
-
<input type="number" name="main_vol_qty[]" value="{{ vp.qty }}" placeholder="От шт">
|
| 2857 |
-
<input type="number" name="main_vol_price[]" value="{{ vp.price }}" placeholder="Цена" step="0.01">
|
| 2858 |
-
<button type="button" onclick="this.parentElement.remove()">X</button>
|
| 2859 |
-
</div>
|
| 2860 |
-
{% endfor %}
|
| 2861 |
-
</div>
|
| 2862 |
-
<button type="button" class="btn btn-outline" style="padding:5px 10px; font-size:0.8rem; margin-top:5px;" onclick="addVolPriceRow('main_vol_prices_edit_{{ product.product_id }}', 'main')"><i class="fas fa-plus"></i> Добавить вариант цены</button>
|
| 2863 |
-
</div>
|
| 2864 |
-
{% endif %}
|
| 2865 |
|
| 2866 |
<div class="variants-container" id="variants-container-edit-{{ product.product_id }}">
|
| 2867 |
<div style="display:flex; justify-content:space-between; align-items:center;">
|
|
@@ -2870,71 +2752,43 @@ ADMIN_TEMPLATE = '''
|
|
| 2870 |
</div>
|
| 2871 |
<div id="variants-list-edit-{{ product.product_id }}">
|
| 2872 |
{% for variant in product.variants %}
|
| 2873 |
-
{% set vid = uuid4().hex %}
|
| 2874 |
<div class="variant-row">
|
| 2875 |
-
<div class="
|
| 2876 |
-
<
|
| 2877 |
-
<
|
| 2878 |
</div>
|
| 2879 |
-
|
| 2880 |
-
<
|
| 2881 |
-
|
| 2882 |
-
|
| 2883 |
-
<div class="form-group" style="flex:2;">
|
| 2884 |
-
<label>Название</label>
|
| 2885 |
-
<input type="text" name="var_name_{{ vid }}" value="{{ variant.name }}" placeholder="Цвет, размер" required>
|
| 2886 |
-
</div>
|
| 2887 |
-
<div class="form-group" style="flex:0;">
|
| 2888 |
-
<label> </label>
|
| 2889 |
-
<label style="display:flex; align-items:center; gap:5px; margin-top:10px; cursor:pointer;"><input type="checkbox" name="var_is_available_{{ vid }}" value="1" {% if variant.is_available != False %}checked{% endif %}> В наличии</label>
|
| 2890 |
-
</div>
|
| 2891 |
-
{% if settings.business_type != 'retail' %}
|
| 2892 |
-
<div class="form-group">
|
| 2893 |
-
<label>В уп. (шт)</label>
|
| 2894 |
-
<input type="number" name="var_ppb_{{ vid }}" value="{{ variant.pieces_per_box }}" placeholder="Шт">
|
| 2895 |
-
</div>
|
| 2896 |
-
{% endif %}
|
| 2897 |
-
{% if settings.use_barcodes and sys_mode not in['external', 'light_external'] %}
|
| 2898 |
-
<div class="form-group">
|
| 2899 |
-
<label>Штрих-код</label>
|
| 2900 |
-
<div style="display:flex; gap:5px;">
|
| 2901 |
-
<input type="text" name="var_barcode_{{ vid }}" value="{{ variant.barcode }}" placeholder="Код">
|
| 2902 |
-
<button type="button" class="btn btn-outline" style="padding: 12px;" onclick="startScanner(val => this.previousElementSibling.value = val)"><i class="fas fa-barcode"></i></button>
|
| 2903 |
-
</div>
|
| 2904 |
-
</div>
|
| 2905 |
-
{% endif %}
|
| 2906 |
-
<div class="form-group var-price-input" {% if not product.has_variant_prices %}style="display:none;"{% endif %}>
|
| 2907 |
-
<label>Цена</label>
|
| 2908 |
-
<input type="number" name="var_price_{{ vid }}" value="{{ variant.price }}" placeholder="Цена за ед." step="0.01" {% if product.has_variant_prices %}required{% endif %}>
|
| 2909 |
-
</div>
|
| 2910 |
-
{% if settings.business_type == 'mixed' %}
|
| 2911 |
-
<div class="form-group var-price-input" {% if not product.has_variant_prices %}style="display:none;"{% endif %}>
|
| 2912 |
-
<label>Цена уп.</label>
|
| 2913 |
-
<input type="number" name="var_box_price_{{ vid }}" value="{{ variant.box_price }}" placeholder="Опционально" step="0.01">
|
| 2914 |
-
</div>
|
| 2915 |
-
{% endif %}
|
| 2916 |
-
{% if settings.track_inventory and sys_mode not in ['external', 'light_external'] %}
|
| 2917 |
-
<div class="form-group">
|
| 2918 |
-
<label>Остаток</label>
|
| 2919 |
-
<input type="number" name="var_stock_{{ vid }}" value="{{ variant.stock }}" placeholder="Остаток">
|
| 2920 |
-
</div>
|
| 2921 |
-
{% endif %}
|
| 2922 |
</div>
|
| 2923 |
-
{%
|
| 2924 |
-
|
| 2925 |
-
|
| 2926 |
-
<
|
| 2927 |
-
|
| 2928 |
-
<
|
| 2929 |
-
|
| 2930 |
-
<input type="number" name="var_vol_price_{{ vid }}[]" value="{{ vp.price }}" placeholder="Цена" step="0.01">
|
| 2931 |
-
<button type="button" onclick="this.parentElement.remove()">X</button>
|
| 2932 |
-
</div>
|
| 2933 |
-
{% endfor %}
|
| 2934 |
</div>
|
| 2935 |
-
<button type="button" class="btn btn-outline" style="padding:2px 8px; font-size:0.75rem; margin-top:5px;" onclick="addVolPriceRow('var_vol_prices_{{ vid }}', 'var_{{ vid }}')">+ Добавить цену</button>
|
| 2936 |
</div>
|
| 2937 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2938 |
</div>
|
| 2939 |
{% endfor %}
|
| 2940 |
</div>
|
|
@@ -3035,40 +2889,12 @@ ADMIN_TEMPLATE = '''
|
|
| 3035 |
document.getElementById(elId).style.display = 'none';
|
| 3036 |
}
|
| 3037 |
|
| 3038 |
-
function moveVariantUp(btn) {
|
| 3039 |
-
const row = btn.closest('.variant-row');
|
| 3040 |
-
if (row.previousElementSibling && row.previousElementSibling.classList.contains('variant-row')) {
|
| 3041 |
-
row.parentNode.insertBefore(row, row.previousElementSibling);
|
| 3042 |
-
}
|
| 3043 |
-
}
|
| 3044 |
-
|
| 3045 |
-
function moveVariantDown(btn) {
|
| 3046 |
-
const row = btn.closest('.variant-row');
|
| 3047 |
-
if (row.nextElementSibling && row.nextElementSibling.classList.contains('variant-row')) {
|
| 3048 |
-
row.parentNode.insertBefore(row.nextElementSibling, row);
|
| 3049 |
-
}
|
| 3050 |
-
}
|
| 3051 |
-
|
| 3052 |
-
function addVolPriceRow(containerId, prefix) {
|
| 3053 |
-
const container = document.getElementById(containerId);
|
| 3054 |
-
const div = document.createElement('div');
|
| 3055 |
-
div.className = 'vol-price-row';
|
| 3056 |
-
let nameQty = prefix === 'main' ? 'main_vol_qty[]' : `var_vol_qty_${prefix.replace('var_', '')}[]`;
|
| 3057 |
-
let namePrice = prefix === 'main' ? 'main_vol_price[]' : `var_vol_price_${prefix.replace('var_', '')}[]`;
|
| 3058 |
-
div.innerHTML = `<input type="number" name="${nameQty}" placeholder="От шт">
|
| 3059 |
-
<input type="number" name="${namePrice}" placeholder="Цена" step="0.01">
|
| 3060 |
-
<button type="button" onclick="this.parentElement.remove()">X</button>`;
|
| 3061 |
-
container.appendChild(div);
|
| 3062 |
-
}
|
| 3063 |
-
|
| 3064 |
function addVariantRow(containerId) {
|
| 3065 |
const container = document.getElementById(containerId);
|
| 3066 |
const formBlock = container.closest('form');
|
| 3067 |
const formId = formBlock.parentElement.id;
|
| 3068 |
const hasVariantPrices = formBlock.querySelector('input[name="has_variant_prices"]').checked;
|
| 3069 |
|
| 3070 |
-
const vid = Math.random().toString(36).substr(2, 9);
|
| 3071 |
-
|
| 3072 |
const div = document.createElement('div');
|
| 3073 |
div.className = 'variant-row';
|
| 3074 |
|
|
@@ -3076,28 +2902,17 @@ ADMIN_TEMPLATE = '''
|
|
| 3076 |
let reqAttr = hasVariantPrices ? 'required' : '';
|
| 3077 |
|
| 3078 |
let html = `
|
| 3079 |
-
<div class="
|
| 3080 |
-
<
|
| 3081 |
-
<
|
| 3082 |
</div>
|
| 3083 |
-
<button type="button" class="remove-variant-btn" onclick="this.parentElement.remove(); updateMainStockVisibility('${formId}')"><i class="fas fa-times-circle"></i></button>
|
| 3084 |
-
<input type="hidden" name="variant_id[]" value="${vid}">
|
| 3085 |
-
<div style="display:flex; width:100%; gap:15px; flex-wrap:wrap; margin-top:15px;">
|
| 3086 |
-
<div class="form-group" style="flex:2;">
|
| 3087 |
-
<label>Название</label>
|
| 3088 |
-
<input type="text" name="var_name_${vid}" placeholder="Цвет, размер" required>
|
| 3089 |
-
</div>
|
| 3090 |
-
<div class="form-group" style="flex:0;">
|
| 3091 |
-
<label> </label>
|
| 3092 |
-
<label style="display:flex; align-items:center; gap:5px; margin-top:10px; cursor:pointer;"><input type="checkbox" name="var_is_available_${vid}" value="1" checked> В наличии</label>
|
| 3093 |
-
</div>
|
| 3094 |
`;
|
| 3095 |
|
| 3096 |
if (businessType !== 'retail') {
|
| 3097 |
html += `
|
| 3098 |
<div class="form-group">
|
| 3099 |
<label>В уп. (шт)</label>
|
| 3100 |
-
<input type="number" name="
|
| 3101 |
</div>`;
|
| 3102 |
}
|
| 3103 |
|
|
@@ -3106,7 +2921,7 @@ ADMIN_TEMPLATE = '''
|
|
| 3106 |
<div class="form-group">
|
| 3107 |
<label>Штрих-код</label>
|
| 3108 |
<div style="display:flex; gap:5px;">
|
| 3109 |
-
<input type="text" name="
|
| 3110 |
<button type="button" class="btn btn-outline" style="padding: 12px;" onclick="startScanner(val => this.previousElementSibling.value = val)"><i class="fas fa-barcode"></i></button>
|
| 3111 |
</div>
|
| 3112 |
</div>`;
|
|
@@ -3115,7 +2930,7 @@ ADMIN_TEMPLATE = '''
|
|
| 3115 |
html += `
|
| 3116 |
<div class="form-group var-price-input" ${displayStyle}>
|
| 3117 |
<label>Цена</label>
|
| 3118 |
-
<input type="number" name="
|
| 3119 |
</div>
|
| 3120 |
`;
|
| 3121 |
|
|
@@ -3123,7 +2938,7 @@ ADMIN_TEMPLATE = '''
|
|
| 3123 |
html += `
|
| 3124 |
<div class="form-group var-price-input" ${displayStyle}>
|
| 3125 |
<label>Цена уп.</label>
|
| 3126 |
-
<input type="number" name="
|
| 3127 |
</div>`;
|
| 3128 |
}
|
| 3129 |
|
|
@@ -3131,21 +2946,11 @@ ADMIN_TEMPLATE = '''
|
|
| 3131 |
html += `
|
| 3132 |
<div class="form-group">
|
| 3133 |
<label>Остаток</label>
|
| 3134 |
-
<input type="number" name="
|
| 3135 |
</div>`;
|
| 3136 |
}
|
| 3137 |
|
| 3138 |
-
html += `</
|
| 3139 |
-
|
| 3140 |
-
if (businessType === 'wholesale') {
|
| 3141 |
-
html += `
|
| 3142 |
-
<div style="width:100%; border-top:1px dashed #ccc; margin-top:10px; padding-top:10px;">
|
| 3143 |
-
<label style="font-size:0.8rem; font-weight:bold;">Оптовые цены от количества:</label>
|
| 3144 |
-
<div id="var_vol_prices_${vid}"></div>
|
| 3145 |
-
<button type="button" class="btn btn-outline" style="padding:2px 8px; font-size:0.75rem; margin-top:5px;" onclick="addVolPriceRow('var_vol_prices_${vid}', 'var_${vid}')">+ Добавить цену</button>
|
| 3146 |
-
</div>
|
| 3147 |
-
`;
|
| 3148 |
-
}
|
| 3149 |
|
| 3150 |
div.innerHTML = html;
|
| 3151 |
container.appendChild(div);
|
|
@@ -3166,8 +2971,8 @@ ADMIN_TEMPLATE = '''
|
|
| 3166 |
|
| 3167 |
varPriceInputs.forEach(input => {
|
| 3168 |
input.style.display = 'flex';
|
| 3169 |
-
const inp = input.querySelector('input[
|
| 3170 |
-
if(inp
|
| 3171 |
});
|
| 3172 |
} else {
|
| 3173 |
if(mainPriceContainer) mainPriceContainer.style.display = 'flex';
|
|
@@ -3176,8 +2981,8 @@ ADMIN_TEMPLATE = '''
|
|
| 3176 |
|
| 3177 |
varPriceInputs.forEach(input => {
|
| 3178 |
input.style.display = 'none';
|
| 3179 |
-
const inp = input.querySelector('input[
|
| 3180 |
-
if(inp
|
| 3181 |
});
|
| 3182 |
}
|
| 3183 |
}
|
|
@@ -3201,14 +3006,6 @@ ADMIN_TEMPLATE = '''
|
|
| 3201 |
else mainBc.style.display = 'flex';
|
| 3202 |
}
|
| 3203 |
}
|
| 3204 |
-
|
| 3205 |
-
if(businessType === 'wholesale') {
|
| 3206 |
-
const mainVol = form.querySelector('.main-vol-prices-container');
|
| 3207 |
-
if(mainVol) {
|
| 3208 |
-
if(variants.length > 0) mainVol.style.display = 'none';
|
| 3209 |
-
else mainVol.style.display = 'block';
|
| 3210 |
-
}
|
| 3211 |
-
}
|
| 3212 |
}
|
| 3213 |
|
| 3214 |
function filterAdmin() {
|
|
@@ -3634,8 +3431,8 @@ REPORTS_TEMPLATE = '''
|
|
| 3634 |
<tr>
|
| 3635 |
<td style="font-weight:500;">${d}</td>
|
| 3636 |
<td>${data[d].orders}</td>
|
| 3637 |
-
|
| 3638 |
-
</tr>
|
| 3639 |
`;
|
| 3640 |
});
|
| 3641 |
}
|
|
@@ -4208,7 +4005,6 @@ def create_order(env_id):
|
|
| 4208 |
"variant_name": item.get('variant_name', ''),
|
| 4209 |
"variant_idx": item.get('variant_idx', -1),
|
| 4210 |
"discount": float(item.get('discount', 0)),
|
| 4211 |
-
"volume_prices": item.get('volume_prices', []),
|
| 4212 |
"category": product_dict.get(item.get('product_id'), 'Без категории'),
|
| 4213 |
"photo_url": f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{item['photos'][0]}" if item.get('photos') else "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjBmMHcwIi8+PC9zdmc+"
|
| 4214 |
})
|
|
@@ -4588,24 +4384,6 @@ def admin(env_id):
|
|
| 4588 |
data['settings'] = settings
|
| 4589 |
save_env_data(env_id, data)
|
| 4590 |
|
| 4591 |
-
elif action == 'move_category_up':
|
| 4592 |
-
cat_name = request.form.get('category_name')
|
| 4593 |
-
if cat_name in categories:
|
| 4594 |
-
idx = categories.index(cat_name)
|
| 4595 |
-
if idx > 0:
|
| 4596 |
-
categories[idx], categories[idx-1] = categories[idx-1], categories[idx]
|
| 4597 |
-
data['categories'] = categories
|
| 4598 |
-
save_env_data(env_id, data)
|
| 4599 |
-
|
| 4600 |
-
elif action == 'move_category_down':
|
| 4601 |
-
cat_name = request.form.get('category_name')
|
| 4602 |
-
if cat_name in categories:
|
| 4603 |
-
idx = categories.index(cat_name)
|
| 4604 |
-
if idx < len(categories) - 1:
|
| 4605 |
-
categories[idx], categories[idx+1] = categories[idx+1], categories[idx]
|
| 4606 |
-
data['categories'] = categories
|
| 4607 |
-
save_env_data(env_id, data)
|
| 4608 |
-
|
| 4609 |
elif action == 'add_category':
|
| 4610 |
cat_name = request.form.get('category_name', '').strip()
|
| 4611 |
if cat_name and cat_name not in categories:
|
|
@@ -4655,30 +4433,6 @@ def admin(env_id):
|
|
| 4655 |
del data['category_photos'][cat_name]
|
| 4656 |
save_env_data(env_id, data)
|
| 4657 |
|
| 4658 |
-
elif action == 'move_product_up':
|
| 4659 |
-
pid = request.form.get('product_id')
|
| 4660 |
-
for i, p in enumerate(products):
|
| 4661 |
-
if p.get('product_id') == pid:
|
| 4662 |
-
for j in range(i-1, -1, -1):
|
| 4663 |
-
if products[j].get('category') == p.get('category'):
|
| 4664 |
-
products[i], products[j] = products[j], products[i]
|
| 4665 |
-
break
|
| 4666 |
-
break
|
| 4667 |
-
data['products'] = products
|
| 4668 |
-
save_env_data(env_id, data)
|
| 4669 |
-
|
| 4670 |
-
elif action == 'move_product_down':
|
| 4671 |
-
pid = request.form.get('product_id')
|
| 4672 |
-
for i, p in enumerate(products):
|
| 4673 |
-
if p.get('product_id') == pid:
|
| 4674 |
-
for j in range(i+1, len(products)):
|
| 4675 |
-
if products[j].get('category') == p.get('category'):
|
| 4676 |
-
products[i], products[j] = products[j], products[i]
|
| 4677 |
-
break
|
| 4678 |
-
break
|
| 4679 |
-
data['products'] = products
|
| 4680 |
-
save_env_data(env_id, data)
|
| 4681 |
-
|
| 4682 |
elif action == 'add_product':
|
| 4683 |
name = request.form.get('name', '').strip()
|
| 4684 |
barcode = request.form.get('barcode', '').strip()
|
|
@@ -4697,59 +4451,48 @@ def admin(env_id):
|
|
| 4697 |
stock_str = request.form.get('stock', '')
|
| 4698 |
main_stock = int(stock_str) if stock_str else ""
|
| 4699 |
|
| 4700 |
-
main_is_available = request.form.get('main_is_available') == '1'
|
| 4701 |
-
|
| 4702 |
description = request.form.get('description', '').strip()
|
| 4703 |
category = request.form.get('category')
|
| 4704 |
has_variant_prices = 'has_variant_prices' in request.form
|
| 4705 |
|
| 4706 |
-
|
| 4707 |
-
|
| 4708 |
-
|
| 4709 |
-
|
| 4710 |
-
|
| 4711 |
-
|
|
|
|
| 4712 |
|
| 4713 |
-
|
| 4714 |
-
|
| 4715 |
-
for vid in variant_ids:
|
| 4716 |
-
v_name = request.form.get(f'var_name_{vid}', '').strip()
|
| 4717 |
if v_name:
|
| 4718 |
-
v_is_avail = request.form.get(f'var_is_available_{vid}') == '1'
|
| 4719 |
v_price = price
|
| 4720 |
v_box_price = box_price
|
| 4721 |
if has_variant_prices:
|
| 4722 |
-
|
| 4723 |
-
|
| 4724 |
-
|
| 4725 |
-
|
| 4726 |
-
|
| 4727 |
v_stock = ""
|
| 4728 |
-
|
| 4729 |
-
|
| 4730 |
|
| 4731 |
-
v_barcode =
|
|
|
|
|
|
|
| 4732 |
|
| 4733 |
v_ppb = pieces_per_box
|
| 4734 |
-
|
| 4735 |
-
|
| 4736 |
-
|
| 4737 |
-
v_vol_qtys = request.form.getlist(f'var_vol_qty_{vid}[]')
|
| 4738 |
-
v_vol_prices = request.form.getlist(f'var_vol_price_{vid}[]')
|
| 4739 |
-
v_volume_prices = []
|
| 4740 |
-
for vq, vp in zip(v_vol_qtys, v_vol_prices):
|
| 4741 |
-
if vq.strip() and vp.strip():
|
| 4742 |
-
v_volume_prices.append({"qty": int(vq), "price": float(vp)})
|
| 4743 |
-
|
| 4744 |
variants.append({
|
| 4745 |
"name": v_name,
|
| 4746 |
"barcode": v_barcode,
|
| 4747 |
"price": v_price,
|
| 4748 |
"box_price": v_box_price,
|
| 4749 |
"stock": v_stock,
|
| 4750 |
-
"pieces_per_box": v_ppb
|
| 4751 |
-
"is_available": v_is_avail,
|
| 4752 |
-
"volume_prices": v_volume_prices
|
| 4753 |
})
|
| 4754 |
|
| 4755 |
uploaded_photos = request.files.getlist('photos')[:10]
|
|
@@ -4791,8 +4534,6 @@ def admin(env_id):
|
|
| 4791 |
'box_price': box_price,
|
| 4792 |
'min_order': min_order,
|
| 4793 |
'stock': main_stock,
|
| 4794 |
-
'is_available': main_is_available,
|
| 4795 |
-
'volume_prices': main_volume_prices,
|
| 4796 |
'description': description,
|
| 4797 |
'category': category,
|
| 4798 |
'photos': photos_list,
|
|
@@ -4823,60 +4564,45 @@ def admin(env_id):
|
|
| 4823 |
stock_str = request.form.get('stock', '')
|
| 4824 |
main_stock = int(stock_str) if stock_str else ""
|
| 4825 |
|
| 4826 |
-
main_is_available = request.form.get('main_is_available') == '1'
|
| 4827 |
-
|
| 4828 |
description = request.form.get('description', '').strip()
|
| 4829 |
has_variant_prices = 'has_variant_prices' in request.form
|
| 4830 |
|
| 4831 |
-
main_vol_qtys = request.form.getlist('main_vol_qty[]')
|
| 4832 |
-
main_vol_prices = request.form.getlist('main_vol_price[]')
|
| 4833 |
-
main_volume_prices = []
|
| 4834 |
-
for mq, mp in zip(main_vol_qtys, main_vol_prices):
|
| 4835 |
-
if mq.strip() and mp.strip():
|
| 4836 |
-
main_volume_prices.append({"qty": int(mq), "price": float(mp)})
|
| 4837 |
-
|
| 4838 |
remove_photos = request.form.getlist('remove_photos[]')
|
| 4839 |
|
| 4840 |
-
|
| 4841 |
-
|
| 4842 |
-
|
| 4843 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4844 |
if v_name:
|
| 4845 |
-
v_is_avail = request.form.get(f'var_is_available_{vid}') == '1'
|
| 4846 |
v_price = price
|
| 4847 |
v_box_price = box_price
|
| 4848 |
if has_variant_prices:
|
| 4849 |
-
|
| 4850 |
-
|
| 4851 |
-
|
| 4852 |
-
|
| 4853 |
-
|
| 4854 |
v_stock = ""
|
| 4855 |
-
|
| 4856 |
-
|
| 4857 |
-
|
| 4858 |
-
|
| 4859 |
-
|
| 4860 |
v_ppb = pieces_per_box
|
| 4861 |
-
|
| 4862 |
-
|
| 4863 |
-
|
| 4864 |
-
v_vol_qtys = request.form.getlist(f'var_vol_qty_{vid}[]')
|
| 4865 |
-
v_vol_prices = request.form.getlist(f'var_vol_price_{vid}[]')
|
| 4866 |
-
v_volume_prices = []
|
| 4867 |
-
for vq, vp in zip(v_vol_qtys, v_vol_prices):
|
| 4868 |
-
if vq.strip() and vp.strip():
|
| 4869 |
-
v_volume_prices.append({"qty": int(vq), "price": float(vp)})
|
| 4870 |
-
|
| 4871 |
variants.append({
|
| 4872 |
"name": v_name,
|
| 4873 |
"barcode": v_barcode,
|
| 4874 |
"price": v_price,
|
| 4875 |
"box_price": v_box_price,
|
| 4876 |
"stock": v_stock,
|
| 4877 |
-
"pieces_per_box": v_ppb
|
| 4878 |
-
"is_available": v_is_avail,
|
| 4879 |
-
"volume_prices": v_volume_prices
|
| 4880 |
})
|
| 4881 |
|
| 4882 |
uploaded_photos = request.files.getlist('photos')[:10]
|
|
@@ -4918,8 +4644,6 @@ def admin(env_id):
|
|
| 4918 |
p['box_price'] = box_price
|
| 4919 |
p['min_order'] = min_order
|
| 4920 |
p['stock'] = main_stock
|
| 4921 |
-
p['is_available'] = main_is_available
|
| 4922 |
-
p['volume_prices'] = main_volume_prices
|
| 4923 |
p['description'] = description
|
| 4924 |
p['variants'] = variants
|
| 4925 |
p['has_variant_prices'] = has_variant_prices
|
|
|
|
| 235 |
if 'variants' not in product: product['variants'] =[]; changed = True
|
| 236 |
if 'has_variant_prices' not in product: product['has_variant_prices'] = False; changed = True
|
| 237 |
if 'stock' not in product: product['stock'] = ""; changed = True
|
|
|
|
|
|
|
| 238 |
for v in product['variants']:
|
| 239 |
if 'stock' not in v: v['stock'] = ""; changed = True
|
| 240 |
if 'box_price' not in v: v['box_price'] = ""; changed = True
|
| 241 |
if 'barcode' not in v: v['barcode'] = ""; changed = True
|
| 242 |
if 'pieces_per_box' not in v: v['pieces_per_box'] = product.get('pieces_per_box', ""); changed = True
|
|
|
|
|
|
|
| 243 |
|
| 244 |
for order_id, order in env_data['orders'].items():
|
| 245 |
if 'status' not in order: order['status'] = 'confirmed'; changed = True
|
|
|
|
| 249 |
for item in order.get('cart', []):
|
| 250 |
if 'discount' not in item: item['discount'] = 0; changed = True
|
| 251 |
if 'category' not in item: item['category'] = 'Без категории'; changed = True
|
|
|
|
| 252 |
|
| 253 |
if changed or not os.path.exists(DATA_FILE):
|
| 254 |
try:
|
|
|
|
| 313 |
all_data[env_id] = env_data
|
| 314 |
save_data(all_data)
|
| 315 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
def update_order_totals(order, business_type):
|
| 317 |
total = 0
|
| 318 |
global_discount = float(order.get('global_discount', 0))
|
|
|
|
| 324 |
c_price = float(i.get('price', 0))
|
| 325 |
c_box_price = float(i.get('cart_box_price', 0))
|
| 326 |
item_discount = float(i.get('discount', 0))
|
|
|
|
| 327 |
|
| 328 |
+
if business_type in ['mixed', 'wholesale'] and c_box_price > 0 and ppb > 1 and qty >= ppb:
|
| 329 |
+
base_price = c_box_price / ppb
|
| 330 |
+
else:
|
| 331 |
+
base_price = c_price
|
|
|
|
| 332 |
|
| 333 |
+
discounted_price = max(0, base_price - item_discount)
|
| 334 |
item_total = discounted_price * qty
|
| 335 |
i['calculated_price'] = round(discounted_price, 2)
|
| 336 |
total += item_total
|
|
|
|
| 677 |
.process-return-btn { background: #e17055; color: white; border: none; padding: 8px 15px; border-radius: 8px; cursor: pointer; margin-top: 10px; width: 100%; font-weight: 600; }
|
| 678 |
|
| 679 |
.history-btn { background: #0984e3; color: white; border: none; padding: 8px 15px; border-radius: 8px; cursor: pointer; margin-top: 10px; width: 100%; font-weight: 600; text-decoration: none; display: block; text-align: center; box-sizing: border-box; }
|
|
|
|
| 680 |
|
| 681 |
@media (min-width: 768px) {
|
| 682 |
.categories-container { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
|
|
|
|
| 971 |
if (p.variants && p.variants.length > 0) {
|
| 972 |
variantsHtml = `<div class="variants-list">`;
|
| 973 |
p.variants.forEach((v, idx) => {
|
|
|
|
| 974 |
let vPrice = p.has_variant_prices ? v.price : p.price;
|
| 975 |
let vBoxPrice = p.has_variant_prices ? (v.box_price || '') : (p.box_price || '');
|
| 976 |
let vStockHtml = showStock && v.stock !== "" && v.stock !== null ? `<div class="variant-stock">Остаток: ${v.stock} шт</div>` : '';
|
|
|
|
| 983 |
priceText += `<br><span style="font-size:0.8rem; color:#636e72;">Упаковка: ${vBoxPrice} ${currency}</span>`;
|
| 984 |
}
|
| 985 |
|
| 986 |
+
let addBoxBtnVariant = '';
|
| 987 |
+
if (businessType !== 'retail' && vPpb > 1) {
|
| 988 |
+
addBoxBtnVariant = `<button class="box-btn" style="height:32px; margin-right:5px;" onclick="updateCart('${p.product_id}', ${vPpb}, null, false, '${cKey}', ${moq})">+ Упаковка</button>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 989 |
}
|
| 990 |
|
| 991 |
variantsHtml += `
|
| 992 |
+
<div class="variant-item">
|
| 993 |
<div class="variant-info">
|
| 994 |
<span class="variant-name">${v.name}</span>
|
| 995 |
<span class="variant-price">${priceText}</span>
|
|
|
|
| 996 |
${vStockHtml}
|
| 997 |
</div>
|
| 998 |
<div style="display:flex; align-items:center;">
|
| 999 |
+
${addBoxBtnVariant}
|
| 1000 |
+
<div class="quantity-control" style="border:none; background:var(--surface);">
|
| 1001 |
+
<button onclick="updateCart('${p.product_id}', -1, null, false, '${cKey}', ${moq})"><i class="fas fa-minus" style="font-size:0.8rem;"></i></button>
|
| 1002 |
+
<input type="number" id="qty-${cKey}" value="${qty}" onchange="manualUpdateCart('${cKey}', this.value, ${moq})">
|
| 1003 |
+
<button onclick="updateCart('${p.product_id}', 1, null, false, '${cKey}', ${moq})"><i class="fas fa-plus" style="font-size:0.8rem;"></i></button>
|
| 1004 |
+
</div>
|
| 1005 |
</div>
|
| 1006 |
</div>
|
| 1007 |
`;
|
|
|
|
| 1011 |
let mStockHtml = showStock && p.stock !== "" && p.stock !== null ? `<div style="font-size:0.8rem; color:#0984e3; margin-top:4px;">Остаток: ${p.stock} шт</div>` : '';
|
| 1012 |
let qty = cart[p.product_id] ? cart[p.product_id].quantity : 0;
|
| 1013 |
|
| 1014 |
+
let addBoxBtn = '';
|
| 1015 |
+
if (businessType !== 'retail' && ppb > 1) {
|
| 1016 |
+
addBoxBtn = `<button class="box-btn" onclick="updateCart('${p.product_id}', ${ppb}, null, false, null, ${moq})">+ Упаковка</button>`;
|
| 1017 |
+
}
|
| 1018 |
+
|
| 1019 |
let priceText = `${p.price} ${currency}`;
|
| 1020 |
if (businessType === 'mixed' && p.box_price && ppb > 1) {
|
| 1021 |
priceText += `<br><span style="font-size:0.8rem; color:#636e72;">Упаковка: ${p.box_price} ${currency}</span>`;
|
| 1022 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1023 |
|
| 1024 |
+
mainControlsHtml = `
|
| 1025 |
+
<div class="product-bottom">
|
| 1026 |
+
<div style="display:flex; flex-direction:column;">
|
| 1027 |
+
<div class="product-price">${priceText}</div>
|
| 1028 |
+
${mStockHtml}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1029 |
</div>
|
| 1030 |
+
<div class="controls-wrapper">
|
| 1031 |
+
${addBoxBtn}
|
| 1032 |
+
<div class="quantity-control">
|
| 1033 |
+
<button onclick="updateCart('${p.product_id}', -1, null, false, null, ${moq})"><i class="fas fa-minus" style="font-size:0.8rem;"></i></button>
|
| 1034 |
+
<input type="number" id="qty-${p.product_id}" value="${qty}" onchange="manualUpdateCart('${p.product_id}', this.value, ${moq})">
|
| 1035 |
+
<button onclick="updateCart('${p.product_id}', 1, null, false, null, ${moq})"><i class="fas fa-plus" style="font-size:0.8rem;"></i></button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1036 |
</div>
|
| 1037 |
</div>
|
| 1038 |
+
</div>
|
| 1039 |
+
`;
|
| 1040 |
}
|
| 1041 |
|
|
|
|
|
|
|
| 1042 |
const div = document.createElement('div');
|
| 1043 |
+
div.className = 'product-card';
|
| 1044 |
div.innerHTML = `
|
| 1045 |
<div class="product-main-content">
|
| 1046 |
<div class="product-img-wrapper" ${imgClick}>
|
|
|
|
| 1087 |
if (cKey.includes('___')) {
|
| 1088 |
varIdx = parseInt(cKey.split('___')[1]);
|
| 1089 |
}
|
|
|
|
|
|
|
|
|
|
| 1090 |
|
| 1091 |
let pStock = "";
|
| 1092 |
let pPpb = parseInt(p.pieces_per_box) || 1;
|
|
|
|
| 1104 |
let price = p.price;
|
| 1105 |
let bPrice = p.box_price || 0;
|
| 1106 |
let vName = "";
|
|
|
|
| 1107 |
if (varIdx !== -1 && p.variants[varIdx]) {
|
| 1108 |
if (p.has_variant_prices) {
|
| 1109 |
price = p.variants[varIdx].price;
|
| 1110 |
bPrice = p.variants[varIdx].box_price || 0;
|
| 1111 |
}
|
| 1112 |
vName = p.variants[varIdx].name;
|
|
|
|
| 1113 |
}
|
| 1114 |
+
cart[cKey] = { ...p, quantity: 0, cart_price: price, cart_box_price: bPrice, pieces_per_box: pPpb, variant_name: vName, variant_idx: varIdx, discount: 0 };
|
| 1115 |
}
|
| 1116 |
|
| 1117 |
let currentQty = cart[cKey].quantity;
|
|
|
|
| 1168 |
const pId = cKey.split('___')[0];
|
| 1169 |
updateCart(pId, 0, num, true, cKey, moq);
|
| 1170 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1171 |
|
| 1172 |
function calculateItemPrice(item) {
|
| 1173 |
let ppb = parseInt(item.pieces_per_box) || 1;
|
|
|
|
| 1175 |
let cBoxPrice = parseFloat(item.cart_box_price) || 0;
|
| 1176 |
let cPrice = parseFloat(item.cart_price) || 0;
|
| 1177 |
let disc = parseFloat(item.discount) || 0;
|
|
|
|
| 1178 |
|
| 1179 |
let unit = cPrice;
|
| 1180 |
+
if ((businessType === 'mixed' || businessType === 'wholesale') && cBoxPrice > 0 && ppb > 1 && qty >= ppb) {
|
|
|
|
|
|
|
| 1181 |
unit = cBoxPrice / ppb;
|
| 1182 |
}
|
| 1183 |
return Math.max(0, unit - disc) * qty;
|
|
|
|
| 2205 |
|
| 2206 |
.form-group { display: flex; flex-direction: column; gap: 5px; flex: 1; min-width: 150px; }
|
| 2207 |
.form-group label { font-size: 0.85rem; font-weight: 600; color: #636e72; }
|
| 2208 |
+
.form-row { display: flex; gap: 15px; flex-wrap: wrap; }
|
| 2209 |
|
| 2210 |
.file-input-wrapper { position: relative; width: 100%; }
|
| 2211 |
input[type="file"] { width: 100%; padding: 10px; border: 1px dashed #ccc; border-radius: 10px; background: #fafafa; font-size: 0.9rem; }
|
|
|
|
| 2219 |
.social-item label { display: flex; align-items: center; gap: 5px; width: 150px; cursor: pointer; }
|
| 2220 |
|
| 2221 |
.variants-container { background: #f4f6f9; padding: 15px; border-radius: 10px; border: 1px dashed var(--border); display: flex; flex-direction: column; gap: 10px; }
|
| 2222 |
+
.variant-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; background: #fff; padding: 10px; border-radius: 8px; border: 1px solid var(--border); }
|
| 2223 |
.variant-row .form-group { flex: 1 1 30%; min-width: 120px; }
|
| 2224 |
+
.remove-variant-btn { color: var(--danger); background: none; border: none; font-size: 1.2rem; cursor: pointer; padding: 12px 5px; flex: 0 0 auto; }
|
|
|
|
|
|
|
| 2225 |
|
| 2226 |
.order-item { background: #fff; padding: 15px; border-radius: 10px; border: 1px solid var(--border); margin-bottom: 10px; }
|
| 2227 |
.order-header { display: flex; justify-content: space-between; font-weight: bold; margin-bottom: 10px; }
|
|
|
|
| 2230 |
.staff-item { display: flex; flex-direction: column; gap: 10px; background: #fff; padding: 15px; border-radius: 10px; border: 1px solid var(--border); margin-bottom: 10px; }
|
| 2231 |
|
| 2232 |
.badge { background: var(--danger); color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.8rem; margin-left: 5px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2233 |
|
| 2234 |
@media (max-width: 600px) {
|
| 2235 |
.header-panel { flex-direction: column; align-items: stretch; text-align: center; }
|
| 2236 |
.product-item { flex-direction: column; align-items: stretch; }
|
| 2237 |
.product-info { width: 100%; }
|
| 2238 |
.product-actions { align-self: flex-end; }
|
| 2239 |
+
.form-row { flex-direction: column; gap: 10px; }
|
| 2240 |
+
.variant-row { flex-direction: column; align-items: stretch; }
|
| 2241 |
+
.remove-variant-btn { width: 100%; text-align: right; padding: 5px; }
|
| 2242 |
}
|
| 2243 |
</style>
|
| 2244 |
</head>
|
|
|
|
| 2527 |
<i class="fas fa-chevron-down" id="icon-cat-{{ loop.index }}" style="color: #636e72;"></i>
|
| 2528 |
<span class="cat-title-text"><i class="fas fa-folder-open" style="color:var(--info); margin-right:5px;"></i> {{ category }}</span>
|
| 2529 |
</div>
|
| 2530 |
+
<form method="POST" style="margin:0;" onclick="event.stopPropagation();" onsubmit="return confirm('Удалить категорию и все ее товары?');">
|
| 2531 |
+
<input type="hidden" name="action" value="delete_category">
|
| 2532 |
+
<input type="hidden" name="category_name" value="{{ category }}">
|
| 2533 |
+
<button type="submit" class="btn btn-danger"><i class="fas fa-trash-alt"></i></button>
|
| 2534 |
+
</form>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2535 |
</div>
|
| 2536 |
<div class="category-content" id="cat-{{ loop.index }}">
|
| 2537 |
|
|
|
|
| 2572 |
<input type="text" name="name" placeholder="Введите название" required autocomplete="off">
|
| 2573 |
</div>
|
| 2574 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2575 |
{% if settings.use_barcodes and sys_mode not in ['external', 'light_external'] %}
|
| 2576 |
<div class="form-group main-barcode-container">
|
| 2577 |
<label>Штрих-код</label>
|
|
|
|
| 2616 |
{% endif %}
|
| 2617 |
</div>
|
| 2618 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2619 |
<div class="variants-container" id="variants-container-add-{{ loop.index }}">
|
| 2620 |
<div style="display:flex; justify-content:space-between; align-items:center;">
|
| 2621 |
<label style="font-weight:600; font-size:0.9rem;"><i class="fas fa-tags"></i> Варианты товара</label>
|
|
|
|
| 2640 |
|
| 2641 |
{% for product in products %}
|
| 2642 |
{% if product.category == category %}
|
| 2643 |
+
<div class="product-item" data-pid="{{ product.product_id }}">
|
| 2644 |
<div class="product-info">
|
| 2645 |
{% if product.photos and product.photos|length > 0 %}
|
| 2646 |
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product.photos[0] }}" class="product-img">
|
|
|
|
| 2676 |
• Остаток по вариантам
|
| 2677 |
{% endif %}
|
| 2678 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2679 |
</span>
|
| 2680 |
</div>
|
| 2681 |
</div>
|
| 2682 |
+
<div class="product-actions">
|
|
|
|
|
|
|
| 2683 |
<button class="btn btn-warning" onclick="toggleEditProduct('edit-prod-{{ product.product_id }}')"><i class="fas fa-edit"></i></button>
|
| 2684 |
<form method="POST" style="margin:0;" onsubmit="return confirm('Удалить товар?');">
|
| 2685 |
<input type="hidden" name="action" value="delete_product">
|
|
|
|
| 2701 |
<input type="text" name="name" value="{{ product.name }}" required autocomplete="off">
|
| 2702 |
</div>
|
| 2703 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2704 |
{% if settings.use_barcodes and sys_mode not in ['external', 'light_external'] %}
|
| 2705 |
<div class="form-group main-barcode-container" {% if product.variants %}style="display:none;"{% endif %}>
|
| 2706 |
<label>Штрих-код</label>
|
|
|
|
| 2744 |
</div>
|
| 2745 |
{% endif %}
|
| 2746 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2747 |
|
| 2748 |
<div class="variants-container" id="variants-container-edit-{{ product.product_id }}">
|
| 2749 |
<div style="display:flex; justify-content:space-between; align-items:center;">
|
|
|
|
| 2752 |
</div>
|
| 2753 |
<div id="variants-list-edit-{{ product.product_id }}">
|
| 2754 |
{% for variant in product.variants %}
|
|
|
|
| 2755 |
<div class="variant-row">
|
| 2756 |
+
<div class="form-group">
|
| 2757 |
+
<label>Название</label>
|
| 2758 |
+
<input type="text" name="variant_name[]" value="{{ variant.name }}" placeholder="Цвет, размер" required>
|
| 2759 |
</div>
|
| 2760 |
+
{% if settings.business_type != 'retail' %}
|
| 2761 |
+
<div class="form-group">
|
| 2762 |
+
<label>В уп. (шт)</label>
|
| 2763 |
+
<input type="number" name="variant_pieces_per_box[]" value="{{ variant.pieces_per_box }}" placeholder="Шт">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2764 |
</div>
|
| 2765 |
+
{% endif %}
|
| 2766 |
+
{% if settings.use_barcodes and sys_mode not in['external', 'light_external'] %}
|
| 2767 |
+
<div class="form-group">
|
| 2768 |
+
<label>Штрих-код</label>
|
| 2769 |
+
<div style="display:flex; gap:5px;">
|
| 2770 |
+
<input type="text" name="variant_barcode[]" value="{{ variant.barcode }}" placeholder="Код">
|
| 2771 |
+
<button type="button" class="btn btn-outline" style="padding: 12px;" onclick="startScanner(val => this.previousElementSibling.value = val)"><i class="fas fa-barcode"></i></button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2772 |
</div>
|
|
|
|
| 2773 |
</div>
|
| 2774 |
{% endif %}
|
| 2775 |
+
<div class="form-group var-price-input" {% if not product.has_variant_prices %}style="display:none;"{% endif %}>
|
| 2776 |
+
<label>Цена</label>
|
| 2777 |
+
<input type="number" name="variant_price[]" value="{{ variant.price }}" placeholder="Цена за ед." step="0.01" {% if product.has_variant_prices %}required{% endif %}>
|
| 2778 |
+
</div>
|
| 2779 |
+
{% if settings.business_type == 'mixed' %}
|
| 2780 |
+
<div class="form-group var-price-input" {% if not product.has_variant_prices %}style="display:none;"{% endif %}>
|
| 2781 |
+
<label>Цена уп.</label>
|
| 2782 |
+
<input type="number" name="variant_box_price[]" value="{{ variant.box_price }}" placeholder="Опционально" step="0.01">
|
| 2783 |
+
</div>
|
| 2784 |
+
{% endif %}
|
| 2785 |
+
{% if settings.track_inventory and sys_mode not in ['external', 'light_external'] %}
|
| 2786 |
+
<div class="form-group">
|
| 2787 |
+
<label>Остаток</label>
|
| 2788 |
+
<input type="number" name="variant_stock[]" value="{{ variant.stock }}" placeholder="Остаток">
|
| 2789 |
+
</div>
|
| 2790 |
+
{% endif %}
|
| 2791 |
+
<button type="button" class="remove-variant-btn" onclick="this.parentElement.remove(); updateMainStockVisibility('edit-prod-{{ product.product_id }}')"><i class="fas fa-times-circle"></i></button>
|
| 2792 |
</div>
|
| 2793 |
{% endfor %}
|
| 2794 |
</div>
|
|
|
|
| 2889 |
document.getElementById(elId).style.display = 'none';
|
| 2890 |
}
|
| 2891 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2892 |
function addVariantRow(containerId) {
|
| 2893 |
const container = document.getElementById(containerId);
|
| 2894 |
const formBlock = container.closest('form');
|
| 2895 |
const formId = formBlock.parentElement.id;
|
| 2896 |
const hasVariantPrices = formBlock.querySelector('input[name="has_variant_prices"]').checked;
|
| 2897 |
|
|
|
|
|
|
|
| 2898 |
const div = document.createElement('div');
|
| 2899 |
div.className = 'variant-row';
|
| 2900 |
|
|
|
|
| 2902 |
let reqAttr = hasVariantPrices ? 'required' : '';
|
| 2903 |
|
| 2904 |
let html = `
|
| 2905 |
+
<div class="form-group">
|
| 2906 |
+
<label>Название</label>
|
| 2907 |
+
<input type="text" name="variant_name[]" placeholder="Цвет, размер" required>
|
| 2908 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2909 |
`;
|
| 2910 |
|
| 2911 |
if (businessType !== 'retail') {
|
| 2912 |
html += `
|
| 2913 |
<div class="form-group">
|
| 2914 |
<label>В уп. (шт)</label>
|
| 2915 |
+
<input type="number" name="variant_pieces_per_box[]" placeholder="Шт">
|
| 2916 |
</div>`;
|
| 2917 |
}
|
| 2918 |
|
|
|
|
| 2921 |
<div class="form-group">
|
| 2922 |
<label>Штрих-код</label>
|
| 2923 |
<div style="display:flex; gap:5px;">
|
| 2924 |
+
<input type="text" name="variant_barcode[]" placeholder="Код">
|
| 2925 |
<button type="button" class="btn btn-outline" style="padding: 12px;" onclick="startScanner(val => this.previousElementSibling.value = val)"><i class="fas fa-barcode"></i></button>
|
| 2926 |
</div>
|
| 2927 |
</div>`;
|
|
|
|
| 2930 |
html += `
|
| 2931 |
<div class="form-group var-price-input" ${displayStyle}>
|
| 2932 |
<label>Цена</label>
|
| 2933 |
+
<input type="number" name="variant_price[]" placeholder="Цена за ед." step="0.01" ${reqAttr}>
|
| 2934 |
</div>
|
| 2935 |
`;
|
| 2936 |
|
|
|
|
| 2938 |
html += `
|
| 2939 |
<div class="form-group var-price-input" ${displayStyle}>
|
| 2940 |
<label>Цена уп.</label>
|
| 2941 |
+
<input type="number" name="variant_box_price[]" placeholder="Опционально" step="0.01">
|
| 2942 |
</div>`;
|
| 2943 |
}
|
| 2944 |
|
|
|
|
| 2946 |
html += `
|
| 2947 |
<div class="form-group">
|
| 2948 |
<label>Остаток</label>
|
| 2949 |
+
<input type="number" name="variant_stock[]" placeholder="Остаток">
|
| 2950 |
</div>`;
|
| 2951 |
}
|
| 2952 |
|
| 2953 |
+
html += `<button type="button" class="remove-variant-btn" onclick="this.parentElement.remove(); updateMainStockVisibility('${formId}')"><i class="fas fa-times-circle"></i></button>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2954 |
|
| 2955 |
div.innerHTML = html;
|
| 2956 |
container.appendChild(div);
|
|
|
|
| 2971 |
|
| 2972 |
varPriceInputs.forEach(input => {
|
| 2973 |
input.style.display = 'flex';
|
| 2974 |
+
const inp = input.querySelector('input[name="variant_price[]"]');
|
| 2975 |
+
if(inp) inp.setAttribute('required', 'required');
|
| 2976 |
});
|
| 2977 |
} else {
|
| 2978 |
if(mainPriceContainer) mainPriceContainer.style.display = 'flex';
|
|
|
|
| 2981 |
|
| 2982 |
varPriceInputs.forEach(input => {
|
| 2983 |
input.style.display = 'none';
|
| 2984 |
+
const inp = input.querySelector('input[name="variant_price[]"]');
|
| 2985 |
+
if(inp) inp.removeAttribute('required');
|
| 2986 |
});
|
| 2987 |
}
|
| 2988 |
}
|
|
|
|
| 3006 |
else mainBc.style.display = 'flex';
|
| 3007 |
}
|
| 3008 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3009 |
}
|
| 3010 |
|
| 3011 |
function filterAdmin() {
|
|
|
|
| 3431 |
<tr>
|
| 3432 |
<td style="font-weight:500;">${d}</td>
|
| 3433 |
<td>${data[d].orders}</td>
|
| 3434 |
+
<td>${Math.round(data[d].sum).toLocaleString()}</td>
|
| 3435 |
+
</tr>
|
| 3436 |
`;
|
| 3437 |
});
|
| 3438 |
}
|
|
|
|
| 4005 |
"variant_name": item.get('variant_name', ''),
|
| 4006 |
"variant_idx": item.get('variant_idx', -1),
|
| 4007 |
"discount": float(item.get('discount', 0)),
|
|
|
|
| 4008 |
"category": product_dict.get(item.get('product_id'), 'Без категории'),
|
| 4009 |
"photo_url": f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{item['photos'][0]}" if item.get('photos') else "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjBmMHcwIi8+PC9zdmc+"
|
| 4010 |
})
|
|
|
|
| 4384 |
data['settings'] = settings
|
| 4385 |
save_env_data(env_id, data)
|
| 4386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4387 |
elif action == 'add_category':
|
| 4388 |
cat_name = request.form.get('category_name', '').strip()
|
| 4389 |
if cat_name and cat_name not in categories:
|
|
|
|
| 4433 |
del data['category_photos'][cat_name]
|
| 4434 |
save_env_data(env_id, data)
|
| 4435 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4436 |
elif action == 'add_product':
|
| 4437 |
name = request.form.get('name', '').strip()
|
| 4438 |
barcode = request.form.get('barcode', '').strip()
|
|
|
|
| 4451 |
stock_str = request.form.get('stock', '')
|
| 4452 |
main_stock = int(stock_str) if stock_str else ""
|
| 4453 |
|
|
|
|
|
|
|
| 4454 |
description = request.form.get('description', '').strip()
|
| 4455 |
category = request.form.get('category')
|
| 4456 |
has_variant_prices = 'has_variant_prices' in request.form
|
| 4457 |
|
| 4458 |
+
variant_names = request.form.getlist('variant_name[]')
|
| 4459 |
+
variant_barcodes = request.form.getlist('variant_barcode[]')
|
| 4460 |
+
variant_prices = request.form.getlist('variant_price[]')
|
| 4461 |
+
variant_box_prices = request.form.getlist('variant_box_price[]')
|
| 4462 |
+
variant_stocks = request.form.getlist('variant_stock[]')
|
| 4463 |
+
variant_ppbs = request.form.getlist('variant_pieces_per_box[]')
|
| 4464 |
+
variants =[]
|
| 4465 |
|
| 4466 |
+
for i in range(len(variant_names)):
|
| 4467 |
+
v_name = variant_names[i].strip()
|
|
|
|
|
|
|
| 4468 |
if v_name:
|
|
|
|
| 4469 |
v_price = price
|
| 4470 |
v_box_price = box_price
|
| 4471 |
if has_variant_prices:
|
| 4472 |
+
if i < len(variant_prices) and variant_prices[i]:
|
| 4473 |
+
v_price = float(variant_prices[i])
|
| 4474 |
+
if i < len(variant_box_prices) and variant_box_prices[i]:
|
| 4475 |
+
v_box_price = float(variant_box_prices[i])
|
| 4476 |
+
|
| 4477 |
v_stock = ""
|
| 4478 |
+
if i < len(variant_stocks) and variant_stocks[i]:
|
| 4479 |
+
v_stock = int(variant_stocks[i])
|
| 4480 |
|
| 4481 |
+
v_barcode = ""
|
| 4482 |
+
if i < len(variant_barcodes) and variant_barcodes[i]:
|
| 4483 |
+
v_barcode = variant_barcodes[i].strip()
|
| 4484 |
|
| 4485 |
v_ppb = pieces_per_box
|
| 4486 |
+
if i < len(variant_ppbs) and variant_ppbs[i]:
|
| 4487 |
+
v_ppb = int(variant_ppbs[i])
|
| 4488 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4489 |
variants.append({
|
| 4490 |
"name": v_name,
|
| 4491 |
"barcode": v_barcode,
|
| 4492 |
"price": v_price,
|
| 4493 |
"box_price": v_box_price,
|
| 4494 |
"stock": v_stock,
|
| 4495 |
+
"pieces_per_box": v_ppb
|
|
|
|
|
|
|
| 4496 |
})
|
| 4497 |
|
| 4498 |
uploaded_photos = request.files.getlist('photos')[:10]
|
|
|
|
| 4534 |
'box_price': box_price,
|
| 4535 |
'min_order': min_order,
|
| 4536 |
'stock': main_stock,
|
|
|
|
|
|
|
| 4537 |
'description': description,
|
| 4538 |
'category': category,
|
| 4539 |
'photos': photos_list,
|
|
|
|
| 4564 |
stock_str = request.form.get('stock', '')
|
| 4565 |
main_stock = int(stock_str) if stock_str else ""
|
| 4566 |
|
|
|
|
|
|
|
| 4567 |
description = request.form.get('description', '').strip()
|
| 4568 |
has_variant_prices = 'has_variant_prices' in request.form
|
| 4569 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4570 |
remove_photos = request.form.getlist('remove_photos[]')
|
| 4571 |
|
| 4572 |
+
variant_names = request.form.getlist('variant_name[]')
|
| 4573 |
+
variant_barcodes = request.form.getlist('variant_barcode[]')
|
| 4574 |
+
variant_prices = request.form.getlist('variant_price[]')
|
| 4575 |
+
variant_box_prices = request.form.getlist('variant_box_price[]')
|
| 4576 |
+
variant_stocks = request.form.getlist('variant_stock[]')
|
| 4577 |
+
variant_ppbs = request.form.getlist('variant_pieces_per_box[]')
|
| 4578 |
+
variants =[]
|
| 4579 |
+
|
| 4580 |
+
for i in range(len(variant_names)):
|
| 4581 |
+
v_name = variant_names[i].strip()
|
| 4582 |
if v_name:
|
|
|
|
| 4583 |
v_price = price
|
| 4584 |
v_box_price = box_price
|
| 4585 |
if has_variant_prices:
|
| 4586 |
+
if i < len(variant_prices) and variant_prices[i]:
|
| 4587 |
+
v_price = float(variant_prices[i])
|
| 4588 |
+
if i < len(variant_box_prices) and variant_box_prices[i]:
|
| 4589 |
+
v_box_price = float(variant_box_prices[i])
|
|
|
|
| 4590 |
v_stock = ""
|
| 4591 |
+
if i < len(variant_stocks) and variant_stocks[i]:
|
| 4592 |
+
v_stock = int(variant_stocks[i])
|
| 4593 |
+
v_barcode = ""
|
| 4594 |
+
if i < len(variant_barcodes) and variant_barcodes[i]:
|
| 4595 |
+
v_barcode = variant_barcodes[i].strip()
|
| 4596 |
v_ppb = pieces_per_box
|
| 4597 |
+
if i < len(variant_ppbs) and variant_ppbs[i]:
|
| 4598 |
+
v_ppb = int(variant_ppbs[i])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4599 |
variants.append({
|
| 4600 |
"name": v_name,
|
| 4601 |
"barcode": v_barcode,
|
| 4602 |
"price": v_price,
|
| 4603 |
"box_price": v_box_price,
|
| 4604 |
"stock": v_stock,
|
| 4605 |
+
"pieces_per_box": v_ppb
|
|
|
|
|
|
|
| 4606 |
})
|
| 4607 |
|
| 4608 |
uploaded_photos = request.files.getlist('photos')[:10]
|
|
|
|
| 4644 |
p['box_price'] = box_price
|
| 4645 |
p['min_order'] = min_order
|
| 4646 |
p['stock'] = main_stock
|
|
|
|
|
|
|
| 4647 |
p['description'] = description
|
| 4648 |
p['variants'] = variants
|
| 4649 |
p['has_variant_prices'] = has_variant_prices
|