<script>
'use strict';
(function () {
const ready = fn => document.readyState === 'loading'
? document.addEventListener('DOMContentLoaded', fn)
: fn();
ready(function () {
const DEBUG = false;
const log = (...a) => DEBUG && console.log('[WS]', ...a);
const sizeLabel = 'Упаковка';
const zeroStartQuant = true;
const SEL = {
QTY_INPUT: '.t-store__prod__quantity-input, .t-catalog__prod__quantity-input',
QTY_PLUS: '.t-store__prod__quantity__plus-wrapper, .t-catalog__prod__quantity__plus-wrapper',
QTY_MINUS: '.t-store__prod__quantity__minus-wrapper, .t-catalog__prod__quantity__minus-wrapper',
QTY_WRAPPER: '.t-store__prod__quantity, .t-catalog__prod__quantity',
PRICE_WRAPPER: '.js-store-price-wrapper, .js-catalog-price-wrapper',
PRICE_VALUE: '.js-product-price, .js-catalog-prod-price-val',
PRICE_CURRENCY: 'div[class*="_price-currency"]'
};
function getFirstNumber(str) {
if (!str) return 0;
const m = String(str).match(/\d+/);
return m ? parseInt(m[0], 10) : 0;
}
function cacheQty(product, val) {
if (!product) return;
product.dataset._qty = val;
}
function parsePrice(t) {
if (!t) return NaN;
const cleaned = String(t).replace(/[^\d.,]/g, '').replace(',', '.');
return parseFloat(cleaned);
}
function formatPrice(n) {
if (isNaN(n)) return '';
const fixed = n.toFixed(2);
const [intPart, decPart] = fixed.split('.');
const withSpaces = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
return `${withSpaces}.${decPart}`;
}
function getInv(p) {
if (!p || !p.hasAttribute('data-product-inv')) return Infinity;
const raw = (p.getAttribute('data-product-inv') || '').trim();
if (raw === '') return Infinity;
const n = parseInt(raw, 10);
return isNaN(n) ? Infinity : n;
}
function getBaseStep(product) {
const optNames = product.querySelectorAll('.js-product-option-name');
const target = Array.from(optNames).find(el => el.textContent.trim() === sizeLabel);
let step = 1;
if (target) {
const sel = target.closest('.js-product-option')?.querySelector('select');
if (sel?.options?.length) {
step = getFirstNumber(sel.options[0].value) || 1;
}
}
return step;
}
function getStep(product) {
let step = getBaseStep(product);
const inv = getInv(product);
if (isFinite(inv) && inv > 0 && inv < step) step = 1;
return step;
}
function getMaxQty(p) {
const inv = getInv(p);
if (!isFinite(inv)) return Infinity;
if (inv <= 0) return 0;
const step = getStep(p);
return Math.floor(inv / step) * step;
}
function setupWholesaleUI(product) {
if (!product) return;
const optNames = product.querySelectorAll('.js-product-option-name');
const target = Array.from(optNames).find(el => el.textContent.trim() === sizeLabel);
if (!target) {
const staleWrapper = product.querySelector('.wholesale-wrapper');
if (staleWrapper) staleWrapper.remove();
product.querySelectorAll('.js-product-option.wholesale').forEach(el => {
el.classList.remove('wholesale');
});
return;
}
const wrap = target.closest('.js-product-option');
if (!wrap || !product.contains(wrap)) return;
wrap.classList.add('wholesale', 't-text');
const sel = wrap.querySelector('select');
if (sel) sel.disabled = true;
if (!sel || !sel.options.length) return;
const optText = sel.options[0].textContent.trim();
let wrapper = product.querySelector('.wholesale-wrapper');
if (wrapper) {
const title = wrapper.querySelector('.wholesale-title');
if (title && title.textContent.trim() !== optText) {
title.textContent = optText;
}
return;
}
const w = document.createElement('div');
w.className = 'wholesale-wrapper t-text';
const t = document.createElement('div');
t.className = 'wholesale-title';
t.textContent = optText;
w.appendChild(t);
const controls = product.querySelector('.js-product-controls-wrapper');
if (controls && product.contains(controls)) {
controls.parentNode.insertBefore(w, controls);
} else {
wrap.insertBefore(w, wrap.firstChild);
}
}
function updateTotalLabel(p) {
const inp = p.querySelector(SEL.QTY_INPUT);
if (!inp) return;
const q = parseInt(inp.value, 10);
const pw = p.querySelector(SEL.PRICE_WRAPPER);
if (!pw) return;
if (isNaN(q) || q <= 0) {
pw.removeAttribute('data-total-label');
return;
}
const pe = pw.querySelector(SEL.PRICE_VALUE);
if (!pe) return;
const pr = parsePrice(pe.textContent);
if (isNaN(pr) || pr <= 0) return;
let c = '';
const ce = pw.querySelector(SEL.PRICE_CURRENCY);
if (ce) c = ce.textContent.trim();
const tot = pr * q;
pw.setAttribute('data-total-label', `${pr} x ${q} = ${formatPrice(tot)} ${c}`.trim());
pw.classList.add('t-text');
}
function updateOrderButtonState(p) {
if (!p) return;
const inp = p.querySelector(SEL.QTY_INPUT);
if (!inp) return;
const q = parseInt(inp.value, 10) || 0;
const max = getMaxQty(p);
const disabled = q <= 0 || max <= 0;
const links = p.querySelectorAll('a[href^="#order"]');
links.forEach(a => {
if (disabled) {
a.classList.add('ws-disabled');
a.setAttribute('data-ws-disabled', '1');
} else {
a.classList.remove('ws-disabled');
a.removeAttribute('data-ws-disabled');
}
});
}
function setQty(p, value) {
const inp = p.querySelector(SEL.QTY_INPUT);
if (!inp) return;
inp.value = value;
inp.setAttribute('value', String(value));
cacheQty(p, value);
updateTotalLabel(p);
updateOrderButtonState(p);
}
function applyInitialQty(p) {
const inp = p.querySelector(SEL.QTY_INPUT);
if (!inp) return;
if (p.dataset._userTouched === '1') {
updateTotalLabel(p);
updateOrderButtonState(p);
return;
}
if (zeroStartQuant) {
inp.setAttribute('min', '0');
setQty(p, 0);
} else {
const step = getStep(p);
const max = getMaxQty(p);
const start = (isFinite(max) && max > 0) ? Math.min(step, max) : step;
inp.setAttribute('min', String(step));
setQty(p, start);
}
}
function handleBlur(e) {
const inp = e.target;
const p = inp.closest('.js-product');
if (!p) return;
p.dataset._userTouched = '1';
const step = getStep(p);
const max = getMaxQty(p);
let v = parseInt(inp.value, 10);
if (isNaN(v) || v < 0) v = 0;
if (v > 0) {
v = Math.max(step, Math.round(v / step) * step);
if (v > max) v = Math.max(0, Math.floor(max / step) * step);
} else {
v = zeroStartQuant ? 0 : Math.min(step, max);
}
setQty(p, v);
}
function initProduct(p) {
if (!p) return;
if (p.closest('.t706__product')) return;
const inp = p.querySelector(SEL.QTY_INPUT);
const pe = p.querySelector('.js-product-price');
if (!inp || !pe) return;
if (p.dataset._wsInited === '1') {
setupWholesaleUI(p);
return;
}
p.dataset._wsInited = '1';
p.classList.add('wholesale-product');
if (zeroStartQuant) {
inp.setAttribute('min', '0');
} else {
inp.setAttribute('min', String(getStep(p)));
}
const pw0 = p.querySelector(SEL.PRICE_WRAPPER);
if (pw0) pw0.removeAttribute('data-total-label');
[0, 300].forEach(t => setTimeout(() => applyInitialQty(p), t));
if (!inp._blurAttached) {
inp.addEventListener('blur', handleBlur);
inp._blurAttached = true;
}
setupWholesaleUI(p);
}
function runInit() {
document.querySelectorAll('.js-product').forEach(initProduct);
}
let moTimer;
const mo = new MutationObserver(() => {
clearTimeout(moTimer);
moTimer = setTimeout(runInit, 100);
});
function observeTargets() {
const targets = document.querySelectorAll(
'.js-store, .js-catalog, .js-product-single, .t-popup, .js-store-grid-cont, .js-catalog-grid-cont'
);
if (!targets.length) {
mo.observe(document.body, { childList: true, subtree: true });
return;
}
targets.forEach(t => mo.observe(t, { childList: true, subtree: true }));
}
observeTargets();
runInit();
function restoreQtyAfterOrder(p, stepVal) {
const inp = p.querySelector(SEL.QTY_INPUT);
if (!inp) return;
const restore = zeroStartQuant ? 0 : stepVal;
inp.value = restore;
inp.setAttribute('value', String(restore));
cacheQty(p, restore);
updateTotalLabel(p);
updateOrderButtonState(p);
}
function scheduleRestore(p, stepVal) {
restoreQtyAfterOrder(p, stepVal);
[100, 300, 700, 1500].forEach(t => {
setTimeout(() => restoreQtyAfterOrder(p, stepVal), t);
});
}
function findCartProductsByUid(uid) {
if (!window.tcart?.products) return [];
return Object.values(window.tcart.products).filter(p => p.gen_uid === uid);
}
function waitForCartProduct(uid, cb, maxAttempts = 20, interval = 100) {
let attempts = 0;
const t = setInterval(() => {
attempts++;
if (findCartProductsByUid(uid).length) {
clearInterval(t);
cb();
return;
}
if (attempts >= maxAttempts) clearInterval(t);
}, interval);
}
function getCartInv(prod) {
const a = Number(prod.inv);
if (a > 0) return a;
const b = Number(prod._wsInv);
return b > 0 ? b : Infinity;
}
function getCartStep(prod) {
const opt = (prod.options || []).find(o => o.option === sizeLabel);
let step = (opt && getFirstNumber(opt.variant)) || 1;
const inv = getCartInv(prod);
if (isFinite(inv) && inv < step) step = 1;
return step;
}
function getCartMax(prod, step) {
const inv = getCartInv(prod);
return isFinite(inv) ? Math.floor(inv / step) * step : Infinity;
}
(function cleanupOldCartField() {
const clean = () => {
Object.values(window.tcart?.products || {}).forEach(p => {
if ('wholesale_option' in p) delete p.wholesale_option;
});
};
if (typeof t_onReady === 'function') {
t_onReady(() => {
setTimeout(clean, 500);
setTimeout(clean, 1500);
});
} else {
setTimeout(clean, 500);
setTimeout(clean, 1500);
}
})();
document.addEventListener('click', function (ev) {
const plus = ev.target.closest(SEL.QTY_PLUS);
if (plus) {
const p = plus.closest('.js-product');
if (!p) return;
p.dataset._userTouched = '1';
ev.stopImmediatePropagation();
ev.preventDefault();
const step = getStep(p);
const max = getMaxQty(p);
const inp = plus.closest(SEL.QTY_WRAPPER)?.querySelector(SEL.QTY_INPUT);
if (inp) {
let v = parseInt(inp.value, 10) || 0;
let n = (v < step) ? step : v + step;
if (n > max) n = Math.max(v, 0);
if (!zeroStartQuant) n = Math.max(step, n);
setQty(p, n);
}
return;
}
const minus = ev.target.closest(SEL.QTY_MINUS);
if (minus) {
const p = minus.closest('.js-product');
if (!p) return;
p.dataset._userTouched = '1';
ev.stopImmediatePropagation();
ev.preventDefault();
const step = getStep(p);
const inp = minus.closest(SEL.QTY_WRAPPER)?.querySelector(SEL.QTY_INPUT);
if (inp) {
let v = parseInt(inp.value, 10) || 0;
const minVal = zeroStartQuant ? 0 : step;
v = Math.max(minVal, v - step);
setQty(p, v);
}
return;
}
const orderLink = ev.target.closest('a[href^="#order"]');
if (orderLink) {
const p = orderLink.closest('.js-product');
if (!p) return;
const inp = p.querySelector(SEL.QTY_INPUT);
if (!inp) return;
const q = parseInt(inp.value, 10) || 0;
const max = getMaxQty(p);
if (q <= 0 || max <= 0) {
ev.preventDefault();
ev.stopImmediatePropagation();
ev.stopPropagation();
}
}
}, true);
document.addEventListener('click', function (ev) {
const link = ev.target.closest('a[href^="#order"]');
if (!link) return;
const p = link.closest('.js-product');
if (!p) return;
const inp = p.querySelector(SEL.QTY_INPUT);
const savedQty = parseInt(p.dataset._qty, 10)
|| (inp ? parseInt(inp.value, 10) : 0)
|| 0;
const wb = p.querySelector('.wholesale');
if (!wb) return;
const uid = p.dataset.productGenUid;
if (!uid) return;
const sel = wb.querySelector('select');
if (!sel?.options.length) return;
const stepVal = getStep(p);
const invNow = getInv(p);
const maxNow = getMaxQty(p);
if (maxNow <= 0) return;
waitForCartProduct(uid, () => {
findCartProductsByUid(uid).forEach(prod => {
delete prod.wholesale_option;
let q = Math.max(
stepVal,
Math.ceil((prod.quantity || savedQty || stepVal) / stepVal) * stepVal
);
if (q > maxNow) q = maxNow;
if (q <= 0) return;
prod.quantity = q;
if (isFinite(invNow) && invNow > 0) prod._wsInv = invNow;
else delete prod._wsInv;
prod.amount = typeof tcart__roundPrice === 'function'
? tcart__roundPrice(prod.price * q)
: Math.round(prod.price * q * 100) / 100;
});
if (typeof tcart__saveLocalObj === 'function') tcart__saveLocalObj();
if (typeof tcart__reDrawProducts === 'function') tcart__reDrawProducts();
if (typeof tcart__reDrawTotal === 'function') tcart__reDrawTotal();
if (typeof tcart__updateTotalProductsinCartObj === 'function') tcart__updateTotalProductsinCartObj();
scheduleRestore(p, stepVal);
});
}, false);
window.addEventListener('click', function (ev) {
const btn = ev.target.closest('.t706__product-plus, .t706__product-minus');
if (!btn) return;
const idx = btn.closest('.t706__product')?.dataset.cartProductI;
const prod = window.tcart?.products?.[idx];
if (!prod) return;
const step = getCartStep(prod);
const max = getCartMax(prod, step);
if (step <= 1 && !isFinite(max)) return;
ev.preventDefault();
ev.stopImmediatePropagation();
const isPlus = btn.classList.contains('t706__product-plus');
const q = prod.quantity || 0;
let n;
if (isPlus) {
n = (Math.floor(q / step) + 1) * step;
if (n > max) return;
} else {
n = Math.max(step, (Math.ceil(q / step) - 1) * step);
if (n > max) n = max;
}
if (n === q) return;
prod.quantity = n;
prod.amount = typeof tcart__roundPrice === 'function'
? tcart__roundPrice(prod.price * n)
: Math.round(prod.price * n * 100) / 100;
if (typeof tcart__saveLocalObj === 'function') tcart__saveLocalObj();
if (typeof tcart__reDrawProducts === 'function') tcart__reDrawProducts();
if (typeof tcart__reDrawTotal === 'function') tcart__reDrawTotal();
if (typeof tcart__updateTotalProductsinCartObj === 'function') tcart__updateTotalProductsinCartObj();
}, true);
document.addEventListener('focusout', e => {
const inp = e.target.closest('.t706__product-quantity-inp');
if (!inp) return;
const idx = inp.closest('.t706__product')?.dataset.cartProductI;
if (idx === undefined) return;
setTimeout(() => {
const prod = window.tcart?.products?.[idx];
if (!prod) return;
const step = getCartStep(prod);
const max = getCartMax(prod, step);
if (step <= 1 && !isFinite(max)) return;
let v = parseInt(inp.value, 10);
if (isNaN(v) || v <= 0) v = step;
else v = Math.max(step, Math.round(v / step) * step);
if (v > max) v = max;
prod.quantity = v;
prod.amount = typeof tcart__roundPrice === 'function'
? tcart__roundPrice(prod.price * v)
: Math.round(prod.price * v * 100) / 100;
inp.value = v;
if (typeof tcart__saveLocalObj === 'function') tcart__saveLocalObj();
if (typeof tcart__reDrawProducts === 'function') tcart__reDrawProducts();
if (typeof tcart__reDrawTotal === 'function') tcart__reDrawTotal();
}, 100);
}, true);
document.addEventListener('input', e => {
if (e.target.matches(SEL.QTY_INPUT)) {
const p = e.target.closest('.js-product');
if (p) {
p.dataset._userTouched = '1';
cacheQty(p, e.target.value);
}
}
});
});
})();
</script>
<style>
.t706__cartwin-content {
max-width: 800px;
}
.js-product.wholesale-product .js-store-price-wrapper:after,
.js-product.wholesale-product .js-catalog-price-wrapper:after
{
content: "Всего: " attr(data-total-label);
display: block;
font-size: 14px;
width: 100%;
}
.js-product.wholesale-product .js-store-price-wrapper:not([data-total-label]):after,
.js-product.wholesale-product .js-store-price-wrapper[data-total-label=""]:after,
.js-product.wholesale-product .js-catalog-price-wrapper:not([data-total-label]):after,
.js-product.wholesale-product .js-catalog-price-wrapper[data-total-label=""]:after
{
display: none;
content: none;
}
.js-product a[href^="#order"].ws-disabled {
pointer-events: none;
cursor: not-allowed;
}
.js-product a[href^="#order"].ws-disabled.t-btn {
opacity: 0.4;
filter: grayscale(0.3);
}
.wholesale-title {
font-weight: 400 !important;
}
.wholesale-wrapper.t-text {
padding-left: 0 !important;
}
.wholesale-wrapper {
font-size: 15px;
opacity: 0.4;
padding-left: 0;
}
.wholesale-title {
font-weight: 400;
}
.js-product-option.wholesale select[disabled] {
pointer-events: none;
opacity: 0.5;
}
.wholesale .js-product-option-name,
.wholesale .t-product__option-variants
{
display: none;
}
.js-product-controls-wrapper.t-store__card__prod-controls-wrapper,
.js-product-controls-wrapper.t-catalog__card__prod-controls-wrapper
{
padding-top: 10px;
}
.t-product__option.js-product-option.wholesale {
margin-top: 0;
}
</style>