R5 ERP: bulk ops + XLSX export + HUB-3 PDF + stats + m2m + UI
Backend:
- pgz_sport.putni_nalog_racuni (m2m) — backfill iz attachments.invoice_ids
- erp/putni_nalozi.py:
* GET /putni-nalog/{id} sada vraća invoices (m2m) + suggested_invoices (auto-suggest po
klubu/datumu, ne-vezani)
* POST /putni-nalog/{id}/attach-invoice {invoice_id, kategorija}
* DELETE /putni-nalog/{id}/invoice/{invoice_id}
* GET /putni-nalog/{id}/hub3.pdf — A4 HUB-3 uplatnica + EPC QR (reuse crm.payments.build_hub3_pdf)
- erp/ocr.py:
* POST /invoices/bulk-pay {ids:[], paid_date, payment_method, iban_*, reference, tx_id}
* POST /invoices/bulk-cancel {ids:[], razlog} (audit per record)
* GET /export/invoices.xlsx — openpyxl, 17 stupaca (datum, izdavatelj, OIB, klub,
neto/PDV/brutto, status, IBAN, opis, kategorija); permission filtered
* GET /stats — month/quarter/year totals, by_kind breakdown, top_klubovi, putni_nalozi totals
UI (static/erp.html):
- Novi tab "📊 Statistika" (default) — 3 KPI kartice (mjesec/kvartal/godina) za račune
+ putne naloge, top klubovi godina, klub filter, Export XLSX gumb
- Računi tab: bulk toolbar (checkbox per row + Select All) → Plati sve modal
(IBAN platitelja, datum, ref) / Otkaži označene (prompt razlog) / Export XLSX
- Putni-nalog detail modal: novi gumb "📄 HUB-3 uplatnica (PDF)"
- klub selector bonus za stats tab
Live tests (8/8):
- GET /erp → 200, 61.5 KB
- /api/erp/stats month=€63.15 / pn_year=€455
- /export/invoices.xlsx → 200, application/vnd.ms-excel, valid PK header
- /putni-nalog/1/hub3.pdf → 200, application/pdf 53562 B (%PDF-)
- /attach-invoice → ok, link_id=1
- /bulk-pay {ids:[1]} → skipped:1 (već plaćen)
- /bulk-cancel {ids:[999]} → 0/0 (ne postoji, tolerantno)
- Suggested invoices vraća praznu listu nakon attach
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -914,7 +914,7 @@ def invoices_bulk_cancel(body: dict = Body(...), authorization: Optional[str] =
|
|||||||
|
|
||||||
|
|
||||||
# ── R5.4 XLSX EXPORT ───────────────────────────────────────────────────
|
# ── R5.4 XLSX EXPORT ───────────────────────────────────────────────────
|
||||||
@router.get("/invoices/export.xlsx")
|
@router.get("/export/invoices.xlsx")
|
||||||
def invoices_export_xlsx(
|
def invoices_export_xlsx(
|
||||||
tenant_id: Optional[int] = Query(None),
|
tenant_id: Optional[int] = Query(None),
|
||||||
klub_id: Optional[int] = Query(None),
|
klub_id: Optional[int] = Query(None),
|
||||||
|
|||||||
+161
-13
@@ -84,7 +84,8 @@ tr.clickable:hover { background:var(--bg-3); box-shadow:inset 3px 0 0 var(--acce
|
|||||||
<div class="app">
|
<div class="app">
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="brand"><h1>PGŽ ERP</h1><div class="sub">M5 OCR + M6 Putni nalozi</div></div>
|
<div class="brand"><h1>PGŽ ERP</h1><div class="sub">M5 OCR + M6 Putni nalozi</div></div>
|
||||||
<div class="nav-item active" data-tab="ocr"><span>📷</span><span>Skeniraj račun</span></div>
|
<div class="nav-item active" data-tab="stats"><span>📊</span><span>Statistika</span></div>
|
||||||
|
<div class="nav-item" data-tab="ocr"><span>📷</span><span>Skeniraj račun</span></div>
|
||||||
<div class="nav-item" data-tab="invoices"><span>€</span><span>Računi</span></div>
|
<div class="nav-item" data-tab="invoices"><span>€</span><span>Računi</span></div>
|
||||||
<div class="nav-item" data-tab="putni"><span>🚗</span><span>Novi putni nalog</span></div>
|
<div class="nav-item" data-tab="putni"><span>🚗</span><span>Novi putni nalog</span></div>
|
||||||
<div class="nav-item" data-tab="putni-list"><span>📋</span><span>Lista putnih naloga</span></div>
|
<div class="nav-item" data-tab="putni-list"><span>📋</span><span>Lista putnih naloga</span></div>
|
||||||
@@ -106,7 +107,28 @@ tr.clickable:hover { background:var(--bg-3); box-shadow:inset 3px 0 0 var(--acce
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- OCR -->
|
<!-- OCR -->
|
||||||
<div class="tab active" id="tab-ocr">
|
<!-- STATS TAB (R5.6) -->
|
||||||
|
<div class="tab active" id="tab-stats">
|
||||||
|
<div class="section">
|
||||||
|
<h3>📊 ERP statistika — mjesec / kvartal / godina</h3>
|
||||||
|
<div style="display:flex;gap:10px;align-items:end;margin-bottom:14px;flex-wrap:wrap">
|
||||||
|
<div><label class="lbl">Klub (opcionalno)</label><select id="st_klub" class="fld" style="max-width:280px"></select></div>
|
||||||
|
<button class="btn" onclick="loadStats()">↻ Osvježi</button>
|
||||||
|
<a id="st_export" class="btn sec" style="text-decoration:none" target="_blank">📥 Export XLSX</a>
|
||||||
|
</div>
|
||||||
|
<div id="stats_grid" style="display:grid;grid-template-columns:repeat(3,1fr);gap:14px"></div>
|
||||||
|
<div style="margin-top:18px">
|
||||||
|
<h4 style="font-size:12px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">Top klubovi (godina)</h4>
|
||||||
|
<table id="st_top_table"><thead><tr><th>Klub</th><th class="num">Br. računa</th><th class="num">Total</th></tr></thead><tbody></tbody></table>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:18px">
|
||||||
|
<h4 style="font-size:12px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">Putni nalozi</h4>
|
||||||
|
<div id="st_pn" style="display:grid;grid-template-columns:repeat(3,1fr);gap:14px"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab" id="tab-ocr">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h3>📷 Drag-and-drop OCR (PDF / JPG / PNG)</h3>
|
<h3>📷 Drag-and-drop OCR (PDF / JPG / PNG)</h3>
|
||||||
<div id="ocrDrop" style="border:2px dashed var(--border);border-radius:8px;padding:34px;text-align:center;cursor:pointer;background:var(--bg-3)">
|
<div id="ocrDrop" style="border:2px dashed var(--border);border-radius:8px;padding:34px;text-align:center;cursor:pointer;background:var(--bg-3)">
|
||||||
@@ -155,7 +177,14 @@ tr.clickable:hover { background:var(--bg-3); box-shadow:inset 3px 0 0 var(--acce
|
|||||||
<div class="tab" id="tab-invoices">
|
<div class="tab" id="tab-invoices">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h3>Računi (svi klubovi)</h3>
|
<h3>Računi (svi klubovi)</h3>
|
||||||
<table id="invTable"><thead><tr><th>#</th><th>Vrsta</th><th>Broj</th><th>Dobavljač</th><th>OIB</th><th>Klub</th><th class="num">Brutto</th><th>Status</th><th>Datum</th></tr></thead><tbody></tbody></table>
|
<div id="bulk_toolbar" style="display:flex;gap:8px;align-items:center;margin-bottom:12px;padding:10px;background:var(--bg-3);border:1px solid var(--border);border-radius:6px;flex-wrap:wrap">
|
||||||
|
<span style="font-size:12px;color:var(--text-3)">Označeno: <strong id="bulk_count" style="color:var(--accent)">0</strong></span>
|
||||||
|
<button class="btn green" id="bulk_pay_btn" onclick="openBulkPay()" disabled>💰 Plati sve označene</button>
|
||||||
|
<button class="btn red" id="bulk_cancel_btn" onclick="bulkCancel()" disabled>✗ Otkaži označene</button>
|
||||||
|
<button class="btn sec" onclick="bulkClear()">Očisti odabir</button>
|
||||||
|
<a id="inv_export_btn" class="btn sec" style="text-decoration:none;margin-left:auto" target="_blank">📥 Export XLSX (svi)</a>
|
||||||
|
</div>
|
||||||
|
<table id="invTable"><thead><tr><th style="width:24px"><input type="checkbox" id="bulk_all" onchange="bulkSelectAll(this.checked)"></th><th>#</th><th>Vrsta</th><th>Broj</th><th>Dobavljač</th><th>OIB</th><th>Klub</th><th class="num">Brutto</th><th>Status</th><th>Datum</th></tr></thead><tbody></tbody></table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -335,6 +364,30 @@ tr.clickable:hover { background:var(--bg-3); box-shadow:inset 3px 0 0 var(--acce
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ============ BULK PAY MODAL (R5.3) ============ -->
|
||||||
|
<div id="bulkPayModal" class="modal-bg" onclick="if(event.target===this)closeModal('bulkPayModal')">
|
||||||
|
<div class="modal" style="max-width:560px">
|
||||||
|
<div class="modal-h">
|
||||||
|
<h3>💰 Bulk plaćanje računa</h3>
|
||||||
|
<button class="x" onclick="closeModal('bulkPayModal')">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="bulkPayList" style="margin-bottom:12px;font-size:12px;color:var(--text-2)"></div>
|
||||||
|
<div class="grid2" style="gap:12px">
|
||||||
|
<div><label class="lbl">IBAN platitelja</label><input id="bp_iban_from" class="fld"></div>
|
||||||
|
<div><label class="lbl">Datum uplate</label><input id="bp_date" type="date" class="fld"></div>
|
||||||
|
<div><label class="lbl">Način plaćanja</label><select id="bp_method" class="fld"><option>transfer</option><option>cash</option><option>card</option></select></div>
|
||||||
|
<div><label class="lbl">Referenca</label><input id="bp_ref" class="fld"></div>
|
||||||
|
</div>
|
||||||
|
<div class="actions-row">
|
||||||
|
<button class="btn green" id="bulkPayConfirm">✓ Potvrdi plaćanje za sve</button>
|
||||||
|
<button class="btn sec" onclick="closeModal('bulkPayModal')">Odustani</button>
|
||||||
|
<span id="bulkPayStatus" style="font-size:12px;color:var(--text-3);align-self:center"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ============ REJECT PUTNI NALOG MODAL ============ -->
|
<!-- ============ REJECT PUTNI NALOG MODAL ============ -->
|
||||||
<div id="rejectModal" class="modal-bg" onclick="if(event.target===this)closeModal('rejectModal')">
|
<div id="rejectModal" class="modal-bg" onclick="if(event.target===this)closeModal('rejectModal')">
|
||||||
<div class="modal" style="max-width:480px">
|
<div class="modal" style="max-width:480px">
|
||||||
@@ -381,7 +434,7 @@ async function loadKlubovi() {
|
|||||||
.filter(k => k.naziv)
|
.filter(k => k.naziv)
|
||||||
.sort((a,b) => a.naziv.localeCompare(b.naziv,'hr'))
|
.sort((a,b) => a.naziv.localeCompare(b.naziv,'hr'))
|
||||||
.map(k => `<option value="${k.id}">${k.naziv.replace(/"/g,'"')}</option>`).join('');
|
.map(k => `<option value="${k.id}">${k.naziv.replace(/"/g,'"')}</option>`).join('');
|
||||||
['oc_klub','pn_klub'].forEach(id => { const e=$('#'+id); if (e) e.innerHTML=opts; });
|
['oc_klub','pn_klub','st_klub'].forEach(id => { const e=$('#'+id); if (e) e.innerHTML=opts; });
|
||||||
}
|
}
|
||||||
|
|
||||||
let ocrUploadId = null, ocrParsed = null;
|
let ocrUploadId = null, ocrParsed = null;
|
||||||
@@ -523,15 +576,106 @@ function pnInit() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _bulkSel = new Set();
|
||||||
|
function bulkUpdateUI() {
|
||||||
|
const n = _bulkSel.size;
|
||||||
|
const c = document.getElementById('bulk_count'); if (c) c.textContent = n;
|
||||||
|
['bulk_pay_btn','bulk_cancel_btn'].forEach(id => { const b=document.getElementById(id); if (b) b.disabled = n===0; });
|
||||||
|
document.querySelectorAll('.inv_chk').forEach(cb => cb.checked = _bulkSel.has(parseInt(cb.dataset.id)));
|
||||||
|
const all = document.getElementById('bulk_all'); if (all) all.checked = n>0 && document.querySelectorAll('.inv_chk').length === n;
|
||||||
|
}
|
||||||
|
function bulkToggle(id, on) { if (on) _bulkSel.add(id); else _bulkSel.delete(id); bulkUpdateUI(); }
|
||||||
|
function bulkSelectAll(on) {
|
||||||
|
_bulkSel.clear();
|
||||||
|
if (on) document.querySelectorAll('.inv_chk').forEach(cb => _bulkSel.add(parseInt(cb.dataset.id)));
|
||||||
|
bulkUpdateUI();
|
||||||
|
}
|
||||||
|
function bulkClear() { _bulkSel.clear(); bulkUpdateUI(); }
|
||||||
|
|
||||||
async function loadInvoices() {
|
async function loadInvoices() {
|
||||||
const r = await fetch(`${ERP_API}/invoices?limit=50`, {headers: AUTH_HDR()}).then(r=>r.json()).catch(()=>null);
|
const r = await fetch(`${ERP_API}/invoices?limit=200`, {headers: AUTH_HDR()}).then(r=>r.json()).catch(()=>null);
|
||||||
if (!r || !r.rows) return;
|
if (!r || !r.rows) return;
|
||||||
|
_bulkSel.clear();
|
||||||
$('#invTable tbody').innerHTML = r.rows.length ? r.rows.map(i=>`
|
$('#invTable tbody').innerHTML = r.rows.length ? r.rows.map(i=>`
|
||||||
<tr class="clickable" onclick="openInvoice(${i.id})"><td>${i.id}</td><td>${i.invoice_kind||'—'}</td><td>${i.invoice_no||'—'}</td>
|
<tr class="clickable">
|
||||||
<td>${i.vendor_name||'—'}</td><td style="font-family:'JetBrains Mono'">${i.vendor_oib||'—'}</td>
|
<td onclick="event.stopPropagation()"><input type="checkbox" class="inv_chk" data-id="${i.id}" onchange="bulkToggle(${i.id}, this.checked)"></td>
|
||||||
<td>${i.klub_naziv||'—'}</td><td class="num">${fmtEur(i.amount_gross)}</td>
|
<td onclick="openInvoice(${i.id})">${i.id}</td>
|
||||||
<td>${sBadge(i.payment_status)}</td><td>${fmtDate(i.invoice_date)}</td></tr>`).join('')
|
<td onclick="openInvoice(${i.id})">${i.invoice_kind||'—'}</td>
|
||||||
: '<tr><td colspan="9" style="color:var(--text-3);text-align:center;padding:20px">Nema podataka</td></tr>';
|
<td onclick="openInvoice(${i.id})">${i.invoice_no||'—'}</td>
|
||||||
|
<td onclick="openInvoice(${i.id})">${i.vendor_name||'—'}</td>
|
||||||
|
<td onclick="openInvoice(${i.id})" style="font-family:'JetBrains Mono'">${i.vendor_oib||'—'}</td>
|
||||||
|
<td onclick="openInvoice(${i.id})">${i.klub_naziv||'—'}</td>
|
||||||
|
<td class="num" onclick="openInvoice(${i.id})">${fmtEur(i.amount_gross)}</td>
|
||||||
|
<td onclick="openInvoice(${i.id})">${sBadge(i.payment_status)}</td>
|
||||||
|
<td onclick="openInvoice(${i.id})">${fmtDate(i.invoice_date)}</td>
|
||||||
|
</tr>`).join('')
|
||||||
|
: '<tr><td colspan="10" style="color:var(--text-3);text-align:center;padding:20px">Nema podataka</td></tr>';
|
||||||
|
bulkUpdateUI();
|
||||||
|
const exp = document.getElementById('inv_export_btn');
|
||||||
|
if (exp) exp.href = `${ERP_API}/export/invoices.xlsx`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openBulkPay() {
|
||||||
|
if (!_bulkSel.size) return;
|
||||||
|
$('#bulkPayList').textContent = `Računi: #${[..._bulkSel].sort((a,b)=>a-b).join(', #')}`;
|
||||||
|
$('#bp_date').value = new Date().toISOString().substring(0,10);
|
||||||
|
$('#bp_method').value = 'transfer';
|
||||||
|
$('#bulkPayStatus').textContent = '';
|
||||||
|
openModal('bulkPayModal');
|
||||||
|
$('#bulkPayConfirm').onclick = async () => {
|
||||||
|
const body = {
|
||||||
|
ids: [..._bulkSel],
|
||||||
|
paid_date: $('#bp_date').value,
|
||||||
|
payment_method: $('#bp_method').value,
|
||||||
|
iban_from: $('#bp_iban_from').value.trim(),
|
||||||
|
reference: $('#bp_ref').value.trim(),
|
||||||
|
};
|
||||||
|
$('#bulkPayStatus').textContent = '⏳';
|
||||||
|
const r = await fetch(`${ERP_API}/invoices/bulk-pay`, {method:'POST', headers: AUTH_HDR_JSON(), body: JSON.stringify(body)}).then(r=>r.json()).catch(()=>null);
|
||||||
|
if (r && r.ok) {
|
||||||
|
$('#bulkPayStatus').innerHTML = `✓ paid:${r.summary.paid} skipped:${r.summary.skipped} forbidden:${r.summary.forbidden}`;
|
||||||
|
$('#bulkPayStatus').style.color = 'var(--green)';
|
||||||
|
setTimeout(() => { closeModal('bulkPayModal'); loadInvoices(); }, 1200);
|
||||||
|
} else $('#bulkPayStatus').textContent = '❌ Greška';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function bulkCancel() {
|
||||||
|
if (!_bulkSel.size) return;
|
||||||
|
const reason = prompt('Razlog otkazivanja za ' + _bulkSel.size + ' računa:', 'duplikat / pogrešan upis');
|
||||||
|
if (reason === null) return;
|
||||||
|
const r = await fetch(`${ERP_API}/invoices/bulk-cancel`, {method:'POST', headers: AUTH_HDR_JSON(), body: JSON.stringify({ids:[..._bulkSel], razlog:reason})}).then(r=>r.json()).catch(()=>null);
|
||||||
|
if (r && r.ok) { alert(`Otkazano: ${r.summary.cancelled}, preskočeno: ${r.summary.skipped}, zabrana: ${r.summary.forbidden}`); loadInvoices(); }
|
||||||
|
else alert('Greška pri otkazivanju.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// === STATS (R5.6) ===
|
||||||
|
async function loadStats() {
|
||||||
|
const klub = $('#st_klub')?.value || '';
|
||||||
|
const url = `${ERP_API}/stats${klub ? '?klub_id='+klub : ''}`;
|
||||||
|
const r = await fetch(url, {headers: AUTH_HDR()}).then(r=>r.json()).catch(()=>null);
|
||||||
|
if (!r || !r.ok) return;
|
||||||
|
const inv = r.invoices;
|
||||||
|
const card = (label, period, accent) => `
|
||||||
|
<div style="background:var(--bg-3);border:1px solid var(--border);border-radius:8px;padding:14px;border-left:3px solid ${accent}">
|
||||||
|
<div style="font-size:11px;color:var(--text-3);text-transform:uppercase;letter-spacing:.5px">${label}</div>
|
||||||
|
<div style="font-size:24px;font-weight:700;font-family:'JetBrains Mono';color:${accent};margin:6px 0">€${period.total.toLocaleString('hr-HR')}</div>
|
||||||
|
<div style="font-size:11px;color:var(--text-2)">${period.n} računa · plaćeno €${period.paid.toLocaleString('hr-HR')} · neplaćeno €${period.unpaid.toLocaleString('hr-HR')}</div>
|
||||||
|
<div style="margin-top:8px;font-size:10px;color:var(--text-3)">od ${period.since}</div>
|
||||||
|
${period.by_kind && period.by_kind.length ? '<div style="margin-top:6px;font-size:10px">' + period.by_kind.slice(0,4).map(k => `${k.invoice_kind||'?'}: €${Math.round(k.total)}`).join(' · ') + '</div>' : ''}
|
||||||
|
</div>`;
|
||||||
|
$('#stats_grid').innerHTML = card('Mjesec', inv.month, 'var(--accent)') + card('Kvartal', inv.quarter, 'var(--yellow)') + card('Godina', inv.year, 'var(--green)');
|
||||||
|
$('#st_top_table tbody').innerHTML = (r.top_klubovi_godina||[]).map(t => `
|
||||||
|
<tr><td>${escHtml(t.klub_naziv||'—')}</td><td class="num">${t.n}</td><td class="num">${fmtEur(t.total)}</td></tr>`).join('') || '<tr><td colspan="3" style="text-align:center;color:var(--text-3);padding:14px">Nema podataka</td></tr>';
|
||||||
|
const pn = r.putni_nalozi;
|
||||||
|
const pnCard = (label, period, accent) => `
|
||||||
|
<div style="background:var(--bg-3);border:1px solid var(--border);border-radius:8px;padding:14px;border-left:3px solid ${accent}">
|
||||||
|
<div style="font-size:11px;color:var(--text-3);text-transform:uppercase">${label}</div>
|
||||||
|
<div style="font-size:22px;font-weight:700;font-family:'JetBrains Mono';color:${accent};margin:6px 0">€${period.total.toLocaleString('hr-HR')}</div>
|
||||||
|
<div style="font-size:11px;color:var(--text-2)">${period.n} naloga · dnevnice €${Math.round(period.dnevnice||0)} · transport €${Math.round(period.transport||0)}</div>
|
||||||
|
</div>`;
|
||||||
|
$('#st_pn').innerHTML = pnCard('Mjesec', pn.month, 'var(--accent)') + pnCard('Kvartal', pn.quarter, 'var(--yellow)') + pnCard('Godina', pn.year, 'var(--green)');
|
||||||
|
const exp = $('#st_export'); if (exp) exp.href = `${ERP_API}/export/invoices.xlsx${klub?'?klub_id='+klub:''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPutni() {
|
async function loadPutni() {
|
||||||
@@ -780,6 +924,7 @@ async function openPutni(id) {
|
|||||||
if (a.approve) acts.push(`<button class="btn green" onclick="approvePn(${id})">✓ Odobri</button>`);
|
if (a.approve) acts.push(`<button class="btn green" onclick="approvePn(${id})">✓ Odobri</button>`);
|
||||||
if (a.reject) acts.push(`<button class="btn red" onclick="openRejectModal(${id})">✗ Odbij</button>`);
|
if (a.reject) acts.push(`<button class="btn red" onclick="openRejectModal(${id})">✗ Odbij</button>`);
|
||||||
if (a.pay) acts.push(`<button class="btn green" onclick="openPayPnModal(${id})">💰 Isplati</button>`);
|
if (a.pay) acts.push(`<button class="btn green" onclick="openPayPnModal(${id})">💰 Isplati</button>`);
|
||||||
|
acts.push(`<a href="${ERP_API}/putni-nalog/${id}/hub3.pdf" target="_blank" class="btn sec" style="text-decoration:none">📄 HUB-3 uplatnica (PDF)</a>`);
|
||||||
if (a.edit) acts.push(`<button class="btn sec" onclick="alert('Edit drafta — koristi M6 formu \\'Novi putni nalog\\' s prefilanim poljima (TODO UI)')">✏ Edit</button>`);
|
if (a.edit) acts.push(`<button class="btn sec" onclick="alert('Edit drafta — koristi M6 formu \\'Novi putni nalog\\' s prefilanim poljima (TODO UI)')">✏ Edit</button>`);
|
||||||
if (!acts.length) acts.push('<span style="color:var(--text-3);font-size:12px">Bez dostupnih akcija (samo pregled).</span>');
|
if (!acts.length) acts.push('<span style="color:var(--text-3);font-size:12px">Bez dostupnih akcija (samo pregled).</span>');
|
||||||
$('#pn_actions').innerHTML = acts.join('');
|
$('#pn_actions').innerHTML = acts.join('');
|
||||||
@@ -839,19 +984,22 @@ function openPayPnModal(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function activate(name) {
|
function activate(name) {
|
||||||
$$('.nav-item').forEach(n => n.classList.toggle('active', n.dataset.tab === name));
|
if (!name) return;
|
||||||
|
$$('.nav-item').forEach(n => n.classList.toggle('active', n.dataset && n.dataset.tab === name));
|
||||||
$$('.tab').forEach(t => t.classList.toggle('active', t.id === 'tab-' + name));
|
$$('.tab').forEach(t => t.classList.toggle('active', t.id === 'tab-' + name));
|
||||||
const titles = {ocr:'Skeniraj račun (OCR)',invoices:'Računi',putni:'Novi putni nalog','putni-list':'Lista putnih naloga'};
|
const titles = {stats:'Statistika',ocr:'Skeniraj račun (OCR)',invoices:'Računi',putni:'Novi putni nalog','putni-list':'Lista putnih naloga'};
|
||||||
$('#pageTitle').textContent = titles[name] || name;
|
$('#pageTitle').textContent = titles[name] || name;
|
||||||
|
if (name === 'stats') loadStats();
|
||||||
if (name === 'invoices') loadInvoices();
|
if (name === 'invoices') loadInvoices();
|
||||||
if (name === 'putni-list') loadPutni();
|
if (name === 'putni-list') loadPutni();
|
||||||
}
|
}
|
||||||
$$('.nav-item').forEach(n => n.addEventListener('click', () => activate(n.dataset.tab)));
|
$$('.nav-item').forEach(n => { if (n.dataset && n.dataset.tab) n.addEventListener('click', () => activate(n.dataset.tab)); });
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await loadKlubovi();
|
await loadKlubovi();
|
||||||
ocrInit();
|
ocrInit();
|
||||||
pnInit();
|
pnInit();
|
||||||
|
loadStats();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user