/*
* AGS 92 B2B – náhľad faktúry + QR platba
* Súbor: /buxus/docs/faktury-nahlad.js.txt (načítaný v pätičke, Container 22 → Content after)
*
* Na stránke „Moje faktúry“ (page_id 257) pridá ku každej faktúre odkaz „Náhľad“ (okno s vykreslenou faktúrou,
* tlačidlá Stiahnuť / Tlačiť / Reklamovať) a pri nezaplatených faktúrach „QR platba“ – platobný QR kód
* vyrezaný priamo z PDF faktúry (Money: SK PAY by square, CZ QR Platba). Nič sa negeneruje, údaje sú
* vždy z tej faktúry, ktorú partner vidí. Ak PDF QR neobsahuje (HU), zobrazia sa len platobné údaje.
*
* Verejné rozhranie pre ďalšie skripty: window.agsFaktura.openPreview(pdfUrl, cislo, zostatok), .openQR(...)
*/
(function () {
'use strict';
if (window.__agsFakturaNahladLoaded) return;
window.__agsFakturaNahladLoaded = true;
var PDFJS = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js';
var PDFJS_WORKER = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
var QR_MARKETS = { sk: 1, cz: 1 }; // kde PDF faktúry obsahuje platobný QR (HU/PL doplniť po zapnutí v Money)
var REKLAMACIE_PAGE = 652491;
var QR_CACHE_MAX = 40; // koľko QR kódov držať v localStorage (per prehliadač)
var lang = (location.pathname.split('/')[1] || '').toLowerCase();
if (!/^(sk|cz|hu|pl|com)$/.test(lang)) return;
var L = lang === 'com' ? 'en' : lang;
if (!document.querySelector('.header-basic-info h3')) return; // len prihlásený partner
var T = {
sk: { preview: 'Náhľad', qr: 'QR platba', download: 'Stiahnuť PDF', print: 'Tlačiť', claim: 'Reklamovať', close: 'Zavrieť',
loading: 'Načítavam faktúru…', qrTitle: 'QR platba – faktúra', scan: 'Naskenujte QR kód v mobilnej bankovej aplikácii.',
remaining: 'Zostáva uhradiť', total: 'Suma na faktúre', partial: 'Faktúra je čiastočne uhradená – v bankovej aplikácii upravte sumu na zostatok.',
iban: 'IBAN', swift: 'SWIFT', vs: 'Variabilný symbol', copy: 'Kopírovať', copied: 'Skopírované', save: 'Uložiť QR',
noQr: 'Táto faktúra neobsahuje platobný QR kód. Použite platobné údaje nižšie.', err: 'Faktúru sa nepodarilo načítať. Skúste to znova alebo si ju stiahnite.',
page: 'Strana', of: 'z' },
cz: { preview: 'Náhled', qr: 'QR platba', download: 'Stáhnout PDF', print: 'Tisknout', claim: 'Reklamovat', close: 'Zavřít',
loading: 'Načítám fakturu…', qrTitle: 'QR platba – faktura', scan: 'Naskenujte QR kód v mobilní bankovní aplikaci.',
remaining: 'Zbývá uhradit', total: 'Částka na faktuře', partial: 'Faktura je částečně uhrazena – v bankovní aplikaci upravte částku na zůstatek.',
iban: 'IBAN', swift: 'SWIFT', vs: 'Variabilní symbol', copy: 'Kopírovat', copied: 'Zkopírováno', save: 'Uložit QR',
noQr: 'Tato faktura neobsahuje platební QR kód. Použijte platební údaje níže.', err: 'Fakturu se nepodařilo načíst. Zkuste to znovu nebo si ji stáhněte.',
page: 'Strana', of: 'z' },
hu: { preview: 'Előnézet', qr: 'QR fizetés', download: 'PDF letöltése', print: 'Nyomtatás', claim: 'Reklamáció', close: 'Bezárás',
loading: 'Számla betöltése…', qrTitle: 'QR fizetés – számla', scan: 'Olvassa be a QR kódot a mobilbanki alkalmazásban.',
remaining: 'Fizetendő', total: 'Számla összege', partial: 'A számla részben ki van fizetve – a banki alkalmazásban módosítsa az összeget.',
iban: 'IBAN', swift: 'SWIFT', vs: 'Közlemény / azonosító', copy: 'Másolás', copied: 'Másolva', save: 'QR mentése',
noQr: 'Ez a számla nem tartalmaz fizetési QR kódot. Használja az alábbi adatokat.', err: 'A számla betöltése nem sikerült. Próbálja újra vagy töltse le.',
page: 'Oldal', of: '/' },
pl: { preview: 'Podgląd', qr: 'Płatność QR', download: 'Pobierz PDF', print: 'Drukuj', claim: 'Reklamacja', close: 'Zamknij',
loading: 'Ładowanie faktury…', qrTitle: 'Płatność QR – faktura', scan: 'Zeskanuj kod QR w aplikacji bankowej.',
remaining: 'Do zapłaty', total: 'Kwota faktury', partial: 'Faktura jest częściowo opłacona – w aplikacji bankowej zmień kwotę.',
iban: 'IBAN', swift: 'SWIFT', vs: 'Tytuł / numer', copy: 'Kopiuj', copied: 'Skopiowano', save: 'Zapisz QR',
noQr: 'Ta faktura nie zawiera kodu QR. Użyj danych poniżej.', err: 'Nie udało się wczytać faktury. Spróbuj ponownie lub pobierz ją.',
page: 'Strona', of: 'z' },
en: { preview: 'Preview', qr: 'QR payment', download: 'Download PDF', print: 'Print', claim: 'Claim', close: 'Close',
loading: 'Loading invoice…', qrTitle: 'QR payment – invoice', scan: 'Scan the QR code in your mobile banking app.',
remaining: 'Amount due', total: 'Invoice amount', partial: 'The invoice is partially paid – adjust the amount in your banking app.',
iban: 'IBAN', swift: 'SWIFT', vs: 'Payment reference', copy: 'Copy', copied: 'Copied', save: 'Save QR',
noQr: 'This invoice has no payment QR code. Use the payment details below.', err: 'The invoice could not be loaded. Try again or download it.',
page: 'Page', of: 'of' }
}[L];
// ---------- pomocné ----------
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); }
function parseAmount(s) { s = (s || '').replace(/[^\d,.\-]/g, ''); if (s.indexOf(',') > -1 && s.indexOf('.') > -1) s = s.replace(/,/g, ''); else s = s.replace(',', '.'); var v = parseFloat(s); return isNaN(v) ? 0 : v; }
function lsGet(k) { try { return JSON.parse(localStorage.getItem(k) || 'null'); } catch (e) { return null; } }
function lsSet(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch (e) {} }
function el(tag, cls, html) { var e = document.createElement(tag); if (cls) e.className = cls; if (html != null) e.innerHTML = html; return e; }
// IBAN: z textu odstráni medzery a prípadný text nalepený za IBANom, overí kontrolný súčet (mod 97)
function validIban(s) {
s = (s || '').replace(/\s+/g, '').toUpperCase();
for (var len = Math.min(s.length, 34); len >= 15; len--) {
var c = s.slice(0, len), r = c.slice(4) + c.slice(0, 4), n = '';
for (var i = 0; i < r.length; i++) { var ch = r.charCodeAt(i); n += ch >= 65 ? String(ch - 55) : r[i]; }
var m = 0; for (var j = 0; j < n.length; j++) m = (m * 10 + (+n[j])) % 97;
if (m === 1) return c;
}
return '';
}
function invoiceId(url) { return ((url || '').match(/invoice_id=([A-Za-z0-9\-]+)/) || ['', ''])[1]; }
function copyText(t, btn) {
var done = function () { var o = btn.textContent; btn.textContent = T.copied; setTimeout(function () { btn.textContent = o; }, 1500); };
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(t).then(done, function () { fallback(); });
else fallback();
function fallback() { var ta = document.createElement('textarea'); ta.value = t; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); done(); } catch (e) {} ta.remove(); }
}
// ---------- pdf.js ----------
var pdfjsP = null;
function loadPdfJs() {
if (window.pdfjsLib) { window.pdfjsLib.GlobalWorkerOptions.workerSrc = window.pdfjsLib.GlobalWorkerOptions.workerSrc || PDFJS_WORKER; return Promise.resolve(window.pdfjsLib); }
if (pdfjsP) return pdfjsP;
pdfjsP = new Promise(function (res, rej) {
var def = window.define; window.define = undefined; // pdf.js by sa inak zaregistroval do requirejs stránky
var s = document.createElement('script'); s.src = PDFJS;
s.onload = function () { window.define = def; window.pdfjsLib.GlobalWorkerOptions.workerSrc = PDFJS_WORKER; res(window.pdfjsLib); };
s.onerror = function () { window.define = def; pdfjsP = null; rej(new Error('pdfjs')); };
document.head.appendChild(s);
});
return pdfjsP;
}
// PDF dokumenty držíme v pamäti stránky (nie v localStorage) – server ich generuje ~3 s
var docCache = {};
function getDoc(url) {
if (docCache[url]) return docCache[url];
docCache[url] = loadPdfJs().then(function (lib) {
return fetch(url, { credentials: 'same-origin' }).then(function (r) { if (!r.ok) throw new Error('http ' + r.status); return r.arrayBuffer(); })
.then(function (buf) { return lib.getDocument({ data: buf }).promise.then(function (pdf) { return { pdf: pdf, buf: buf }; }); });
});
docCache[url].catch(function () { delete docCache[url]; });
return docCache[url];
}
// ---------- platobné údaje + QR z PDF ----------
var QR_KEY = 'ags_qr_' + lang;
function qrCacheGet(id) { var c = lsGet(QR_KEY) || {}; return c[id] || null; }
function qrCachePut(id, v) {
var c = lsGet(QR_KEY) || {}; v.t = Date.now(); c[id] = v;
var keys = Object.keys(c); if (keys.length > QR_CACHE_MAX) { keys.sort(function (a, b) { return c[a].t - c[b].t; }).slice(0, keys.length - QR_CACHE_MAX).forEach(function (k) { delete c[k]; }); }
lsSet(QR_KEY, c);
}
function extractPayment(url) {
var id = invoiceId(url) || url;
var cached = qrCacheGet(id);
if (cached) return Promise.resolve(cached);
return getDoc(url).then(function (d) {
var pdf = d.pdf, lib = window.pdfjsLib;
var pageNums = []; for (var p = 1; p <= Math.min(pdf.numPages, 4); p++) pageNums.push(p);
// text zo všetkých strán (IBAN/VS sú na 1., súčty na poslednej); QR býva na poslednej strane (CZ) alebo na 1. (SK)
return Promise.all(pageNums.map(function (pn) {
return pdf.getPage(pn).then(function (page) {
return page.getTextContent().then(function (tc) {
var text = tc.items.map(function (i) { return i.str; }).join(' ').replace(/\s+/g, ' ');
return withTimeout(page.getOperatorList(), 8000).then(function (ops) {
var names = [];
for (var i = 0; i < ops.fnArray.length; i++) if (ops.fnArray[i] === lib.OPS.paintImageXObject || ops.fnArray[i] === lib.OPS.paintJpegXObject) names.push(ops.argsArray[i][0]);
return Promise.all(names.map(function (n) { return withTimeout(new Promise(function (res) { try { page.objs.get(n, res); } catch (e) { res(null); } }), 4000).catch(function () { return null; }); }));
}).catch(function () { return []; }).then(function (imgs) { return { text: text, imgs: imgs }; });
});
});
})).then(function (pages) {
var text = pages.map(function (x) { return x.text; }).join(' ');
var g = function (re) { var m = text.match(re); return m ? m[1].replace(/\s+/g, '') : ''; };
var info = {
iban: validIban(g(/IBAN:?\s*([A-Z]{2}\d{2}[A-Z0-9 ]{10,40})/)),
swift: g(/SWIFT:?\s*([A-Z0-9]{8,11})/),
account: g(/(?:Číslo účtu|Číslo účtu|Účet|Számlaszám|Nr rachunku|Account):?\s*([A-Z0-9\-\/ ]{8,34}?)(?=\s{2}|\s[A-ZÁ-Ž])/),
vs: g(/Variabiln[ýí] symbol:?\s*(\d{4,12})/i) || g(/Var\.?\s*(?:symbol|kód):?\s*(\d{4,12})/i)
};
// suma na faktúre: „Zostáva uhradiť / Zbývá uhradit / Fizetendő összeg“ (CZ zostava má číslo PRED „Celková částka“, preto nie ten riadok)
var tm = text.match(/(?:Zostáva uhradiť|Zbývá uhradit|Fizetendő összeg|Do zapłaty|Amount due)\s*:?\s*(\d[\d\s]*[,.]\d{2})\s*(EUR|CZK|Kč|HUF|Ft|PLN|zł)?/i);
info.totalTxt = tm ? (tm[1].replace(/\s+/g, ' ') + ' ' + (tm[2] || '')).trim() : '';
// platobný QR = najväčší približne štvorcový obrázok (logo je široké, čiarový kód veľmi široký); hľadá sa od poslednej strany
var best = null, bestScore = 0;
pages.slice().reverse().forEach(function (pg) {
pg.imgs.forEach(function (img) {
if (!img || !img.width || !img.height || img.width < 120) return;
var r = img.width / img.height; if (r < 0.6 || r > 1.4) return;
var score = img.width * img.height * (1 - Math.abs(1 - r));
if (score > bestScore) { bestScore = score; best = img; }
});
});
if (best) {
var c = document.createElement('canvas'); c.width = best.width; c.height = best.height; var ctx = c.getContext('2d');
ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, c.width, c.height);
if (best.bitmap) ctx.drawImage(best.bitmap, 0, 0);
else if (best.data) { var imd = ctx.createImageData(best.width, best.height); if (best.data.length === imd.data.length) imd.data.set(best.data); else { for (var p = 0, q = 0; p < imd.data.length; p += 4, q += 3) { imd.data[p] = best.data[q]; imd.data[p + 1] = best.data[q + 1]; imd.data[p + 2] = best.data[q + 2]; imd.data[p + 3] = 255; } } ctx.putImageData(imd, 0, 0); }
info.qr = c.toDataURL('image/png');
}
qrCachePut(id, info);
return info;
});
});
}
function withTimeout(p, ms) { return Promise.race([p, new Promise(function (_, rej) { setTimeout(function () { rej(new Error('timeout')); }, ms); })]); }
// ---------- UI ----------
function ico(path) { return 'url("data:image/svg+xml;utf8,' + encodeURIComponent("") + '")'; }
var CSS = '' +
'.ags-fx-cell{white-space:nowrap;vertical-align:top}.ags-fx-cell a{display:block;font-size:12px;line-height:1.75;cursor:pointer;text-decoration:none;color:#1c84c6}' +
'.ags-fx-cell a:before{content:"";display:inline-block;width:13px;height:13px;margin-right:5px;vertical-align:-2px;background-repeat:no-repeat;background-size:13px 13px}' +
'.ags-fx-cell a.ags-fx-prev:before{background-image:' + ico("
' + esc(T.scan) + '
'; else h += '' + esc(T.noQr) + '
'; if (ctx.unpaidTxt) h += '' + esc(ctx.unpaidTxt) + '
'; if (info.totalTxt && ctx.unpaid > 0 && Math.abs(parseAmount(info.totalTxt) - ctx.unpaid) > 0.005) h += '' + esc(r[1]) + '