Fiskalni pazar: ispravan Content-Disposition header i data-full-reload na formama

This commit is contained in:
2026-07-02 20:19:20 +02:00
parent c82b17f9c1
commit 576d963f28
3 changed files with 21 additions and 4 deletions
+1 -1
View File
@@ -292,7 +292,7 @@ func main() {
r.Get("/podesavanja/fiskalizacija/be-status", h.BeStatus) r.Get("/podesavanja/fiskalizacija/be-status", h.BeStatus)
r.Post("/podesavanja/fiskalizacija/be-reset-audit", h.BeResetAudit) r.Post("/podesavanja/fiskalizacija/be-reset-audit", h.BeResetAudit)
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "fiskal.pazar")).Get("/fiskal/pazar", h.FiskalniPazar) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "fiskal.pazar")).Get("/fiskal/pazar", h.FiskalniPazar)
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "fiskal.pazar")).Post("/fiskal/pazar/izvestaj", h.FiskalniIzvestaj) r.With(doz("fiskal.pazar")).Post("/fiskal/pazar/izvestaj", h.FiskalniIzvestaj)
r.With(doz("fiskal.zakljucenje")).Post("/fiskal/pazar/zakljuci", h.ZakljuciFiskalniDan) r.With(doz("fiskal.zakljucenje")).Post("/fiskal/pazar/zakljuci", h.ZakljuciFiskalniDan)
r.Get("/admin/podesavanja/kalkulacija-pdv", h.PdvStope) r.Get("/admin/podesavanja/kalkulacija-pdv", h.PdvStope)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/dodaj", h.DodajPdvStopu) r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/dodaj", h.DodajPdvStopu)
+18 -1
View File
@@ -4,7 +4,9 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strings" "strings"
"unicode"
ntechsqlite "ntech/internal/db/sqlite" ntechsqlite "ntech/internal/db/sqlite"
"ntech/internal/fiskal" "ntech/internal/fiskal"
@@ -12,6 +14,21 @@ import (
"ntech/internal/model" "ntech/internal/model"
) )
// contentDispositionZaFajl gradi Content-Disposition header za dati naziv fajla.
// HTTP header vrednosti moraju biti ASCII — naziv sa slovima kao što su Š/Č/Ž
// (npr. iz PFR-a: "DNEVNI IZVEŠTAJ - ...pdf") bi inače napravio nevalidan header
// koji pregledač ne može da parsira. RFC 6266 filename* nosi pravo UTF-8 ime,
// dok filename (ASCII) služi kao fallback za starije klijente.
func contentDispositionZaFajl(ime string) string {
ascii := strings.Map(func(r rune) rune {
if r > unicode.MaxASCII {
return '_'
}
return r
}, ime)
return fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, ascii, url.PathEscape(ime))
}
// PodaciFiskalniPazar su podaci za stranicu dnevnog fiskalnog preseka (pazara). // PodaciFiskalniPazar su podaci za stranicu dnevnog fiskalnog preseka (pazara).
type PodaciFiskalniPazar struct { type PodaciFiskalniPazar struct {
model.PodaciStranice model.PodaciStranice
@@ -115,6 +132,6 @@ func (h *Handler) FiskalniIzvestaj(w http.ResponseWriter, r *http.Request) {
imeFajla = "dnevni-izvestaj.pdf" imeFajla = "dnevni-izvestaj.pdf"
} }
w.Header().Set("Content-Type", "application/pdf") w.Header().Set("Content-Type", "application/pdf")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, imeFajla)) w.Header().Set("Content-Disposition", contentDispositionZaFajl(imeFajla))
w.Write(sadrzaj) w.Write(sadrzaj)
} }
+2 -2
View File
@@ -76,13 +76,13 @@
<div style="display:flex;gap:10px;flex-wrap:wrap;padding-top:12px;border-top:0.5px solid var(--ivica);"> <div style="display:flex;gap:10px;flex-wrap:wrap;padding-top:12px;border-top:0.5px solid var(--ivica);">
<a href="/fiskal/pazar" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;">Osveži</a> <a href="/fiskal/pazar" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;">Osveži</a>
<form method="POST" action="/fiskal/pazar/izvestaj" style="display:inline;"> <form method="POST" action="/fiskal/pazar/izvestaj" style="display:inline;" data-full-reload>
<input type="hidden" name="_csrf" value="{{.CsrfToken}}"> <input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;">Preuzmi dnevni izveštaj</button> <button type="submit" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;">Preuzmi dnevni izveštaj</button>
</form> </form>
{{if index .Dozvole "fiskal.zakljucenje"}} {{if index .Dozvole "fiskal.zakljucenje"}}
<form method="POST" action="/fiskal/pazar/zakljuci" style="display:inline;" <form method="POST" action="/fiskal/pazar/zakljuci" style="display:inline;" data-full-reload
onsubmit="return confirm('Zaključiti dan? Ovo trajno svodi brojače prometa na nulu — radnja se ne može poništiti.')"> onsubmit="return confirm('Zaključiti dan? Ovo trajno svodi brojače prometa na nulu — radnja se ne može poništiti.')">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}"> <input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit" style="padding:8px 16px;background:#dc2626;border:none;border-radius:8px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;">Zaključi dan</button> <button type="submit" style="padding:8px 16px;background:#dc2626;border:none;border-radius:8px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;">Zaključi dan</button>