6-sub sprint: Dokumenti+HNS profil+Admin+ERP+CRM+PGŽ filter
SUB1 Dokumenti: pgz:dokumenti SECTIONS handler u app.html (klikabilan grid 19 godišnjaka, PDF stream) SUB2 HNS profil: sport2.html drill-down — bio-chips (visina/težina/noga/poz/dres) + HNS deep + Google + Wiki + 🏆 Karijera/📅 Utakmice tabovi (Josip Zec id=449: 257 nast/182 gol/15 sez) SUB3 Admin Users: sidebar.js href fix /admin/users → /sport/admin/users + razriješen audit ID konflikt SUB4 ERP Full: 5 novih endpointa (invoice-uploads, racuni/ulazni/{rid}/uploads, expense-reports, putni-nalog-racuni, payments) + 3 nova taba (📎 Uploads/OCR, ✈ Putni, 💰 Plaćanja) + inline stavke drill-down + sidebar entry SUB5 CRM Salesforce-Lite: dodan crm_v2 sidebar entry (router 956 linija već mounted) SUB6 PGŽ filter: 2 nova endpointa /api/v2/savezi/priority-sort + /api/v2/clanovi/priority-sort; togglePGZFilter wired u Klubovi/Savezi/Sportaši (sport2.html + app.html); ⭐💰📖 badge prefix; klubovi 1536/1641, savezi 35/246, sportaši 4979/5499 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+228
-12
@@ -117,6 +117,9 @@ table tbody tr:hover{background:var(--bg3)}
|
||||
<button class="tab" data-panel="glavna">📊 Glavna knjiga</button>
|
||||
<button class="tab" data-panel="partneri">🤝 Partneri</button>
|
||||
<button class="tab" data-panel="racuni">🧾 Računi</button>
|
||||
<button class="tab" data-panel="uploads">📎 Uploads (OCR)</button>
|
||||
<button class="tab" data-panel="putni">✈ Putni nalozi</button>
|
||||
<button class="tab" data-panel="payments">💰 Plaćanja</button>
|
||||
<button class="tab" data-panel="pdv">% PDV</button>
|
||||
<button class="tab" data-panel="place">💼 Plaće</button>
|
||||
<button class="tab" data-panel="proracun">€ Proračun</button>
|
||||
@@ -198,7 +201,76 @@ table tbody tr:hover{background:var(--bg3)}
|
||||
<button class="btn" onclick="loadRacuni()">Osvježi</button>
|
||||
</div>
|
||||
<div class="tbl-wrap">
|
||||
<table id="rac-tbl"><thead><tr><th>#</th><th>Broj</th><th>Datum</th><th>Partner</th><th>OIB</th><th class="num">Neto</th><th class="num">PDV</th><th class="num">Brutto</th><th>Status</th><th>Akcije</th></tr></thead><tbody></tbody></table>
|
||||
<table id="rac-tbl"><thead><tr><th>#</th><th>Broj</th><th>Datum</th><th>Partner</th><th>OIB</th><th class="num">Neto</th><th class="num">PDV</th><th class="num">Brutto</th><th>Status</th><th>Akcije</th></tr></thead><tbody><tr><td colspan="10" style="color:var(--t2);text-align:center;padding:18px">Klikni "Osvježi" za učitavanje…</td></tr></tbody></table>
|
||||
</div>
|
||||
<div id="rac-detail" style="display:none;margin-top:14px;border-top:1px solid var(--bd);padding-top:12px">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
||||
<h4 id="rac-detail-title" style="font-size:12px;color:var(--t2);margin:0">Stavke</h4>
|
||||
<button class="btn sec" onclick="document.getElementById('rac-detail').style.display='none'">× Zatvori</button>
|
||||
</div>
|
||||
<div class="tbl-wrap">
|
||||
<table id="rac-stavke-tbl"><thead><tr><th>#</th><th>Naziv</th><th class="num">Količina</th><th>JM</th><th class="num">Cijena</th><th class="num">Popust %</th><th class="num">PDV %</th><th class="num">Neto</th><th class="num">Brutto</th></tr></thead><tbody></tbody></table>
|
||||
</div>
|
||||
<h4 id="rac-uploads-title" style="font-size:12px;color:var(--t2);margin:12px 0 6px;display:none">Privitci (uploads)</h4>
|
||||
<div id="rac-uploads-wrap" style="display:none" class="tbl-wrap">
|
||||
<table id="rac-uploads-tbl"><thead><tr><th>#</th><th>Datoteka</th><th class="num">Veličina</th><th>Mime</th><th>OCR</th><th>Datum</th></tr></thead><tbody></tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ INVOICE UPLOADS (OCR) ============ -->
|
||||
<section class="panel" id="panel-uploads">
|
||||
<div class="card">
|
||||
<div class="card-h"><div class="card-t">Invoice Uploads (OCR/AI extraction)</div></div>
|
||||
<div class="toolbar">
|
||||
<input id="up-q" placeholder="Datoteka / vendor / broj…">
|
||||
<label>Status <select id="up-status"><option value="">— svi —</option><option value="pending">pending</option><option value="ocr_done">ocr_done</option><option value="approved">approved</option><option value="rejected">rejected</option></select></label>
|
||||
<button class="btn" onclick="loadUploads()">Osvježi</button>
|
||||
</div>
|
||||
<div class="tbl-wrap">
|
||||
<table id="up-tbl"><thead><tr><th>#</th><th>Datoteka</th><th class="num">Veličina</th><th>Vendor</th><th>OIB</th><th>Br. računa</th><th>Datum</th><th class="num">Brutto</th><th>OCR status</th><th class="num">Conf</th><th>Račun</th></tr></thead><tbody><tr><td colspan="11" style="color:var(--t2);text-align:center;padding:18px">Klikni "Osvježi"…</td></tr></tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ PUTNI NALOZI / EXPENSE REPORTS ============ -->
|
||||
<section class="panel" id="panel-putni">
|
||||
<div class="card">
|
||||
<div class="card-h"><div class="card-t">Putni nalozi i ostali troškovi (expense_reports)</div></div>
|
||||
<div class="toolbar">
|
||||
<label>Tip <select id="pn-type"><option value="">— svi —</option><option value="putni_nalog">Putni nalog</option><option value="expense">Trošak</option></select></label>
|
||||
<label>Status <select id="pn-status"><option value="">— svi —</option><option value="draft">draft</option><option value="podnesen">podnesen</option><option value="odobren">odobren</option><option value="isplacen">isplacen</option><option value="rejected">rejected</option></select></label>
|
||||
<label>Godina <input type="number" id="pn-godina" placeholder="2026" style="width:90px"></label>
|
||||
<button class="btn" onclick="loadExpenseReports()">Osvježi</button>
|
||||
</div>
|
||||
<div class="tbl-wrap">
|
||||
<table id="pn-tbl"><thead><tr><th>#</th><th>Tip</th><th>Klub</th><th>Odredište</th><th>Svrha</th><th>Od</th><th>Do</th><th class="num">Km</th><th class="num">Trošak</th><th class="num">Dnevnice</th><th>Status</th></tr></thead><tbody><tr><td colspan="11" style="color:var(--t2);text-align:center;padding:18px">Klikni "Osvježi"…</td></tr></tbody></table>
|
||||
</div>
|
||||
<div id="pn-detail" style="display:none;margin-top:14px;border-top:1px solid var(--bd);padding-top:12px">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
||||
<h4 id="pn-detail-title" style="font-size:12px;color:var(--t2);margin:0">Vezani računi</h4>
|
||||
<button class="btn sec" onclick="document.getElementById('pn-detail').style.display='none'">× Zatvori</button>
|
||||
</div>
|
||||
<div class="tbl-wrap">
|
||||
<table id="pn-rac-tbl"><thead><tr><th>#</th><th>Broj računa</th><th>Vendor</th><th class="num">Brutto</th><th>Valuta</th><th>Kategorija</th><th>Datum</th></tr></thead><tbody></tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ PAYMENTS ============ -->
|
||||
<section class="panel" id="panel-payments">
|
||||
<div class="card">
|
||||
<div class="card-h"><div class="card-t">Plaćanja / Bank Reconciliation (payments)</div></div>
|
||||
<div class="toolbar">
|
||||
<label>Status <select id="py-status"><option value="">— svi —</option><option value="unmatched">unmatched</option><option value="matched">matched</option><option value="manual">manual</option></select></label>
|
||||
<label>Način <select id="py-method"><option value="">— svi —</option><option value="transfer">transfer</option><option value="cash">cash</option><option value="card">card</option></select></label>
|
||||
<label>Godina <input type="number" id="py-godina" placeholder="2026" style="width:90px"></label>
|
||||
<button class="btn" onclick="loadPayments()">Osvježi</button>
|
||||
</div>
|
||||
<div class="tbl-wrap">
|
||||
<table id="py-tbl"><thead><tr><th>#</th><th>Datum</th><th>Klub</th><th class="num">Iznos</th><th>Valuta</th><th>Način</th><th>IBAN OD</th><th>IBAN ZA</th><th>Referenca</th><th>Račun</th><th>Putni nalog</th><th>Match</th></tr></thead><tbody><tr><td colspan="12" style="color:var(--t2);text-align:center;padding:18px">Klikni "Osvježi"…</td></tr></tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -689,10 +761,33 @@ async function knjizi(tip, id){
|
||||
catch(e){ alert(e.message); }
|
||||
}
|
||||
async function racDetail(tip, id){
|
||||
const d = await api('/racuni/'+tip+'/'+id);
|
||||
let html = `Račun ${d.head.broj||''} · ${d.head.partner_naziv}\nNeto: ${fmt(d.head.iznos_neto)} · PDV: ${fmt(d.head.iznos_pdv)} · Brutto: ${fmt(d.head.iznos_brutto)}\n\nSTAVKE:\n`;
|
||||
d.stavke.forEach(s => html += `${s.naziv} ${s.kolicina}×${fmt(s.cijena_jed)} = ${fmt(s.iznos_brutto)}\n`);
|
||||
alert(html);
|
||||
try {
|
||||
const d = await api('/racuni/'+tip+'/'+id);
|
||||
document.getElementById('rac-detail').style.display = 'block';
|
||||
document.getElementById('rac-detail-title').textContent =
|
||||
`Stavke računa ${tip} · #${id} · ${d.head.broj||'(bez broja)'} · ${d.head.partner_naziv||''} · Neto ${fmt(d.head.iznos_neto)} · Brutto ${fmt(d.head.iznos_brutto)}`;
|
||||
const sb = document.querySelector('#rac-stavke-tbl tbody');
|
||||
sb.innerHTML = (d.stavke||[]).length
|
||||
? d.stavke.map((s,ix)=>`<tr><td>${ix+1}</td><td>${s.naziv||''}</td><td class="num">${fmt(s.kolicina)}</td><td>${s.jed_mjera||''}</td><td class="num">${fmt(s.cijena_jed)}</td><td class="num">${fmt(s.popust_pct)}</td><td class="num">${fmt(s.pdv_pct)}</td><td class="num">${fmt(s.iznos_neto)}</td><td class="num"><b>${fmt(s.iznos_brutto)}</b></td></tr>`).join('')
|
||||
: `<tr><td colspan="9" style="color:var(--t2);text-align:center;padding:14px">Nema stavki.</td></tr>`;
|
||||
// For ulazni: show linked invoice_uploads (file_name)
|
||||
const upT = document.getElementById('rac-uploads-title');
|
||||
const upW = document.getElementById('rac-uploads-wrap');
|
||||
if (tip === 'ulazni') {
|
||||
upT.style.display='block'; upW.style.display='block';
|
||||
try {
|
||||
const u = await api('/racuni/ulazni/'+id+'/uploads');
|
||||
const ub = document.querySelector('#rac-uploads-tbl tbody');
|
||||
ub.innerHTML = (u.rows||[]).length
|
||||
? u.rows.map(r=>`<tr><td>${r.id}</td><td><a href="/uploads/${r.file_path||''}" target="_blank">${r.file_name||''}</a></td><td class="num">${fmt((r.file_size||0)/1024)} KB</td><td>${r.mime||''}</td><td>${r.ocr_status||''}</td><td>${(r.uploaded_at||'').slice(0,10)}</td></tr>`).join('')
|
||||
: `<tr><td colspan="6" style="color:var(--t2);text-align:center;padding:10px">Nema privitaka.</td></tr>`;
|
||||
} catch(e){
|
||||
document.querySelector('#rac-uploads-tbl tbody').innerHTML = `<tr><td colspan="6" style="color:var(--red)">Greška: ${e.message}</td></tr>`;
|
||||
}
|
||||
} else {
|
||||
upT.style.display='none'; upW.style.display='none';
|
||||
}
|
||||
} catch(e) { alert('Greška: '+e.message); }
|
||||
}
|
||||
async function openRacunModal(tip){
|
||||
await loadPartnerCache();
|
||||
@@ -858,13 +953,131 @@ async function savePlaca(){
|
||||
|
||||
// ===== PRORAČUN =====
|
||||
async function loadProracun(){
|
||||
const d = await api('/proracun');
|
||||
document.querySelector('#pr-tbl tbody').innerHTML = d.rows.map(r=>`<tr>
|
||||
<td><b>${r.godina}</b></td><td class="num">${fmt(r.proracun_pgz)}</td>
|
||||
<td class="num">${fmt(r.rebalans1)}</td><td class="num">${fmt(r.rebalans2)}</td>
|
||||
<td class="num"><b>${fmt(r.ukupno_pgz)}</b></td><td class="num">${fmt(r.ministarstvo)}</td>
|
||||
<td class="num"><b>${fmt(r.ukupno)}</b></td><td>${r.napomena||''}</td>
|
||||
</tr>`).join('');
|
||||
const tbody = document.querySelector('#pr-tbl tbody');
|
||||
tbody.innerHTML = `<tr><td colspan="8" style="color:var(--t2);text-align:center;padding:14px">Učitavam…</td></tr>`;
|
||||
try {
|
||||
const d = await api('/proracun');
|
||||
tbody.innerHTML = (d.rows||[]).length
|
||||
? d.rows.map(r=>`<tr>
|
||||
<td><b>${r.godina}</b></td><td class="num">${fmt(r.proracun_pgz)}</td>
|
||||
<td class="num">${fmt(r.rebalans1)}</td><td class="num">${fmt(r.rebalans2)}</td>
|
||||
<td class="num"><b>${fmt(r.ukupno_pgz)}</b></td><td class="num">${fmt(r.ministarstvo)}</td>
|
||||
<td class="num"><b>${fmt(r.ukupno)}</b></td><td>${r.napomena||''}</td>
|
||||
</tr>`).join('')
|
||||
: `<tr><td colspan="8" style="color:var(--t2);text-align:center;padding:14px">Nema podataka.</td></tr>`;
|
||||
} catch(e) {
|
||||
tbody.innerHTML = `<tr><td colspan="8" style="color:var(--red)">Greška: ${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== INVOICE UPLOADS =====
|
||||
async function loadUploads(){
|
||||
const tbody = document.querySelector('#up-tbl tbody');
|
||||
tbody.innerHTML = `<tr><td colspan="11" style="color:var(--t2);text-align:center;padding:14px">Učitavam…</td></tr>`;
|
||||
try {
|
||||
const q = (document.getElementById('up-q').value||'').trim();
|
||||
const st = document.getElementById('up-status').value;
|
||||
const p = new URLSearchParams();
|
||||
if(q) p.set('q', q);
|
||||
if(st) p.set('ocr_status', st);
|
||||
const d = await api('/invoice-uploads?'+p.toString());
|
||||
tbody.innerHTML = (d.rows||[]).length
|
||||
? d.rows.map(r=>`<tr>
|
||||
<td>${r.id}</td>
|
||||
<td><a href="/uploads/${r.file_path||''}" target="_blank">${r.file_name||''}</a></td>
|
||||
<td class="num">${fmt((r.file_size||0)/1024)} KB</td>
|
||||
<td>${r.ai_vendor_name||''}</td>
|
||||
<td>${r.ai_vendor_oib||''}</td>
|
||||
<td>${r.ai_invoice_no||''}</td>
|
||||
<td>${r.ai_invoice_date||''}</td>
|
||||
<td class="num">${fmt(r.ai_amount_gross)}</td>
|
||||
<td><span class="badge ${r.ocr_status||''}">${r.ocr_status||'—'}</span></td>
|
||||
<td class="num">${r.ocr_confidence!=null?fmt(r.ocr_confidence)+' %':''}</td>
|
||||
<td>${r.invoice_id?('#'+r.invoice_id):'—'}</td>
|
||||
</tr>`).join('')
|
||||
: `<tr><td colspan="11" style="color:var(--t2);text-align:center;padding:14px">Nema uploadova.</td></tr>`;
|
||||
} catch(e) {
|
||||
tbody.innerHTML = `<tr><td colspan="11" style="color:var(--red)">Greška: ${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== PUTNI NALOZI / EXPENSE REPORTS =====
|
||||
async function loadExpenseReports(){
|
||||
const tbody = document.querySelector('#pn-tbl tbody');
|
||||
tbody.innerHTML = `<tr><td colspan="11" style="color:var(--t2);text-align:center;padding:14px">Učitavam…</td></tr>`;
|
||||
try {
|
||||
const t = document.getElementById('pn-type').value;
|
||||
const s = document.getElementById('pn-status').value;
|
||||
const g = document.getElementById('pn-godina').value;
|
||||
const p = new URLSearchParams();
|
||||
if(t) p.set('report_type', t);
|
||||
if(s) p.set('status', s);
|
||||
if(g) p.set('godina', g);
|
||||
const d = await api('/expense-reports?'+p.toString());
|
||||
tbody.innerHTML = (d.rows||[]).length
|
||||
? d.rows.map(r=>`<tr onclick="expenseDetail(${r.id})" style="cursor:pointer">
|
||||
<td>${r.id}</td>
|
||||
<td>${r.report_type||''}</td>
|
||||
<td>${r.klub_naziv||r.klub_id||''}</td>
|
||||
<td>${r.destination||''}</td>
|
||||
<td>${r.purpose||''}</td>
|
||||
<td>${r.date_from||''}</td>
|
||||
<td>${r.date_to||''}</td>
|
||||
<td class="num">${fmt(r.km_driven)}</td>
|
||||
<td class="num">${fmt(r.cost_total)}</td>
|
||||
<td class="num">${fmt(r.dnevnice_amount)}</td>
|
||||
<td><span class="badge ${r.status||''}">${r.status||''}</span></td>
|
||||
</tr>`).join('')
|
||||
: `<tr><td colspan="11" style="color:var(--t2);text-align:center;padding:14px">Nema putnih naloga.</td></tr>`;
|
||||
} catch(e) {
|
||||
tbody.innerHTML = `<tr><td colspan="11" style="color:var(--red)">Greška: ${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function expenseDetail(id){
|
||||
try {
|
||||
document.getElementById('pn-detail').style.display='block';
|
||||
document.getElementById('pn-detail-title').textContent = `Vezani računi za putni nalog #${id}`;
|
||||
const d = await api('/putni-nalog-racuni?putni_nalog_id='+id);
|
||||
const tb = document.querySelector('#pn-rac-tbl tbody');
|
||||
tb.innerHTML = (d.rows||[]).length
|
||||
? d.rows.map(r=>`<tr><td>${r.id}</td><td>${r.invoice_no||('#'+r.invoice_id)}</td><td>${r.vendor_name||''}</td><td class="num">${fmt(r.amount_gross)}</td><td>${r.currency||''}</td><td>${r.kategorija||''}</td><td>${(r.attached_at||'').slice(0,10)}</td></tr>`).join('')
|
||||
: `<tr><td colspan="7" style="color:var(--t2);text-align:center;padding:10px">Nema vezanih računa.</td></tr>`;
|
||||
} catch(e) { alert('Greška: '+e.message); }
|
||||
}
|
||||
|
||||
// ===== PAYMENTS =====
|
||||
async function loadPayments(){
|
||||
const tbody = document.querySelector('#py-tbl tbody');
|
||||
tbody.innerHTML = `<tr><td colspan="12" style="color:var(--t2);text-align:center;padding:14px">Učitavam…</td></tr>`;
|
||||
try {
|
||||
const s = document.getElementById('py-status').value;
|
||||
const m = document.getElementById('py-method').value;
|
||||
const g = document.getElementById('py-godina').value;
|
||||
const p = new URLSearchParams();
|
||||
if(s) p.set('matched_status', s);
|
||||
if(m) p.set('payment_method', m);
|
||||
if(g) p.set('godina', g);
|
||||
const d = await api('/payments?'+p.toString());
|
||||
tbody.innerHTML = (d.rows||[]).length
|
||||
? d.rows.map(r=>`<tr>
|
||||
<td>${r.id}</td>
|
||||
<td>${r.payment_date||''}</td>
|
||||
<td>${r.klub_naziv||r.klub_id||''}</td>
|
||||
<td class="num"><b>${fmt(r.amount)}</b></td>
|
||||
<td>${r.currency||''}</td>
|
||||
<td>${r.payment_method||''}</td>
|
||||
<td>${r.iban_from||''}</td>
|
||||
<td>${r.iban_to||''}</td>
|
||||
<td>${r.reference||''}</td>
|
||||
<td>${r.invoice_id?('#'+r.invoice_id):'—'}</td>
|
||||
<td>${r.expense_report_id?('#'+r.expense_report_id):'—'}</td>
|
||||
<td><span class="badge ${r.matched_status||''}">${r.matched_status||''}</span></td>
|
||||
</tr>`).join('')
|
||||
: `<tr><td colspan="12" style="color:var(--t2);text-align:center;padding:14px">Nema plaćanja.</td></tr>`;
|
||||
} catch(e) {
|
||||
tbody.innerHTML = `<tr><td colspan="12" style="color:var(--red)">Greška: ${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== IZVJEŠTAJI =====
|
||||
@@ -925,6 +1138,9 @@ const loaders = {
|
||||
glavna: loadGlavnaKnjiga,
|
||||
partneri: loadPartneri,
|
||||
racuni: loadRacuni,
|
||||
uploads: loadUploads,
|
||||
putni: loadExpenseReports,
|
||||
payments: loadPayments,
|
||||
pdv: loadPdv,
|
||||
place: () => { loadZap(); loadPlace(); },
|
||||
proracun: loadProracun,
|
||||
|
||||
Reference in New Issue
Block a user