Storno kroz modal + ispravka about:blank u fiskalnom tabu Prodaje

- Servis i Prodaja: storniranje naloga sad ide kroz modal — dugme
  'Storniraj nalog' prvo traži potvrdu, tek onda otvara modal sa poljem za
  PIB/JMBG (kad je potrebno za refund); dugme 'Potvrdi storno' ostaje
  zaključano dok se polje ne popuni. Posle uspešnog storna modal se gasi i
  sadržaj stranice se osvežava (data-refresh=#sadrzaj).
- Dnevni pazar (Fiskalni presek): link u sidebaru i sve tri rute
  (FiskalniPazar, ZakljuciFiskalniDan, FiskalniIzvestaj) sad proveravaju
  modul fiskalizacije, ne samo RBAC dozvolu i tehničku dostupnost PFR-a.
- Prodaja: čekboks 'Prikaži fiskalni račun' se više ne nudi kad firma ne
  fiskalizuje, a pripremiFiskalniTab() više ne otvara prazan tab u tom
  slučaju — do sad je ostajao trajno na about:blank jer odredišna stranica
  nikad nije imala fiskalni račun da ga popuni. Dodata i odbrana za slučaj
  kad fiskalizacija jeste uključena ali PFR nije dostupan (zatvara osiroteli
  tab umesto about:blank).

Detalji u docs/Greške.md (van git-a).
This commit is contained in:
2026-07-03 02:22:13 +02:00
parent c87dc67857
commit edba23ae50
6 changed files with 157 additions and 25 deletions
+63 -11
View File
@@ -252,18 +252,58 @@
Storno vraća sve artikle na stanje u magacinu. Nalog ostaje u evidenciji sa oznakom storniran.
</div>
</div>
<form method="POST" action="/prodaja/storno/{{.Nalog.ID}}" style="display:flex;flex-direction:column;gap:8px;align-items:flex-end;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<input type="text" name="razlog" placeholder="Razlog storniranja (opciono)" style="width:240px;">
{{if .PotrebnaIdentKupca}}
<input type="text" name="poreski_broj_kupca" placeholder="PIB ili JMBG kupca (obavezno za refundaciju)" required style="width:240px;">
{{end}}
<button type="submit" class="btn-upozorenje"
data-potvrda="Stornirati nalog {{.Nalog.BrojNaloga}}?">
Storniraj nalog
</button>
</form>
<button type="button" class="btn-upozorenje" onclick="ntechPotvrdiPaOtvoriStorno(this, 'Stornirati nalog {{.Nalog.BrojNaloga}}?')">
Storniraj nalog
</button>
<dialog class="premesti-modal" onclick="if(event.target===this)this.close()">
<form method="dialog" class="premesti-zaglavlje">
<h3>Storniranje naloga {{.Nalog.BrojNaloga}}</h3>
<button type="submit" class="premesti-zatvori" aria-label="Zatvori">&times;</button>
</form>
<div style="display:flex;flex-direction:column;gap:10px;padding:16px;">
<form method="POST" action="/prodaja/storno/{{.Nalog.ID}}" data-refresh="#sadrzaj" style="display:flex;flex-direction:column;gap:10px;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<div>
<label class="polje-labela">Razlog storniranja (opciono)</label>
<input type="text" name="razlog" placeholder="Razlog storniranja" style="width:100%;padding:8px 12px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
</div>
{{if .PotrebnaIdentKupca}}
<div>
<label class="polje-labela">PIB ili JMBG kupca</label>
<input type="text" id="storno-pib" name="poreski_broj_kupca" placeholder="Obavezno za fiskalni refund" required
style="width:100%;padding:8px 12px;border:1px solid #dc2626;border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
</div>
{{end}}
<button type="submit" id="btn-storno-potvrdi" class="btn-upozorenje" style="width:100%;{{if .PotrebnaIdentKupca}}opacity:0.4;cursor:not-allowed;{{end}}" {{if .PotrebnaIdentKupca}}disabled{{end}}>
Potvrdi storno
</button>
</form>
</div>
</dialog>
</div>
<script>
(function(){
// isti obrazac kao u Servisu (servis_detalji.html) — v. komentar tamo zašto
// se NE koristi ntechOtvoriModal (auto-submit na zatvaranje dialoga).
window.ntechPotvrdiPaOtvoriStorno = function (btn, poruka) {
window._ntechPotvrdi(poruka).then(function (ok) {
if (!ok) return;
var d = btn.nextElementSibling;
if (d && d.tagName === 'DIALOG') d.showModal();
});
};
var pib = document.getElementById('storno-pib');
var btnPotvrdi = document.getElementById('btn-storno-potvrdi');
if (pib && btnPotvrdi) {
pib.addEventListener('input', function () {
var ok = pib.value.trim() !== '';
btnPotvrdi.disabled = !ok;
btnPotvrdi.style.opacity = ok ? '1' : '0.4';
btnPotvrdi.style.cursor = ok ? 'pointer' : 'not-allowed';
});
}
})();
</script>
{{end}}
{{if .FiskalniRacun}}
@@ -296,6 +336,18 @@
}
})();
</script>
{{else}}
<script>
(function(){
// fiskalizacija je pokušana (racun=1 tražen pri naplati) ali računa nema —
// PFR nedostupan ili slično (v. FiskalGreska ispod). Prazan tab je već otvoren
// pre redirekta — zatvori ga umesto da ostane trajno na about:blank.
if (new URLSearchParams(location.search).get('racun') === '1') {
var t = window.open('', 'ntech-fiskalni-tab');
if (t) t.close();
}
})();
</script>
{{end}}
{{if .FiskalGreska}}