Files
pgz-sport/static/erp_full.html
T
Damir Radulić efa15d0086 Task 2: Putni nalozi — full CRUD + status workflow
- routers/erp_full_router.py: GET/POST/PATCH/DELETE /api/v2/erp/putni-nalozi
  - status workflow: draft → poslano → odobreno/odbijeno → isplaceno
  - cost_total auto-calc, approved_at/paid_at on transitions
  - alias under /expense-reports/* preserved
- static/erp_full.html: novi UI lista + modal + status buttons

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 18:28:53 +02:00

1414 lines
75 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<!--
erp_full.html — FULL ERP (SAP-Lite) za PGŽ Sport
Author: Damir Radulić <dradulic@outlook.com> / damir@rinet.one
Version: 1.0.0
Date: 2026-05-05
Description: Sub-tabs Dnevnik | Glavna knjiga | Partneri | Računi | PDV | Plaće | Proračun | Izvještaji
-->
<html lang="hr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>PGŽ SPORT — ERP (SAP-Lite)</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
<style>
:root{
--pgz-blue:#003087; --pgz-blue2:#004CC4; --pgz-gold:#F4C430;
--bg0:#08090e; --bg1:#0d1021; --bg2:#111628; --bg3:#161d35; --bg4:#1c2542;
--rim:#1e2a50; --rim2:#283560;
--t0:#fff; --t1:#e2e6f0; --t2:#8a95b4; --t4:#4e5a7a;
--green:#00e88f; --red:#ff2d55; --amber:#f59e0b; --cyan:#00c8e8;
--font:'Inter',sans-serif; --mono:'JetBrains Mono',monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{font-family:var(--font);background:var(--bg0);color:var(--t1);font-size:13px;overflow-x:hidden}
button,input,select,textarea{font-family:inherit;font-size:inherit;outline:none}
::-webkit-scrollbar{width:8px;height:8px}
::-webkit-scrollbar-track{background:var(--bg1)}
::-webkit-scrollbar-thumb{background:var(--rim2);border-radius:4px}
.main{padding:0 0 0 0;flex:1;min-width:0}
.tb{background:var(--bg1);border-bottom:1px solid var(--rim);padding:12px 22px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:5}
.tb-t{font-size:15px;font-weight:700;color:var(--t0)}
.tb-s{font-size:11px;color:var(--t2)}
.content{padding:18px 22px}
.tabs{display:flex;gap:4px;border-bottom:1px solid var(--rim);margin-bottom:14px;flex-wrap:wrap}
.tab{padding:9px 16px;cursor:pointer;color:var(--t2);font-weight:600;font-size:12px;border-bottom:2px solid transparent;transition:all .15s;background:none;border-left:0;border-right:0;border-top:0}
.tab:hover{color:var(--t1)}
.tab.active{color:var(--pgz-gold);border-bottom-color:var(--pgz-gold)}
.panel{display:none}
.panel.active{display:block}
.card{background:var(--bg2);border:1px solid var(--rim);border-radius:8px;padding:14px;margin-bottom:14px}
.card-h{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--rim)}
.card-t{font-weight:700;color:var(--t0);font-size:13px}
.toolbar{display:flex;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap}
.toolbar input,.toolbar select{background:var(--bg2);border:1px solid var(--rim);border-radius:5px;padding:7px 10px;color:var(--t1);font-size:12px}
.toolbar input:focus,.toolbar select:focus{border-color:var(--pgz-blue2)}
.toolbar label{font-size:11px;color:var(--t2);display:flex;align-items:center;gap:6px}
.btn{background:var(--pgz-blue2);border:0;color:#fff;padding:7px 14px;border-radius:5px;cursor:pointer;font-size:12px;font-weight:600;transition:background .15s}
.btn:hover{background:var(--pgz-blue)}
.btn.gold{background:var(--pgz-gold);color:var(--bg0)}
.btn.gold:hover{background:#e0b220}
.btn.green{background:var(--green);color:var(--bg0)}
.btn.red{background:var(--red);color:#fff}
.btn.sec{background:var(--bg3);color:var(--t1);border:1px solid var(--rim)}
table{width:100%;border-collapse:collapse;font-size:12px}
table th{background:var(--bg3);color:var(--t2);text-transform:uppercase;font-size:10px;letter-spacing:.5px;padding:8px 10px;text-align:left;border-bottom:1px solid var(--rim);font-weight:700}
table td{padding:7px 10px;border-bottom:1px solid var(--rim);color:var(--t1)}
table tbody tr{cursor:pointer;transition:background .15s}
table tbody tr:hover{background:var(--bg3)}
.num{font-family:var(--mono);text-align:right}
.tbl-wrap{overflow-x:auto;max-height:600px;overflow-y:auto;border:1px solid var(--rim);border-radius:6px}
.modal-bg{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:100;align-items:flex-start;justify-content:center;padding-top:40px}
.modal-bg.show{display:flex}
.modal{background:var(--bg1);border:1px solid var(--rim);border-radius:8px;padding:18px;width:min(720px,94vw);max-height:90vh;overflow-y:auto}
.modal h3{font-size:14px;font-weight:700;color:var(--pgz-gold);margin-bottom:14px;padding-bottom:8px;border-bottom:1px solid var(--rim)}
.form-row{display:grid;grid-template-columns:140px 1fr;gap:8px;margin-bottom:8px;align-items:center}
.form-row label{font-size:11px;color:var(--t2)}
.form-row input,.form-row select,.form-row textarea{width:100%;background:var(--bg2);border:1px solid var(--rim);border-radius:4px;padding:6px 9px;color:var(--t1);font-size:12px}
.form-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:14px;padding-top:10px;border-top:1px solid var(--rim)}
.kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin-bottom:14px}
.kpi{background:linear-gradient(135deg,var(--bg2) 0%,var(--bg1) 100%);border:1px solid var(--rim);border-radius:8px;padding:12px 14px;position:relative}
.kpi::before{content:"";position:absolute;top:0;left:0;width:3px;height:100%;background:var(--pgz-gold)}
.kpi.g::before{background:var(--green)}
.kpi.r::before{background:var(--red)}
.kpi-l{font-size:10.5px;color:var(--t2);text-transform:uppercase;letter-spacing:1px;font-weight:600}
.kpi-v{font-size:22px;font-weight:800;color:var(--t0);margin-top:4px;font-family:var(--mono)}
.badge{display:inline-block;padding:2px 7px;border-radius:3px;font-size:10px;font-weight:600;text-transform:uppercase}
.badge.nacrt{background:var(--bg4);color:var(--t1)}
.badge.knjizen{background:var(--green);color:var(--bg0)}
.badge.placen{background:var(--pgz-gold);color:var(--bg0)}
.badge.otkazan{background:var(--red);color:#fff}
.badge.draft{background:var(--bg4);color:var(--t1)}
.badge.poslano{background:var(--cyan);color:var(--bg0)}
.badge.odobreno{background:var(--green);color:var(--bg0)}
.badge.odbijeno{background:var(--red);color:#fff}
.badge.isplaceno{background:var(--pgz-gold);color:var(--bg0)}
.dnev-line-row{display:grid;grid-template-columns:140px 1fr 100px 100px 1fr 30px;gap:6px;margin-bottom:6px;align-items:center}
.dnev-line-row input,.dnev-line-row select{background:var(--bg2);border:1px solid var(--rim);border-radius:4px;padding:5px 8px;color:var(--t1);font-size:12px;width:100%}
.dnev-balans{padding:8px;background:var(--bg3);border-radius:4px;margin-top:8px;font-family:var(--mono);font-size:11px}
.dnev-balans.ok{color:var(--green)}
.dnev-balans.bad{color:var(--red)}
</style>
<link rel="stylesheet" href="/static/shared/sidebar.css">
<script src="/static/shared/sidebar.js" defer data-active="erp_full"></script>
</head>
<body>
<main class="main">
<div class="tb">
<div>
<div class="tb-t">ERP — SAP-Lite</div>
<div class="tb-s">Dvostavno knjigovodstvo · HR-RRIF kontni plan · FINA e-Račun</div>
</div>
<div class="tb-s"><span style="color:var(--green)"></span> /api/v2/erp/*</div>
</div>
<div class="content">
<div class="tabs">
<button class="tab active" data-panel="dnevnik">📓 Dnevnik</button>
<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>
<button class="tab" data-panel="izvjestaji">📈 Izvještaji</button>
<button class="tab" data-panel="kontni">📚 Kontni plan</button>
</div>
<!-- ============ DNEVNIK ============ -->
<section class="panel active" id="panel-dnevnik">
<div class="card">
<div class="card-h">
<div class="card-t">Dnevnik knjiženja</div>
<button class="btn gold" onclick="openDnevnikModal()">+ Novi zapis</button>
</div>
<div class="toolbar">
<label>Godina <input type="number" id="dnev-godina" value="2026" style="width:90px"></label>
<label>Tip <select id="dnev-tip"><option value="">— svi —</option><option value="rucno">Ručno</option><option value="racun_u">Račun ulazni</option><option value="racun_i">Račun izlazni</option><option value="placa">Plaća</option><option value="storno">Storno</option></select></label>
<button class="btn" onclick="loadDnevnik()">Osvježi</button>
</div>
<div class="tbl-wrap">
<table id="dnev-tbl"><thead><tr><th>#</th><th>Datum</th><th>Opis</th><th>Tip</th><th class="num">Stavki</th><th class="num">Ukupno</th><th>Akcije</th></tr></thead><tbody></tbody></table>
</div>
</div>
</section>
<!-- ============ GLAVNA KNJIGA ============ -->
<section class="panel" id="panel-glavna">
<div class="card">
<div class="card-h">
<div class="card-t">Glavna knjiga (saldo po kontu)</div>
<button class="btn sec" onclick="exportXlsx('glavna-knjiga', 2026)">⬇ XLSX</button>
</div>
<div class="toolbar">
<label>Klasa <select id="gk-klasa"><option value="">— sve —</option><option>0</option><option>1</option><option>2</option><option>3</option><option>4</option><option>7</option><option>9</option></select></label>
<button class="btn" onclick="loadGlavnaKnjiga()">Osvježi</button>
</div>
<div class="tbl-wrap">
<table id="gk-tbl"><thead><tr><th>Šifra</th><th>Naziv</th><th>Klasa</th><th>Vrsta</th><th class="num">Duguje</th><th class="num">Potražuje</th><th class="num">Saldo</th><th class="num">Stavki</th></tr></thead><tbody></tbody></table>
</div>
</div>
</section>
<!-- ============ PARTNERI ============ -->
<section class="panel" id="panel-partneri">
<div class="card">
<div class="card-h">
<div class="card-t">Partneri (kupci/dobavljači)</div>
<button class="btn gold" onclick="openPartnerModal()">+ Novi partner</button>
</div>
<div class="toolbar">
<input id="part-q" placeholder="Pretraži (naziv/OIB)…">
<label>Vrsta <select id="part-vrsta"><option value="">— sve —</option><option value="kupac">Kupac</option><option value="dobavljac">Dobavljač</option><option value="oba">Oba</option></select></label>
<button class="btn" onclick="loadPartneri()">Osvježi</button>
</div>
<div class="tbl-wrap">
<table id="part-tbl"><thead><tr><th>#</th><th>OIB</th><th>Naziv</th><th>Vrsta</th><th>Grad</th><th>IBAN</th><th class="num">Saldo</th><th>Akcije</th></tr></thead><tbody></tbody></table>
</div>
</div>
</section>
<!-- ============ RAČUNI ============ -->
<section class="panel" id="panel-racuni">
<div class="card">
<div class="card-h">
<div class="card-t">Računi</div>
<div style="display:flex;gap:6px">
<button class="btn gold" onclick="openRacunModal('ulazni')">+ Ulazni</button>
<button class="btn green" onclick="openRacunModal('izlazni')">+ Izlazni</button>
<label class="btn sec" style="cursor:pointer;display:inline-flex;align-items:center;gap:6px">
📥 Import e-Račun XML
<input type="file" id="eracun-file" accept=".xml,application/xml" style="display:none" onchange="importERacun()">
</label>
</div>
</div>
<div class="toolbar">
<label>Tip <select id="rac-tip"><option value="ulazni">Ulazni</option><option value="izlazni">Izlazni</option></select></label>
<label>Status <select id="rac-status"><option value="">— svi —</option><option value="nacrt">Nacrt</option><option value="knjizen">Knjižen</option><option value="placen">Plaćen</option><option value="otkazan">Otkazan</option></select></label>
<label>Godina <input type="number" id="rac-godina" value="2026" style="width:90px"></label>
<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><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">Računi (OCR) — Invoice Uploads / AI extraction</div>
<div style="display:flex;gap:6px">
<button class="btn primary" onclick="document.getElementById('up-file').click()">📎 Upload novi račun</button>
<input type="file" id="up-file" accept="application/pdf,image/*" style="display:none" onchange="uploadInvoiceFile(this.files[0])">
</div>
</div>
<div id="up-drop" style="border:2px dashed var(--rim2);border-radius:8px;padding:18px;text-align:center;color:var(--t2);margin-bottom:10px;background:var(--bg3)">
Dovuci PDF / JPG / PNG ovdje (max 25 MB) ili koristi gumb gore.
<div id="up-progress" style="margin-top:6px;font-size:11px;color:var(--t1)"></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><th>Akcije</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>
<!-- ============ PUTNI NALOZI / EXPENSE REPORTS ============ -->
<section class="panel" id="panel-putni">
<div class="card">
<div class="card-h">
<div class="card-t">Putni nalozi (expense_reports)</div>
<button class="btn gold" onclick="openPutniModal()">+ Novi putni nalog</button>
</div>
<div class="toolbar">
<label>Godina <input type="number" id="pn-godina" placeholder="2026" style="width:90px"></label>
<label>Status <select id="pn-status"><option value="">— svi —</option><option value="draft">draft</option><option value="poslano">poslano</option><option value="odobreno">odobreno</option><option value="odbijeno">odbijeno</option><option value="isplaceno">isplaceno</option></select></label>
<label>Klub ID <input type="number" id="pn-klub" placeholder="ID kluba" style="width:90px"></label>
<label>Tip <select id="pn-type"><option value="">— svi —</option><option value="sluzbeno_putovanje">Službeno putovanje</option><option value="putni_nalog">Putni nalog</option><option value="expense">Trošak</option></select></label>
<button class="btn" onclick="loadExpenseReports()">Osvježi</button>
</div>
<div class="tbl-wrap">
<table id="pn-tbl"><thead><tr><th>#</th><th>Br</th><th>Klub</th><th>Destinacija</th><th>Od</th><th>Do</th><th class="num">Km</th><th class="num">Cost total</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"…</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>
<!-- ============ PDV ============ -->
<section class="panel" id="panel-pdv">
<div class="card">
<div class="card-h">
<div class="card-t">PDV — Knjige + Obrazac</div>
<div style="display:flex;gap:6px">
<button class="btn sec" onclick="exportXlsx('pdv-u', document.getElementById('pdv-godina').value, document.getElementById('pdv-mjesec').value)">⬇ U XLSX</button>
<button class="btn sec" onclick="exportXlsx('pdv-i', document.getElementById('pdv-godina').value, document.getElementById('pdv-mjesec').value)">⬇ I XLSX</button>
</div>
</div>
<div class="toolbar">
<label>Godina <input type="number" id="pdv-godina" value="2026" style="width:90px"></label>
<label>Mjesec <input type="number" id="pdv-mjesec" min="1" max="12" placeholder="1-12" style="width:80px"></label>
<button class="btn" onclick="loadPdv()">Osvježi</button>
</div>
<div id="pdv-summary" class="kpi-grid"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
<div>
<h4 style="font-size:12px;color:var(--t2);margin-bottom:8px">KNJIGA U-RA (ulazni)</h4>
<div class="tbl-wrap"><table id="pdv-u-tbl"><thead><tr><th>Broj</th><th>Datum</th><th>Partner</th><th>OIB</th><th class="num">Neto</th><th class="num">PDV</th></tr></thead><tbody></tbody></table></div>
</div>
<div>
<h4 style="font-size:12px;color:var(--t2);margin-bottom:8px">KNJIGA I-RA (izlazni)</h4>
<div class="tbl-wrap"><table id="pdv-i-tbl"><thead><tr><th>Broj</th><th>Datum</th><th>Partner</th><th>OIB</th><th class="num">Neto</th><th class="num">PDV</th></tr></thead><tbody></tbody></table></div>
</div>
</div>
</div>
</section>
<!-- ============ PLAĆE ============ -->
<section class="panel" id="panel-place">
<div class="card">
<div class="card-h">
<div class="card-t">Zaposlenici i obračun plaća</div>
<div style="display:flex;gap:6px">
<button class="btn gold" onclick="openZapModal()">+ Zaposlenik</button>
<button class="btn green" onclick="openPlacaModal()">€ Obračun plaće</button>
</div>
</div>
<h4 style="font-size:12px;color:var(--t2);margin:8px 0">Zaposlenici</h4>
<div class="tbl-wrap" style="margin-bottom:14px">
<table id="zap-tbl"><thead><tr><th>#</th><th>OIB</th><th>Ime</th><th>Prezime</th><th>Klub</th><th>Mjesto</th><th class="num">Bruto</th><th>Aktivan</th></tr></thead><tbody></tbody></table>
</div>
<h4 style="font-size:12px;color:var(--t2);margin:8px 0">Obračuni plaća</h4>
<div class="toolbar">
<label>Godina <input type="number" id="pl-godina" value="2026" style="width:90px"></label>
<label>Mjesec <input type="number" id="pl-mjesec" placeholder="1-12" style="width:80px"></label>
<button class="btn" onclick="loadPlace()">Osvježi</button>
</div>
<div class="tbl-wrap">
<table id="pl-tbl"><thead><tr><th>#</th><th>Zaposlenik</th><th>God/Mj</th><th class="num">Bruto</th><th class="num">Doprinosi iz</th><th class="num">Dohodnina</th><th class="num">Neto</th><th class="num">Doprinosi na</th><th class="num">Trošak</th></tr></thead><tbody></tbody></table>
</div>
</div>
</section>
<!-- ============ PRORAČUN ============ -->
<section class="panel" id="panel-proracun">
<div class="card">
<div class="card-h"><div class="card-t">Proračun PGŽ Sport (po godinama)</div></div>
<div class="tbl-wrap">
<table id="pr-tbl"><thead><tr><th>Godina</th><th class="num">Proračun PGŽ</th><th class="num">Rebalans 1</th><th class="num">Rebalans 2</th><th class="num">Ukupno PGŽ</th><th class="num">Ministarstvo</th><th class="num">Ukupno</th><th>Napomena</th></tr></thead><tbody></tbody></table>
</div>
</div>
</section>
<!-- ============ IZVJEŠTAJI ============ -->
<section class="panel" id="panel-izvjestaji">
<div class="card">
<div class="card-h">
<div class="card-t">Izvještaji (Bilanca · PnL · Cashflow)</div>
</div>
<div class="toolbar">
<label>Tip <select id="iz-tip"><option value="bilanca">Bilanca</option><option value="pnl">PnL (Račun dobiti/gubitka)</option><option value="cashflow">Cashflow</option></select></label>
<label>Godina <input type="number" id="iz-godina" value="2026" style="width:90px"></label>
<button class="btn" onclick="loadIzvjestaj()">Generiraj</button>
<button class="btn sec" onclick="exportXlsx(document.getElementById('iz-tip').value, document.getElementById('iz-godina').value)">⬇ XLSX</button>
<button class="btn sec" onclick="exportPdf(document.getElementById('iz-tip').value, document.getElementById('iz-godina').value)">⬇ PDF</button>
</div>
<div id="iz-out"></div>
</div>
</section>
<!-- ============ KONTNI PLAN ============ -->
<section class="panel" id="panel-kontni">
<div class="card">
<div class="card-h">
<div class="card-t">Kontni plan (HR-RRIF)</div>
<button class="btn gold" onclick="openKontoModal()">+ Novi konto</button>
</div>
<div class="toolbar">
<input id="kp-q" placeholder="Šifra ili naziv…">
<label>Klasa <select id="kp-klasa"><option value="">— sve —</option><option>0</option><option>1</option><option>2</option><option>3</option><option>4</option><option>7</option><option>9</option></select></label>
<label>Vrsta <select id="kp-vrsta"><option value="">— sve —</option><option value="aktiva">Aktiva</option><option value="pasiva">Pasiva</option><option value="prihod">Prihod</option><option value="rashod">Rashod</option><option value="kapital">Kapital</option></select></label>
<button class="btn" onclick="loadKontniPlan()">Osvježi</button>
</div>
<div class="tbl-wrap">
<table id="kp-tbl"><thead><tr><th>Šifra</th><th>Naziv</th><th>Klasa</th><th>Vrsta</th><th>Aktivan</th><th>Akcije</th></tr></thead><tbody></tbody></table>
</div>
</div>
</section>
</div>
</main>
<!-- ===== MODALS ===== -->
<div class="modal-bg" id="m-konto" onclick="if(event.target===this)closeModal('m-konto')">
<div class="modal">
<h3>Novi / izmjena konta</h3>
<div class="form-row"><label>Šifra</label><input id="k-sifra"></div>
<div class="form-row"><label>Naziv</label><input id="k-naziv"></div>
<div class="form-row"><label>Klasa</label><input type="number" id="k-klasa" min="0" max="9"></div>
<div class="form-row"><label>Vrsta</label><select id="k-vrsta"><option value="aktiva">Aktiva</option><option value="pasiva">Pasiva</option><option value="prihod">Prihod</option><option value="rashod">Rashod</option><option value="kapital">Kapital</option><option value="izvanbilanca">Izvanbilanca</option></select></div>
<div class="form-actions">
<button class="btn sec" onclick="closeModal('m-konto')">Odustani</button>
<button class="btn gold" onclick="saveKonto()">Spremi</button>
</div>
</div>
</div>
<div class="modal-bg" id="m-partner" onclick="if(event.target===this)closeModal('m-partner')">
<div class="modal">
<h3>Novi / izmjena partnera</h3>
<div class="form-row"><label>Naziv</label><input id="p-naziv"></div>
<div class="form-row"><label>OIB</label><input id="p-oib" maxlength="11"></div>
<div class="form-row"><label>Vrsta</label><select id="p-vrsta"><option value="oba">Oba</option><option value="kupac">Kupac</option><option value="dobavljac">Dobavljač</option></select></div>
<div class="form-row"><label>IBAN</label><input id="p-iban"></div>
<div class="form-row"><label>Adresa</label><input id="p-adresa"></div>
<div class="form-row"><label>Grad</label><input id="p-grad"></div>
<div class="form-row"><label>Email</label><input id="p-email"></div>
<div class="form-row"><label>Telefon</label><input id="p-telefon"></div>
<div class="form-actions">
<button class="btn sec" onclick="closeModal('m-partner')">Odustani</button>
<button class="btn gold" onclick="savePartner()">Spremi</button>
</div>
</div>
</div>
<div class="modal-bg" id="m-dnev" onclick="if(event.target===this)closeModal('m-dnev')">
<div class="modal" style="width:min(960px,96vw)">
<h3>Novi zapis u dnevniku</h3>
<div class="form-row"><label>Datum</label><input type="date" id="d-datum"></div>
<div class="form-row"><label>Opis</label><input id="d-opis"></div>
<div class="form-row"><label>Tip dokumenta</label><select id="d-tip"><option value="rucno">Ručno</option><option value="racun_u">Račun ulazni</option><option value="racun_i">Račun izlazni</option><option value="placa">Plaća</option></select></div>
<h4 style="font-size:11px;color:var(--t2);margin:14px 0 6px">STAVKE (D=duguje, P=potražuje, samo jedno > 0)</h4>
<div id="d-lines"></div>
<button class="btn sec" onclick="addDnevLine()" style="margin-top:8px">+ Dodaj stavku</button>
<div id="d-balans" class="dnev-balans">Duguje: 0,00 · Potražuje: 0,00</div>
<div class="form-actions">
<button class="btn sec" onclick="closeModal('m-dnev')">Odustani</button>
<button class="btn gold" onclick="saveDnev()">Spremi zapis</button>
</div>
</div>
</div>
<div class="modal-bg" id="m-rac" onclick="if(event.target===this)closeModal('m-rac')">
<div class="modal" style="width:min(900px,96vw)">
<h3 id="m-rac-title">Novi račun</h3>
<div class="form-row"><label>Broj</label><input id="r-broj"></div>
<div class="form-row"><label>Partner</label><select id="r-partner"></select></div>
<div class="form-row"><label>Datum izdavanja</label><input type="date" id="r-datum"></div>
<div class="form-row"><label>Dospijeće</label><input type="date" id="r-dospjece"></div>
<div class="form-row"><label>Status</label><select id="r-status"><option value="nacrt">Nacrt</option><option value="knjizen">Knjižen (auto-knjiženje)</option></select></div>
<h4 style="font-size:11px;color:var(--t2);margin:14px 0 6px">STAVKE</h4>
<div id="r-lines"></div>
<button class="btn sec" onclick="addRacLine()" style="margin-top:8px">+ Dodaj stavku</button>
<div id="r-summary" class="dnev-balans">Neto: 0,00 · PDV: 0,00 · Brutto: 0,00</div>
<div class="form-actions">
<button class="btn sec" onclick="closeModal('m-rac')">Odustani</button>
<button class="btn gold" onclick="saveRac()">Spremi račun</button>
</div>
</div>
</div>
<div class="modal-bg" id="m-zap" onclick="if(event.target===this)closeModal('m-zap')">
<div class="modal">
<h3>Novi zaposlenik</h3>
<div class="form-row"><label>Ime</label><input id="z-ime"></div>
<div class="form-row"><label>Prezime</label><input id="z-prezime"></div>
<div class="form-row"><label>OIB</label><input id="z-oib" maxlength="11"></div>
<div class="form-row"><label>Klub ID</label><input type="number" id="z-klub"></div>
<div class="form-row"><label>Radno mjesto</label><input id="z-mjesto"></div>
<div class="form-row"><label>Plata bruto</label><input type="number" step="0.01" id="z-bruto"></div>
<div class="form-row"><label>IBAN</label><input id="z-iban"></div>
<div class="form-actions">
<button class="btn sec" onclick="closeModal('m-zap')">Odustani</button>
<button class="btn gold" onclick="saveZap()">Spremi</button>
</div>
</div>
</div>
<div class="modal-bg" id="m-pn" onclick="if(event.target===this)closeModal('m-pn')">
<div class="modal" style="width:min(820px,96vw)">
<h3 id="m-pn-title">Novi putni nalog</h3>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div class="form-row"><label>Klub ID *</label><input type="number" id="pn-f-klub"></div>
<div class="form-row"><label>User ID</label><input type="number" id="pn-f-user"></div>
<div class="form-row"><label>Član ID</label><input type="number" id="pn-f-clan"></div>
<div class="form-row"><label>Tip</label><select id="pn-f-type"><option value="sluzbeno_putovanje">Službeno putovanje</option><option value="putni_nalog">Putni nalog</option><option value="expense">Trošak</option></select></div>
<div class="form-row"><label>Br. naloga</label><input id="pn-f-no" placeholder="auto / opc"></div>
<div class="form-row"><label>Destinacija *</label><input id="pn-f-dest"></div>
<div class="form-row" style="grid-column:1/3"><label>Svrha *</label><input id="pn-f-purpose"></div>
<div class="form-row"><label>Datum od *</label><input type="date" id="pn-f-from"></div>
<div class="form-row"><label>Datum do *</label><input type="date" id="pn-f-to"></div>
<div class="form-row"><label>Vozilo (tip)</label><input id="pn-f-vtype" placeholder="osobno / službeno"></div>
<div class="form-row"><label>Reg. oznaka</label><input id="pn-f-vplate"></div>
<div class="form-row"><label>Km voženo</label><input type="number" step="0.1" id="pn-f-km" value="0" oninput="recalcPutniTotal()"></div>
<div class="form-row"><label>€ / km</label><input type="number" step="0.01" id="pn-f-kmrate" value="0.42" oninput="recalcPutniTotal()"></div>
<div class="form-row"><label>Trošak prijevoz</label><input type="number" step="0.01" id="pn-f-tr" value="0" oninput="recalcPutniTotal()"></div>
<div class="form-row"><label>Trošak smještaj</label><input type="number" step="0.01" id="pn-f-lo" value="0" oninput="recalcPutniTotal()"></div>
<div class="form-row"><label>Trošak hrana</label><input type="number" step="0.01" id="pn-f-me" value="0" oninput="recalcPutniTotal()"></div>
<div class="form-row"><label>Ostali troškovi</label><input type="number" step="0.01" id="pn-f-ot" value="0" oninput="recalcPutniTotal()"></div>
<div class="form-row"><label>Br. dnevnica</label><input type="number" step="0.5" id="pn-f-dnc" value="0" oninput="recalcPutniTotal()"></div>
<div class="form-row"><label>Iznos dnevnice</label><input type="number" step="0.01" id="pn-f-dna" value="30.00" oninput="recalcPutniTotal()"></div>
<div class="form-row" style="grid-column:1/3"><label>Napomena</label><textarea id="pn-f-notes" rows="2"></textarea></div>
</div>
<div class="dnev-balans ok" id="pn-f-total" style="margin-top:10px">Ukupno: 0,00 €</div>
<div class="form-actions">
<button class="btn sec" onclick="closeModal('m-pn')">Odustani</button>
<button class="btn gold" onclick="savePutni()">Spremi</button>
</div>
</div>
</div>
<div class="modal-bg" id="m-pl" onclick="if(event.target===this)closeModal('m-pl')">
<div class="modal">
<h3>Obračun plaće (HR 2026)</h3>
<div class="form-row"><label>Zaposlenik</label><select id="pl-zap"></select></div>
<div class="form-row"><label>Godina</label><input type="number" id="pl-god" value="2026"></div>
<div class="form-row"><label>Mjesec</label><input type="number" id="pl-mj" min="1" max="12"></div>
<div class="form-row"><label>Bruto (€)</label><input type="number" step="0.01" id="pl-bruto" placeholder="prazno = iz zaposlenika"></div>
<div class="form-row"><label>Osobni odbitak</label><input type="number" step="0.01" id="pl-odb" value="600"></div>
<div class="form-row"><label>Prirez %</label><input type="number" step="0.1" id="pl-prirez" value="0"></div>
<div class="form-row"><label>Datum isplate</label><input type="date" id="pl-isplata"></div>
<div class="form-row"><label>Knjiži u dnevnik</label><input type="checkbox" id="pl-knjizi"></div>
<div class="form-actions">
<button class="btn sec" onclick="closeModal('m-pl')">Odustani</button>
<button class="btn gold" onclick="savePlaca()">Obračunaj</button>
</div>
</div>
</div>
<script>
const API = '/api/v2/erp';
const AUTH = () => ({ 'Authorization': 'Bearer ' + (localStorage.getItem('jwt') || localStorage.getItem('access_token') || 'admin-pgz-2026') });
const fmt = n => (Number(n||0)).toLocaleString('hr-HR',{minimumFractionDigits:2,maximumFractionDigits:2});
async function api(path, opts={}) {
const r = await fetch(API + path, { headers: { 'Content-Type':'application/json', ...AUTH() }, ...opts });
if (!r.ok) {
let detail = await r.text();
throw new Error(`${r.status}: ${detail}`);
}
return r.json();
}
// Tab switching
document.querySelectorAll('.tab').forEach(t => t.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));
document.querySelectorAll('.panel').forEach(x=>x.classList.remove('active'));
t.classList.add('active');
const panel = t.dataset.panel;
document.getElementById('panel-' + panel).classList.add('active');
loaders[panel] && loaders[panel]();
}));
function closeModal(id){ document.getElementById(id).classList.remove('show'); }
function openModal(id){ document.getElementById(id).classList.add('show'); }
// ===== KONTNI PLAN =====
async function loadKontniPlan(){
const q = document.getElementById('kp-q').value;
const klasa = document.getElementById('kp-klasa').value;
const vrsta = document.getElementById('kp-vrsta').value;
const params = new URLSearchParams();
if (q) params.set('q', q);
if (klasa) params.set('klasa', klasa);
if (vrsta) params.set('vrsta', vrsta);
const d = await api('/kontni-plan?' + params.toString());
const tbody = document.querySelector('#kp-tbl tbody');
tbody.innerHTML = d.rows.map(r=>`<tr>
<td><b>${r.sifra}</b></td><td>${r.naziv}</td><td>${r.klasa}</td><td>${r.vrsta}</td>
<td>${r.aktivan?'✓':'✗'}</td>
<td><button class="btn sec" onclick="event.stopPropagation();editKonto(${r.id})">✎</button></td>
</tr>`).join('');
}
function openKontoModal(){
['k-sifra','k-naziv','k-klasa'].forEach(i=>document.getElementById(i).value='');
document.getElementById('m-konto').dataset.id='';
openModal('m-konto');
}
async function editKonto(id){
const d = await api('/kontni-plan?'); // we don't have a single GET, so fetch & filter
const r = d.rows.find(x=>x.id===id);
if(!r) return;
document.getElementById('k-sifra').value=r.sifra;
document.getElementById('k-naziv').value=r.naziv;
document.getElementById('k-klasa').value=r.klasa;
document.getElementById('k-vrsta').value=r.vrsta;
document.getElementById('m-konto').dataset.id=id;
openModal('m-konto');
}
async function saveKonto(){
const body = {
sifra: document.getElementById('k-sifra').value.trim(),
naziv: document.getElementById('k-naziv').value.trim(),
klasa: parseInt(document.getElementById('k-klasa').value),
vrsta: document.getElementById('k-vrsta').value,
aktivan: true
};
const id = document.getElementById('m-konto').dataset.id;
try {
if(id) await api('/kontni-plan/'+id, { method:'PUT', body: JSON.stringify(body) });
else await api('/kontni-plan', { method:'POST', body: JSON.stringify(body) });
closeModal('m-konto'); loadKontniPlan();
} catch(e){ alert(e.message); }
}
// ===== PARTNERI =====
async function loadPartneri(){
const q = document.getElementById('part-q').value;
const v = document.getElementById('part-vrsta').value;
const p = new URLSearchParams();
if(q)p.set('q',q); if(v)p.set('vrsta',v);
const d = await api('/partneri?'+p.toString());
const tbody = document.querySelector('#part-tbl tbody');
tbody.innerHTML = d.rows.map(r=>`<tr onclick="loadPartnerSaldo(${r.id})">
<td>${r.id}</td><td>${r.oib||'—'}</td><td><b>${r.naziv}</b></td>
<td>${r.vrsta}</td><td>${r.grad||''}</td><td>${r.iban||''}</td>
<td class="num" id="ps-${r.id}">…</td>
<td><button class="btn sec" onclick="event.stopPropagation();editPartner(${r.id})">✎</button></td>
</tr>`).join('');
d.rows.forEach(r => {
api('/partneri/'+r.id+'/saldo').then(x => {
const el = document.getElementById('ps-'+r.id);
if(el) el.textContent = fmt(x.info?.saldo || 0);
}).catch(()=>{});
});
}
function openPartnerModal(){
['p-naziv','p-oib','p-iban','p-adresa','p-grad','p-email','p-telefon'].forEach(i=>document.getElementById(i).value='');
document.getElementById('m-partner').dataset.id='';
openModal('m-partner');
}
async function editPartner(id){
const d = await api('/partneri?');
const r = d.rows.find(x=>x.id===id);
if(!r) return;
document.getElementById('p-naziv').value=r.naziv||'';
document.getElementById('p-oib').value=r.oib||'';
document.getElementById('p-vrsta').value=r.vrsta;
document.getElementById('p-iban').value=r.iban||'';
document.getElementById('p-adresa').value=r.adresa||'';
document.getElementById('p-grad').value=r.grad||'';
document.getElementById('p-email').value=r.email||'';
document.getElementById('p-telefon').value=r.telefon||'';
document.getElementById('m-partner').dataset.id=id;
openModal('m-partner');
}
async function savePartner(){
const body = {
naziv: document.getElementById('p-naziv').value.trim(),
oib: document.getElementById('p-oib').value.trim() || null,
vrsta: document.getElementById('p-vrsta').value,
iban: document.getElementById('p-iban').value.trim() || null,
adresa: document.getElementById('p-adresa').value || null,
grad: document.getElementById('p-grad').value || null,
email: document.getElementById('p-email').value || null,
telefon: document.getElementById('p-telefon').value || null,
};
const id = document.getElementById('m-partner').dataset.id;
try {
if(id) await api('/partneri/'+id, { method:'PUT', body: JSON.stringify(body) });
else await api('/partneri', { method:'POST', body: JSON.stringify(body) });
closeModal('m-partner'); loadPartneri();
} catch(e){ alert(e.message); }
}
async function loadPartnerSaldo(id){
const d = await api('/partneri/'+id+'/saldo');
alert(`Partner saldo: ${fmt(d.info?.saldo||0)} EUR\nDuguje: ${fmt(d.info?.uk_duguje||0)}\nPotražuje: ${fmt(d.info?.uk_potrazuje||0)}\nBroj stavki: ${d.stavke.length}`);
}
// ===== DNEVNIK =====
async function loadDnevnik(){
const g = document.getElementById('dnev-godina').value;
const t = document.getElementById('dnev-tip').value;
const p = new URLSearchParams();
if(g) p.set('godina', g);
if(t) p.set('dokument_tip', t);
const d = await api('/dnevnik?'+p.toString());
const tbody = document.querySelector('#dnev-tbl tbody');
tbody.innerHTML = d.rows.map(r=>`<tr onclick="dnevDetail(${r.id})">
<td>${r.redni_broj||r.id}</td><td>${r.datum}</td><td>${r.opis||''}</td>
<td>${r.dokument_tip||''}</td><td class="num">${r.broj_stavki}</td>
<td class="num">${fmt(r.uk_duguje)}</td>
<td><button class="btn sec" onclick="event.stopPropagation();dnevStorno(${r.id})">↺ Storno</button></td>
</tr>`).join('');
}
async function dnevDetail(id){
const d = await api('/dnevnik/'+id);
let html = `<b>${d.head.opis||''}</b> · ${d.head.datum} · #${d.head.id}\n\nSTAVKE:\n`;
d.stavke.forEach(s => html += `${s.konto_sifra} ${s.konto_naziv}: D=${fmt(s.duguje)} P=${fmt(s.potrazuje)}\n`);
alert(html);
}
async function dnevStorno(id){
if(!confirm('Sigurno storno za zapis #'+id+'?')) return;
try {
await api('/dnevnik/'+id+'/storno', { method:'POST' });
loadDnevnik();
} catch(e){ alert(e.message); }
}
let kontoCache = [];
async function loadKontoCache(){
if(kontoCache.length) return kontoCache;
const d = await api('/kontni-plan?aktivan=true');
kontoCache = d.rows;
return kontoCache;
}
async function openDnevnikModal(){
await loadKontoCache();
document.getElementById('d-datum').value = new Date().toISOString().slice(0,10);
document.getElementById('d-opis').value = '';
document.getElementById('d-tip').value = 'rucno';
document.getElementById('d-lines').innerHTML = '';
addDnevLine(); addDnevLine();
updateDnevBalans();
openModal('m-dnev');
}
function addDnevLine(){
const opts = kontoCache.map(k=>`<option value="${k.id}">${k.sifra}${k.naziv}</option>`).join('');
const div = document.createElement('div');
div.className = 'dnev-line-row';
div.innerHTML = `
<select class="d-konto">${opts}</select>
<input class="d-opis" placeholder="opis stavke">
<input type="number" step="0.01" class="d-duguje" placeholder="Duguje" oninput="updateDnevBalans()">
<input type="number" step="0.01" class="d-potrazuje" placeholder="Potražuje" oninput="updateDnevBalans()">
<input type="number" class="d-partner" placeholder="Partner ID (opc)">
<button class="btn red" onclick="this.parentElement.remove();updateDnevBalans()">×</button>`;
document.getElementById('d-lines').appendChild(div);
}
function updateDnevBalans(){
const rows = document.querySelectorAll('#d-lines .dnev-line-row');
let d=0,p=0;
rows.forEach(r => {
d += parseFloat(r.querySelector('.d-duguje').value)||0;
p += parseFloat(r.querySelector('.d-potrazuje').value)||0;
});
const el = document.getElementById('d-balans');
el.textContent = `Duguje: ${fmt(d)} · Potražuje: ${fmt(p)} · Razlika: ${fmt(d-p)}`;
el.className = 'dnev-balans ' + (d===p && d>0 ? 'ok' : 'bad');
}
async function saveDnev(){
const stavke = Array.from(document.querySelectorAll('#d-lines .dnev-line-row')).map(r => ({
konto_id: parseInt(r.querySelector('.d-konto').value),
opis: r.querySelector('.d-opis').value,
duguje: parseFloat(r.querySelector('.d-duguje').value)||0,
potrazuje: parseFloat(r.querySelector('.d-potrazuje').value)||0,
partner_id: parseInt(r.querySelector('.d-partner').value)||null
}));
const body = {
datum: document.getElementById('d-datum').value,
opis: document.getElementById('d-opis').value,
dokument_tip: document.getElementById('d-tip').value,
stavke
};
try {
await api('/dnevnik', { method:'POST', body: JSON.stringify(body) });
closeModal('m-dnev'); loadDnevnik();
} catch(e){ alert(e.message); }
}
// ===== GLAVNA KNJIGA =====
async function loadGlavnaKnjiga(){
const k = document.getElementById('gk-klasa').value;
const p = new URLSearchParams();
if(k) p.set('klasa', k);
const d = await api('/glavna-knjiga?'+p.toString());
const tbody = document.querySelector('#gk-tbl tbody');
tbody.innerHTML = d.rows.map(r=>`<tr>
<td><b>${r.sifra}</b></td><td>${r.naziv}</td><td>${r.klasa}</td><td>${r.vrsta}</td>
<td class="num">${fmt(r.sum_duguje)}</td>
<td class="num">${fmt(r.sum_potrazuje)}</td>
<td class="num"><b>${fmt(r.saldo)}</b></td>
<td class="num">${r.broj_stavki}</td>
</tr>`).join('');
}
// ===== RAČUNI =====
let partnerCache = [];
async function loadPartnerCache(){
if(partnerCache.length) return partnerCache;
const d = await api('/partneri?');
partnerCache = d.rows;
return partnerCache;
}
async function loadRacuni(){
const tip = document.getElementById('rac-tip').value;
const status = document.getElementById('rac-status').value;
const godina = document.getElementById('rac-godina').value;
const p = new URLSearchParams();
if(status) p.set('status', status);
if(godina) p.set('godina', godina);
const d = await api('/racuni/'+tip+'?'+p.toString());
const tbody = document.querySelector('#rac-tbl tbody');
tbody.innerHTML = d.rows.map(r=>`<tr onclick="racDetail('${tip}',${r.id})">
<td>${r.id}</td><td>${r.broj||''}</td><td>${r.datum_izdavanja}</td>
<td>${r.partner_naziv||''}</td><td>${r.partner_oib||''}</td>
<td class="num">${fmt(r.iznos_neto)}</td>
<td class="num">${fmt(r.iznos_pdv)}</td>
<td class="num"><b>${fmt(r.iznos_brutto)}</b></td>
<td><span class="badge ${r.status}">${r.status}</span></td>
<td>${r.status==='nacrt'?`<button class="btn green" onclick="event.stopPropagation();knjizi('${tip}',${r.id})">Knjiži</button>`:''}</td>
</tr>`).join('');
}
async function knjizi(tip, id){
if(!confirm('Knjižiti račun #'+id+' u dnevnik?')) return;
try{ await api('/racuni/'+tip+'/'+id+'/knjizi', {method:'POST'}); loadRacuni(); }
catch(e){ alert(e.message); }
}
async function racDetail(tip, id){
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();
document.getElementById('m-rac').dataset.tip = tip;
document.getElementById('m-rac-title').textContent = 'Novi ' + tip + ' račun';
document.getElementById('r-broj').value='';
document.getElementById('r-datum').value = new Date().toISOString().slice(0,10);
document.getElementById('r-dospjece').value = '';
document.getElementById('r-status').value = 'nacrt';
document.getElementById('r-partner').innerHTML = partnerCache.map(p=>`<option value="${p.id}">${p.naziv}${p.oib?' · '+p.oib:''}</option>`).join('');
document.getElementById('r-lines').innerHTML='';
addRacLine();
updateRacSummary();
openModal('m-rac');
}
function addRacLine(){
const div = document.createElement('div');
div.className = 'dnev-line-row';
div.style.gridTemplateColumns = '1fr 60px 80px 80px 60px 30px';
div.innerHTML = `
<input class="r-naziv" placeholder="Naziv stavke">
<input type="number" step="0.01" class="r-kol" value="1" oninput="updateRacSummary()">
<input type="number" step="0.01" class="r-cij" placeholder="Cijena" oninput="updateRacSummary()">
<input type="number" step="0.01" class="r-pop" value="0" placeholder="Popust %" oninput="updateRacSummary()">
<input type="number" step="0.01" class="r-pdv" value="25" placeholder="PDV %" oninput="updateRacSummary()">
<button class="btn red" onclick="this.parentElement.remove();updateRacSummary()">×</button>`;
document.getElementById('r-lines').appendChild(div);
}
function updateRacSummary(){
const rows = document.querySelectorAll('#r-lines .dnev-line-row');
let neto=0, pdv=0;
rows.forEach(r => {
const k = parseFloat(r.querySelector('.r-kol').value)||0;
const c = parseFloat(r.querySelector('.r-cij').value)||0;
const pop = parseFloat(r.querySelector('.r-pop').value)||0;
const ppdv = parseFloat(r.querySelector('.r-pdv').value)||0;
const n = k*c*(1-pop/100);
neto += n;
pdv += n*ppdv/100;
});
document.getElementById('r-summary').textContent = `Neto: ${fmt(neto)} · PDV: ${fmt(pdv)} · Brutto: ${fmt(neto+pdv)}`;
}
async function saveRac(){
const tip = document.getElementById('m-rac').dataset.tip;
const stavke = Array.from(document.querySelectorAll('#r-lines .dnev-line-row')).map(r => ({
naziv: r.querySelector('.r-naziv').value,
kolicina: parseFloat(r.querySelector('.r-kol').value)||1,
cijena_jed: parseFloat(r.querySelector('.r-cij').value)||0,
popust_pct: parseFloat(r.querySelector('.r-pop').value)||0,
pdv_pct: parseFloat(r.querySelector('.r-pdv').value)||25,
}));
const body = {
broj: document.getElementById('r-broj').value || null,
partner_id: parseInt(document.getElementById('r-partner').value),
datum_izdavanja: document.getElementById('r-datum').value,
datum_dospjeca: document.getElementById('r-dospjece').value || null,
status: document.getElementById('r-status').value,
stavke
};
try {
await api('/racuni/'+tip, { method:'POST', body: JSON.stringify(body) });
closeModal('m-rac');
loadRacuni();
} catch(e){ alert(e.message); }
}
async function importERacun(){
const f = document.getElementById('eracun-file').files[0];
if(!f) return;
const fd = new FormData();
fd.append('file', f);
const r = await fetch(API+'/racuni/eracun-import', { method:'POST', headers: AUTH(), body: fd });
const d = await r.json();
alert(r.ok ? `e-Račun importiran:\nBroj: ${d.broj}\nNeto: ${fmt(d.neto)} PDV: ${fmt(d.pdv)} Brutto: ${fmt(d.brutto)}` : 'Greška: '+JSON.stringify(d));
document.getElementById('eracun-file').value='';
loadRacuni();
}
// ===== PDV =====
async function loadPdv(){
const g = document.getElementById('pdv-godina').value;
const m = document.getElementById('pdv-mjesec').value;
const params = new URLSearchParams({ godina:g });
if(m) params.set('mjesec', m);
const [u,i,o] = await Promise.all([
api('/pdv/knjiga-u?'+params.toString()),
api('/pdv/knjiga-i?'+params.toString()),
api('/pdv/obrazac?'+params.toString())
]);
document.querySelector('#pdv-u-tbl tbody').innerHTML = u.rows.map(r=>`<tr><td>${r.broj||''}</td><td>${r.datum_izdavanja}</td><td>${r.partner_naziv||''}</td><td>${r.partner_oib||''}</td><td class="num">${fmt(r.iznos_neto)}</td><td class="num">${fmt(r.iznos_pdv)}</td></tr>`).join('');
document.querySelector('#pdv-i-tbl tbody').innerHTML = i.rows.map(r=>`<tr><td>${r.broj||''}</td><td>${r.datum_izdavanja}</td><td>${r.partner_naziv||''}</td><td>${r.partner_oib||''}</td><td class="num">${fmt(r.iznos_neto)}</td><td class="num">${fmt(r.iznos_pdv)}</td></tr>`).join('');
document.getElementById('pdv-summary').innerHTML = `
<div class="kpi"><div class="kpi-l">Pretporez (U)</div><div class="kpi-v">${fmt(o.ulazni.pdv)} €</div></div>
<div class="kpi"><div class="kpi-l">PDV obveza (I)</div><div class="kpi-v">${fmt(o.izlazni.pdv)} €</div></div>
<div class="kpi ${o.obveza_za_uplatu>0?'r':'g'}"><div class="kpi-l">Obveza za uplatu</div><div class="kpi-v">${fmt(o.obveza_za_uplatu)} €</div></div>
<div class="kpi g"><div class="kpi-l">Pretporez za povrat</div><div class="kpi-v">${fmt(o.pretporez_za_povrat)} €</div></div>`;
}
// ===== ZAPOSLENICI + PLAĆE =====
async function loadZap(){
const d = await api('/zaposlenici');
document.querySelector('#zap-tbl tbody').innerHTML = d.rows.map(r=>`<tr>
<td>${r.id}</td><td>${r.oib||'—'}</td><td>${r.ime}</td><td>${r.prezime}</td>
<td>${r.klub_naziv||'—'}</td><td>${r.radno_mjesto||''}</td>
<td class="num">${fmt(r.plata_bruto)}</td><td>${r.aktivan?'✓':'✗'}</td>
</tr>`).join('');
}
async function loadPlace(){
const g = document.getElementById('pl-godina').value;
const m = document.getElementById('pl-mjesec').value;
const p = new URLSearchParams();
if(g)p.set('godina',g); if(m)p.set('mjesec',m);
const d = await api('/place/obracun?'+p.toString());
document.querySelector('#pl-tbl tbody').innerHTML = d.rows.map(r=>`<tr>
<td>${r.id}</td><td>${r.ime} ${r.prezime}</td><td>${r.godina}/${String(r.mjesec).padStart(2,'0')}</td>
<td class="num">${fmt(r.bruto)}</td>
<td class="num">${fmt(r.doprinosi_iz_plate)}</td>
<td class="num">${fmt(r.dohodnina)}</td>
<td class="num"><b>${fmt(r.neto)}</b></td>
<td class="num">${fmt(r.doprinosi_na_plate)}</td>
<td class="num">${fmt(r.ukupni_trosak)}</td>
</tr>`).join('');
}
function openZapModal(){
['z-ime','z-prezime','z-oib','z-klub','z-mjesto','z-bruto','z-iban'].forEach(i=>document.getElementById(i).value='');
openModal('m-zap');
}
async function saveZap(){
const body = {
ime: document.getElementById('z-ime').value,
prezime: document.getElementById('z-prezime').value,
oib: document.getElementById('z-oib').value || null,
klub_id: parseInt(document.getElementById('z-klub').value)||null,
radno_mjesto: document.getElementById('z-mjesto').value || null,
plata_bruto: parseFloat(document.getElementById('z-bruto').value)||0,
iban: document.getElementById('z-iban').value || null,
aktivan: true
};
try { await api('/zaposlenici', { method:'POST', body: JSON.stringify(body) }); closeModal('m-zap'); loadZap(); }
catch(e){ alert(e.message); }
}
async function openPlacaModal(){
const d = await api('/zaposlenici');
document.getElementById('pl-zap').innerHTML = d.rows.map(r=>`<option value="${r.id}">${r.ime} ${r.prezime}</option>`).join('');
openModal('m-pl');
}
async function savePlaca(){
const body = {
zaposlenik_id: parseInt(document.getElementById('pl-zap').value),
godina: parseInt(document.getElementById('pl-god').value),
mjesec: parseInt(document.getElementById('pl-mj').value),
bruto: parseFloat(document.getElementById('pl-bruto').value) || null,
osobni_odbitak: parseFloat(document.getElementById('pl-odb').value)||600,
prirez_pct: parseFloat(document.getElementById('pl-prirez').value)||0,
datum_isplate: document.getElementById('pl-isplata').value || null,
knjizi: document.getElementById('pl-knjizi').checked
};
try {
const r = await api('/place/obracun', { method:'POST', body: JSON.stringify(body) });
alert(`Bruto: ${fmt(r.calc.bruto)}\nDoprinosi iz: ${fmt(r.calc.doprinosi_iz_plate)}\nDohodnina: ${fmt(r.calc.dohodnina)}\nNeto: ${fmt(r.calc.neto)}\nUkupni trošak: ${fmt(r.calc.ukupni_trosak)}`);
closeModal('m-pl'); loadPlace();
} catch(e){ alert(e.message); }
}
// ===== PRORAČUN =====
async function loadProracun(){
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="12" 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>
<td><a class="btn sec" href="/uploads/${r.file_path||''}" target="_blank">Otvori</a></td>
</tr>`).join('')
: `<tr><td colspan="12" style="color:var(--t2);text-align:center;padding:14px">Nema uploadova.</td></tr>`;
} catch(e) {
tbody.innerHTML = `<tr><td colspan="12" style="color:var(--red)">Greška: ${e.message}</td></tr>`;
}
}
// Upload new invoice file via multipart
async function uploadInvoiceFile(file){
if(!file) return;
const prog = document.getElementById('up-progress');
prog.textContent = 'Šaljem ' + file.name + ' (' + Math.round(file.size/1024) + ' KB)…';
try {
const fd = new FormData();
fd.append('file', file);
// Note: no Content-Type header — browser sets multipart boundary
const r = await fetch(API + '/invoice-uploads', {
method:'POST', body: fd, headers: AUTH()
});
if(!r.ok) throw new Error('HTTP ' + r.status + ' ' + (await r.text()));
const j = await r.json();
prog.textContent = '✓ Uploaded #' + j.id + ' (' + Math.round((j.file_size||0)/1024) + ' KB) — OCR pending.';
document.getElementById('up-file').value = '';
loadUploads();
} catch(e) {
prog.textContent = '✗ Greška: ' + e.message;
}
}
// Drag & drop on uploads card
document.addEventListener('DOMContentLoaded', () => {
const drop = document.getElementById('up-drop');
if(!drop) return;
['dragenter','dragover'].forEach(ev => drop.addEventListener(ev, e => {
e.preventDefault(); e.stopPropagation();
drop.style.background='var(--bg2)';
}));
['dragleave','drop'].forEach(ev => drop.addEventListener(ev, e => {
e.preventDefault(); e.stopPropagation();
drop.style.background='var(--bg3)';
}));
drop.addEventListener('drop', e => {
if(e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0]){
uploadInvoiceFile(e.dataTransfer.files[0]);
}
});
});
// ===== PUTNI NALOZI / EXPENSE REPORTS =====
function _pnStatusActions(r){
const id = r.id;
const s = r.status || 'draft';
const acts = [];
if (s === 'draft') {
acts.push(`<button class="btn green" title="Pošalji na odobrenje" onclick="event.stopPropagation();putniSetStatus(${id},'poslano')">Pošalji</button>`);
acts.push(`<button class="btn red" title="Odbij" onclick="event.stopPropagation();putniSetStatus(${id},'odbijeno')">Odbij</button>`);
} else if (s === 'poslano') {
acts.push(`<button class="btn green" title="Odobri" onclick="event.stopPropagation();putniSetStatus(${id},'odobreno')">Odobri</button>`);
acts.push(`<button class="btn red" title="Odbij" onclick="event.stopPropagation();putniSetStatus(${id},'odbijeno')">Odbij</button>`);
} else if (s === 'odobreno') {
acts.push(`<button class="btn gold" title="Označi kao isplaćeno" onclick="event.stopPropagation();putniSetStatus(${id},'isplaceno')">Isplati</button>`);
}
acts.push(`<button class="btn sec" title="Uredi" onclick="event.stopPropagation();editPutni(${id})">✎</button>`);
if (s === 'draft') {
acts.push(`<button class="btn red" title="Obriši" onclick="event.stopPropagation();deletePutni(${id})">×</button>`);
}
return acts.join(' ');
}
async function loadExpenseReports(){
const tbody = document.querySelector('#pn-tbl tbody');
tbody.innerHTML = `<tr><td colspan="10" 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 k = document.getElementById('pn-klub').value;
const p = new URLSearchParams();
if(t) p.set('report_type', t);
if(s) p.set('status', s);
if(g) p.set('godina', g);
if(k) p.set('klub_id', k);
const d = await api('/putni-nalozi?'+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_no||'—'}</td>
<td>${r.klub_naziv||r.klub_id||''}</td>
<td>${r.destination||''}</td>
<td>${r.date_from||''}</td>
<td>${r.date_to||''}</td>
<td class="num">${fmt(r.km_driven)}</td>
<td class="num"><b>${fmt(r.cost_total)}</b></td>
<td><span class="badge ${r.status||''}">${r.status||''}</span></td>
<td style="white-space:nowrap">${_pnStatusActions(r)}</td>
</tr>`).join('')
: `<tr><td colspan="10" style="color:var(--t2);text-align:center;padding:14px">Nema putnih naloga.</td></tr>`;
} catch(e) {
tbody.innerHTML = `<tr><td colspan="10" 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-nalozi/'+id);
const tb = document.querySelector('#pn-rac-tbl tbody');
const racuni = d.racuni || [];
tb.innerHTML = racuni.length
? racuni.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); }
}
function _pnSetForm(r){
const set = (id,v) => { const el = document.getElementById(id); if(el) el.value = (v==null?'':v); };
set('pn-f-klub', r.klub_id);
set('pn-f-user', r.user_id);
set('pn-f-clan', r.clan_id);
set('pn-f-type', r.report_type || 'sluzbeno_putovanje');
set('pn-f-no', r.report_no);
set('pn-f-dest', r.destination);
set('pn-f-purpose', r.purpose);
set('pn-f-from', (r.date_from||'').toString().slice(0,10));
set('pn-f-to', (r.date_to||'').toString().slice(0,10));
set('pn-f-vtype', r.vehicle_type);
set('pn-f-vplate', r.vehicle_plate);
set('pn-f-km', r.km_driven||0);
set('pn-f-kmrate', r.km_rate||0.42);
set('pn-f-tr', r.cost_transport||0);
set('pn-f-lo', r.cost_lodging||0);
set('pn-f-me', r.cost_meals||0);
set('pn-f-ot', r.cost_other||0);
set('pn-f-dnc', r.dnevnice_count||0);
set('pn-f-dna', r.dnevnice_amount||30.00);
set('pn-f-notes', r.notes);
recalcPutniTotal();
}
function recalcPutniTotal(){
const v = id => parseFloat(document.getElementById(id).value)||0;
const total = v('pn-f-km')*v('pn-f-kmrate') + v('pn-f-tr') + v('pn-f-lo')
+ v('pn-f-me') + v('pn-f-ot') + v('pn-f-dnc')*v('pn-f-dna');
document.getElementById('pn-f-total').textContent = `Ukupno: ${fmt(total)}`;
}
function openPutniModal(){
document.getElementById('m-pn-title').textContent = 'Novi putni nalog';
document.getElementById('m-pn').dataset.id = '';
_pnSetForm({
report_type: 'sluzbeno_putovanje',
date_from: new Date().toISOString().slice(0,10),
date_to: new Date().toISOString().slice(0,10),
km_driven: 0, km_rate: 0.42,
cost_transport: 0, cost_lodging: 0, cost_meals: 0, cost_other: 0,
dnevnice_count: 0, dnevnice_amount: 30.00,
});
openModal('m-pn');
}
async function editPutni(id){
try {
const d = await api('/putni-nalozi/'+id);
document.getElementById('m-pn-title').textContent = `Putni nalog #${id} (${d.head.status})`;
document.getElementById('m-pn').dataset.id = id;
_pnSetForm(d.head);
openModal('m-pn');
} catch(e){ alert('Greška: '+e.message); }
}
async function savePutni(){
const v = id => document.getElementById(id).value;
const num = id => { const x = parseFloat(v(id)); return isNaN(x)?0:x; };
const intOrNull = id => { const x = parseInt(v(id)); return isNaN(x)?null:x; };
const strOrNull = id => { const s = v(id).trim(); return s?s:null; };
const body = {
klub_id: intOrNull('pn-f-klub'),
user_id: intOrNull('pn-f-user'),
clan_id: intOrNull('pn-f-clan'),
report_type: v('pn-f-type') || 'sluzbeno_putovanje',
report_no: strOrNull('pn-f-no'),
destination: v('pn-f-dest').trim(),
purpose: v('pn-f-purpose').trim(),
date_from: v('pn-f-from'),
date_to: v('pn-f-to'),
vehicle_type: strOrNull('pn-f-vtype'),
vehicle_plate: strOrNull('pn-f-vplate'),
km_driven: num('pn-f-km'),
km_rate: num('pn-f-kmrate'),
cost_transport: num('pn-f-tr'),
cost_lodging: num('pn-f-lo'),
cost_meals: num('pn-f-me'),
cost_other: num('pn-f-ot'),
dnevnice_count: num('pn-f-dnc'),
dnevnice_amount: num('pn-f-dna'),
notes: strOrNull('pn-f-notes'),
};
if (!body.klub_id) { alert('Klub ID je obavezan.'); return; }
if (!body.destination) { alert('Destinacija je obavezna.'); return; }
if (!body.purpose) { alert('Svrha je obavezna.'); return; }
if (!body.date_from || !body.date_to) { alert('Datumi su obavezni.'); return; }
const id = document.getElementById('m-pn').dataset.id;
try {
if (id) {
await api('/putni-nalozi/'+id, { method:'PATCH', body: JSON.stringify(body) });
} else {
await api('/putni-nalozi', { method:'POST', body: JSON.stringify(body) });
}
closeModal('m-pn');
loadExpenseReports();
} catch(e){ alert('Greška: '+e.message); }
}
async function putniSetStatus(id, newStatus){
const labels = {poslano:'pošaljete na odobrenje', odobreno:'odobrite', odbijeno:'odbijete', isplaceno:'označite kao isplaćeno'};
if(!confirm(`Sigurno ${labels[newStatus]||'promijenite status'} za putni nalog #${id}?`)) return;
try {
await api('/putni-nalozi/'+id, { method:'PATCH', body: JSON.stringify({status:newStatus}) });
loadExpenseReports();
} catch(e){ alert('Greška: '+e.message); }
}
async function deletePutni(id){
if(!confirm(`Obriši putni nalog #${id}? (samo draft)`)) return;
try {
await api('/putni-nalozi/'+id, { method:'DELETE' });
loadExpenseReports();
} 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 =====
async function loadIzvjestaj(){
const tip = document.getElementById('iz-tip').value;
const g = document.getElementById('iz-godina').value;
const d = await api('/izvjestaji/'+tip+'?godina='+g);
const out = document.getElementById('iz-out');
if(tip==='bilanca'){
out.innerHTML = `
<div class="kpi-grid">
<div class="kpi g"><div class="kpi-l">Ukupno aktiva</div><div class="kpi-v">${fmt(d.ukupno_aktiva)} €</div></div>
<div class="kpi"><div class="kpi-l">Ukupno pasiva</div><div class="kpi-v">${fmt(d.ukupno_pasiva)} €</div></div>
<div class="kpi ${d.balans_ok?'g':'r'}"><div class="kpi-l">Balans</div><div class="kpi-v">${d.balans_ok?'✓ OK':'✗ neusklađen'}</div></div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
<div><h4 style="font-size:12px;color:var(--t2);margin-bottom:6px">AKTIVA</h4>
<div class="tbl-wrap"><table><thead><tr><th>Šifra</th><th>Naziv</th><th class="num">Saldo</th></tr></thead>
<tbody>${d.aktiva.map(r=>`<tr><td>${r.sifra}</td><td>${r.naziv}</td><td class="num">${fmt(r.saldo)}</td></tr>`).join('')}</tbody></table></div></div>
<div><h4 style="font-size:12px;color:var(--t2);margin-bottom:6px">PASIVA</h4>
<div class="tbl-wrap"><table><thead><tr><th>Šifra</th><th>Naziv</th><th class="num">Saldo</th></tr></thead>
<tbody>${d.pasiva.map(r=>`<tr><td>${r.sifra}</td><td>${r.naziv}</td><td class="num">${fmt(r.saldo)}</td></tr>`).join('')}</tbody></table></div></div>
</div>`;
} else if(tip==='pnl'){
out.innerHTML = `
<div class="kpi-grid">
<div class="kpi g"><div class="kpi-l">Prihodi</div><div class="kpi-v">${fmt(d.ukupno_prihodi)} €</div></div>
<div class="kpi r"><div class="kpi-l">Rashodi</div><div class="kpi-v">${fmt(d.ukupno_rashodi)} €</div></div>
<div class="kpi ${d.rezultat>=0?'g':'r'}"><div class="kpi-l">Rezultat (${d.tip_rezultata})</div><div class="kpi-v">${fmt(d.rezultat)} €</div></div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
<div><h4 style="font-size:12px;color:var(--t2);margin-bottom:6px">PRIHODI</h4>
<div class="tbl-wrap"><table><thead><tr><th>Šifra</th><th>Naziv</th><th class="num">Iznos</th></tr></thead>
<tbody>${d.prihodi.map(r=>`<tr><td>${r.sifra}</td><td>${r.naziv}</td><td class="num">${fmt(r.iznos)}</td></tr>`).join('')}</tbody></table></div></div>
<div><h4 style="font-size:12px;color:var(--t2);margin-bottom:6px">RASHODI</h4>
<div class="tbl-wrap"><table><thead><tr><th>Šifra</th><th>Naziv</th><th class="num">Iznos</th></tr></thead>
<tbody>${d.rashodi.map(r=>`<tr><td>${r.sifra}</td><td>${r.naziv}</td><td class="num">${fmt(r.iznos)}</td></tr>`).join('')}</tbody></table></div></div>
</div>`;
} else {
out.innerHTML = `
<div class="tbl-wrap"><table><thead><tr><th>Mjesec</th><th class="num">Uplate</th><th class="num">Isplate</th><th class="num">Net</th></tr></thead>
<tbody>${d.po_mjesecu.map(r=>`<tr><td>${r.mjesec}</td><td class="num">${fmt(r.uplate)}</td><td class="num">${fmt(r.isplate)}</td><td class="num"><b>${fmt(Number(r.uplate)-Number(r.isplate))}</b></td></tr>`).join('')}</tbody></table></div>`;
}
}
function exportXlsx(report, godina, mjesec){
let url = API+'/export/xlsx/'+report+'?godina='+godina;
if(mjesec) url += '&mjesec='+mjesec;
window.open(url, '_blank');
}
function exportPdf(report, godina){
window.open(API+'/export/pdf/'+report+'?godina='+godina, '_blank');
}
// Lazy loaders per panel
const loaders = {
dnevnik: loadDnevnik,
glavna: loadGlavnaKnjiga,
partneri: loadPartneri,
racuni: loadRacuni,
uploads: loadUploads,
putni: loadExpenseReports,
payments: loadPayments,
pdv: loadPdv,
place: () => { loadZap(); loadPlace(); },
proracun: loadProracun,
izvjestaji: loadIzvjestaj,
kontni: loadKontniPlan
};
// Switch programmatically (used by deep links: ?tab=uploads / #tab=putni)
function activateTab(panelId){
const t = document.querySelector('.tab[data-panel="' + panelId + '"]');
if(!t) return false;
t.click();
return true;
}
// Initial
document.addEventListener('DOMContentLoaded', () => {
loadKontoCache();
loadPartnerCache();
// Deep-link support: ?tab=<panel> or #tab=<panel>
let target = null;
try {
const u = new URL(window.location.href);
target = u.searchParams.get('tab');
if(!target && u.hash){
const m = u.hash.match(/tab=([a-z]+)/i);
if(m) target = m[1];
}
} catch(e) {}
if(target && activateTab(target)){
// tab.click() already triggers loader
return;
}
loadDnevnik();
});
</script>
</body>
</html>