/* ags 92 – B2B: akčná MOC z XML feedu (moc1.js.txt, verzia 2026-11-01a) * Problém: prenos Money → BUXUS plní pole „Akciová MOC“ len pre SK a aj tam ho hodinový import maže; CZ/HU nikdy. * Partner potom vidí plnú MOC a maržu 100–130 % namiesto ~20 %. (zadanie-ui42-jetsoft-akcna-moc-prenos-money-buxus) * Riešenie (len zobrazenie): pre každý produkt v zozname (katalóg, značka, hľadanie) a na detaile sa akčná MOC vezme * z nočného Indexu B2B (= XML feed, pole AKCNAMOC / AKCNIMOC / SPECIAL_RETAIL_PRICE) a ak ju BUXUS v karte nemá, * doplní sa riadok „Akciová MOC“, bežná MOC sa prečiarkne a marža % + hodnota marže sa prepočítajú rovnako ako BUXUS * (hodnota = akčná MOC bez DPH − VO; DPH SK 23 / CZ 21 / HU 27 %). Kde BUXUS akčnú MOC má, skript nič nemení. * Cenový blok cena* číta ten istý DOM → dostane správne čísla automaticky. Košík, VO a faktúry sa nedotýka. * Zapínanie: window.agsMoc = { mode:'off'|'preview'|'live', test:[odtlačky firiem], api:'…/exec', ttlHours:6 }. * Vypnutie: mode:'off' alebo zmazať riadok include v Containeri 22. Meranie: ags_moc_fix {miesto, n}, ags_moc_index, ags_moc_stop. */ (function () { 'use strict'; var C = window.agsMoc; if (!C || C.mode === 'off' || window.__agsMoc1) return; window.__agsMoc1 = 1; var M = (location.pathname.match(/^\/(sk|cz|hu)(\/|$)/) || [])[1]; if (!M) return; var h3 = document.querySelector('.header-basic-info h3'); var firm = h3 ? h3.textContent.replace(/\s+/g, ' ').trim() : ''; if (!firm) return; var FP = (function (s) { var x = 2166136261; for (var i = 0; i < s.length; i++) { x ^= s.charCodeAt(i); x = Math.imul(x, 16777619) >>> 0; } return x.toString(16); })('ags92|' + firm.toLowerCase()); var isTest = (C.test || []).indexOf(FP) >= 0; if (C.mode !== 'live' && !isTest) return; var VAT = { sk: 1.23, cz: 1.21, hu: 1.27 }[M]; var LBL = { sk: 'Akciová MOC', cz: 'Akční MOC', hu: 'Akciós fogyasztói ár' }[M]; var NB = String.fromCharCode(160); var API = C.api || ''; var TTL = (C.ttlHours || 6) * 3600 * 1000; function track(ev, o) { try { o = o || {}; o.event = ev; o.trh = M; (window.dataLayer = window.dataLayer || []).push(o); } catch (e) {} } function q(s, r) { return (r || document).querySelector(s); } function qa(s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); } /* ---------- Index B2B (feed) ---------- */ var byPid = {}, byCode = {}, READY = false, IDXT = ''; window.__agsMoc = { ready: false, byPid: byPid, byCode: byCode, fp: FP, test: isTest }; function idb() { return new Promise(function (res, rej) { try { var r = indexedDB.open('ags-hladaj', 1); r.onupgradeneeded = function () { r.result.createObjectStore('idx'); }; r.onsuccess = function () { res(r.result); }; r.onerror = function () { rej(r.error); }; } catch (e) { rej(e); } }); } function idbGet(k) { return idb().then(function (db) { return new Promise(function (res) { var t = db.transaction('idx').objectStore('idx').get(k); t.onsuccess = function () { res(t.result); }; t.onerror = function () { res(null); }; }); }).catch(function () { return null; }); } function idbSet(k, v) { return idb().then(function (db) { return new Promise(function (res) { var t = db.transaction('idx', 'readwrite'); t.objectStore('idx').put(v, k); t.oncomplete = function () { res(true); }; t.onerror = function () { res(false); }; }); }).catch(function () { return false; }); } function fetchT(url, ms) { var ctl = ('AbortController' in window) ? new AbortController() : null; var timer = ctl ? setTimeout(function () { ctl.abort(); }, ms) : null; return fetch(url, { signal: ctl ? ctl.signal : undefined, credentials: 'omit', cache: 'no-store' }).then(function (r) { if (timer) clearTimeout(timer); if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }); } function prepare(data) { var f = data.f || [], ix = {}; f.forEach(function (n, i) { ix[n] = i; }); if (ix.pid == null || ix.amoc == null || ix.moc == null) throw new Error('index bez cien'); var n = 0; (data.r || []).forEach(function (a) { var pid = a[ix.pid] | 0, moc = +a[ix.moc] || 0, am = +a[ix.amoc] || 0, code = a[ix.code]; var rec = { pid: pid, code: code, moc: moc, amoc: (am > 0 && moc > 0 && am < moc) ? am : 0 }; if (pid) byPid[pid] = rec; if (code) byCode[String(code).toUpperCase()] = rec; if (rec.amoc) n++; }); IDXT = data.t || ''; READY = true; window.__agsMoc.ready = true; window.__agsMoc.t = IDXT; window.__agsMoc.nAkc = n; } function load() { var K = 'moc-' + M; return idbGet(K).then(function (c) { var fresh = c && c.saved && (Date.now() - c.saved) < TTL && c.data && c.data.r; if (fresh) { prepare(c.data); track('ags_moc_index', { src: 'cache', t: IDXT }); return; } if (!API) throw new Error('no api'); return fetchT(API + (API.indexOf('?') > -1 ? '&' : '?') + 'm=' + M, 10000).then(function (data) { if (!data || !data.r || data.r.length < 100) throw new Error('bad index'); prepare(data); idbSet(K, { saved: Date.now(), data: data }); track('ags_moc_index', { src: 'net', t: IDXT }); }).catch(function (e) { if (c && c.data && c.data.r) { prepare(c.data); track('ags_moc_index', { src: 'stale', t: IDXT }); return; } throw e; }); }); } /* ---------- čísla ---------- */ function parseNum(t) { t = String(t || '').split(NB).join(' ').trim(); var m = t.match(/-?\d[\d ]*(?:[.,]\d+)?/); if (!m) return null; var n = parseFloat(m[0].replace(/ /g, '').replace(',', '.')); return isNaN(n) ? null : n; } /* zapíše číslo n do textu v rovnakom tvare, aký tam je (desatinné miesta, oddeľovač, mena, %) */ function fmtLike(n, sample) { var raw = String(sample || ''), hasNB = raw.indexOf(NB) >= 0, s = raw.split(NB).join(' '); var m = s.match(/-?\d[\d ]*(?:([.,])(\d+))?/); var hit = m ? m[0].replace(/\s+$/, '') : ''; var dec = m && m[2] ? m[2].length : 0, sep = m && m[1] ? m[1] : '.'; var parts = Math.abs(n).toFixed(dec).split('.'); var intp = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, hasNB ? NB : ' '); var num = (n < 0 ? '-' : '') + intp + (parts[1] ? sep + parts[1] : ''); return m ? (hasNB ? raw.replace(hit.split(' ').join(NB), num) : s.replace(hit, num)) : num; } /* ---------- oprava jedného cenového bloku ---------- */ var MUTE = 0, fixed = 0, BUD = { n: 0, t: Date.now(), stop: false }, BODY = null; function budget() { if (BUD.stop) return false; if (Date.now() - BUD.t > 5000) { BUD.t = Date.now(); BUD.n = 0; } if (++BUD.n > 600) { BUD.stop = true; try { BODY && BODY.disconnect(); } catch (e) {} track('ags_moc_stop', {}); return false; } return true; } function valEl(row) { return row ? (q('.text-right', row) || row.lastElementChild) : null; } function rowsOf(box) { return qa(':scope > .row', box); } function findRows(box) { var rows = rowsOf(box), r = { rp: null, ra: null, vo: null, mz: null, hm: null }; rows.forEach(function (x, i) { var cl = x.className || ''; if (/retail-action-price/.test(cl)) { r.ra = x; return; } if (/retail-price/.test(cl)) { r.rp = x; return; } if (q('[data-lazy-property="item-total-price-no-vat-formated-with-currency"]', x) || /your-price-without-vat/.test(cl)) { r.vo = x; return; } if (q('[data-lazy-property="item-margin-percent-formated"]', x)) { r.mz = x; r.hm = rows[i + 1] || null; return; } if (q('[data-lazy-property="item-margin-value-formated"]', x)) { r.hm = x; return; } }); if (!r.vo) { rows.forEach(function (x) { if (!r.vo && /your-price/.test(x.className || '') && /€|Kč|Ft/.test((valEl(x) || x).textContent)) r.vo = x; }); } if (!r.mz) { rows.forEach(function (x, i) { if (!r.mz && !/retail|vat-rate|stock/.test(x.className || '') && /\d\s*%\s*$/.test(((valEl(x) || x).textContent || '').trim())) { r.mz = x; if (!r.hm) r.hm = rows[i + 1] || null; } }); } return r; } function codeOf(scope) { var m = (scope && scope.textContent || '').replace(/\s+/g, ' ').match(/(?:Kód|Code|Kód produktu|Termékkód|Cikkszám)\s*:?\s*([A-Za-z0-9._\-\/]+)/i); return m ? m[1].toUpperCase() : ''; } function recOf(pid, scope) { return (pid && byPid[pid]) || byCode[codeOf(scope)] || null; } function patch(box, rec, where) { if (!rec || !rec.amoc) return false; var r = findRows(box); if (!r.rp) return false; var rpV = valEl(r.rp); if (!rpV) return false; if (r.ra && !r.ra.getAttribute('data-agsmoc')) return false; /* BUXUS akčnú MOC má – nemeníme */ var mocTxt = (q('s', rpV) || rpV).textContent; var mocN = parseNum(mocTxt); if (!mocN) return false; if (Math.abs(mocN - rec.moc) > Math.max(0.011, rec.moc * 0.001) && !r.ra) { /* bežná MOC v BUXUSe ≠ feed (iný stav dát) – akčnú doplníme len ak je stále nižšia než zobrazená MOC */ if (!(rec.amoc < mocN)) return false; } MUTE++; try { var changed = false; if (!q('s', rpV)) { rpV.innerHTML = '' + rpV.innerHTML + ''; changed = true; } if (!r.ra) { var ra = r.rp.cloneNode(true); ra.className = r.rp.className + ' retail-action-price'; ra.setAttribute('data-agsmoc', '1'); var lab = ra.firstElementChild; if (lab) lab.textContent = LBL; var rv = valEl(ra); if (rv) rv.innerHTML = fmtLike(rec.amoc, mocTxt); r.rp.parentNode.insertBefore(ra, r.rp.nextSibling); r.ra = ra; changed = true; } else { var rv2 = valEl(r.ra), want = fmtLike(rec.amoc, mocTxt); if (rv2 && rv2.textContent.split(NB).join(' ').trim() !== want.trim()) { rv2.innerHTML = want; changed = true; } } /* marža % a hodnota marže z akčnej MOC – až keď BUXUS načítal VO */ var voV = valEl(r.vo), vo = voV && !q('img', voV) ? parseNum(voV.textContent) : null; if (vo && r.mz) { var hm = Math.round((rec.amoc / VAT - vo) * 100) / 100, mz = Math.round(hm / vo * 100); var mzV = valEl(r.mz), mzT = mzV && q('strong', mzV) ? q('strong', mzV) : (mzV && q('[data-lazy-property]', mzV)) || mzV; if (mzT && !q('img', mzT) && /\d/.test(mzT.textContent)) { var w1 = fmtLike(mz, mzT.textContent); if (mzT.textContent !== w1) { mzT.textContent = w1; changed = true; } } if (r.hm) { var hmV = valEl(r.hm), hmT = hmV && q('strong', hmV) ? q('strong', hmV) : (hmV && q('[data-lazy-property]', hmV)) || hmV; if (hmT && !q('img', hmT) && /\d/.test(hmT.textContent)) { var w2 = fmtLike(hm, hmT.textContent); if (hmT.textContent !== w2) { hmT.textContent = w2; changed = true; } } } box.__agsmocDone = 1; } if (changed && !box.__agsmocCnt) { box.__agsmocCnt = 1; fixed++; } return changed; } finally { MUTE--; } } function watch(box, rec, where) { if (box.__agsmocW) return; box.__agsmocW = 1; var pid = rec.pid; var n = 0, t0 = Date.now(), ob = null; var upd = function () { if (MUTE) return; if (Date.now() - t0 > 1000) { t0 = Date.now(); n = 0; } if (++n > 40) { try { ob && ob.disconnect(); } catch (e) {} track('ags_moc_stop', { miesto: where, pid: pid }); return; } patch(box, rec, where); }; upd(); try { ob = new MutationObserver(function () { upd(); }); ob.observe(box, { childList: true, subtree: true, characterData: true }); } catch (e) {} } function detailPid(ps) { var root = ps.closest('.product-detail, .row, .container, body') || document.body; var a = q('.add-to-cart [href*="product_id="], [name="product_id"]', root) || q('[href*="product_id="]', root); if (a) { var m = (a.getAttribute('href') || a.value || '').match(/product_id=(\d+)/); if (m) return m[1]; } var d = q('[data-lazy-product-id]', root); if (d) return d.getAttribute('data-lazy-product-id'); return ''; } function run() { if (!READY || BUD.stop) return; var before = fixed; var ps = q('.product-price-summary'); if (ps) { var dr = recOf(detailPid(ps), ps.closest('.product-detail, .row, .container') || document.body); if (dr && budget()) watch(ps, dr, 'detail'); } qa('.product-card').forEach(function (card) { var pb = q('div.price', card); if (!pb || pb.__agsmocW) return; var rec = recOf(card.getAttribute('data-lazy-product-id'), q('.product-basic-data', card) || card); if (!rec) return; if (budget()) watch(pb, rec, 'zoznam'); }); if (fixed > before) track('ags_moc_fix', { miesto: ps ? 'detail' : 'zoznam', n: fixed - before }); } function start() { run(); try { var mo = 0; BODY = new MutationObserver(function (ms) { if (mo || MUTE || BUD.stop) return; var own = true; for (var i = 0; i < ms.length; i++) { var t = ms[i].target; if (!(t.closest && (t.closest('div.price') || t.closest('.product-price-summary')))) { own = false; break; } } if (own) return; mo = 1; setTimeout(function () { mo = 0; try { run(); } catch (e) {} }, 200); }); BODY.observe(document.body, { childList: true, subtree: true }); } catch (e) {} } load().then(function () { if (document.body) start(); else document.addEventListener('DOMContentLoaded', start); }).catch(function (e) { track('ags_moc_index', { src: 'fail' }); }); window.agsMocInfo = { ver: 'moc1-20261101a', market: M, test: isTest }; })();