Fiskalizacija: podešavanja L-PFR veze, test konekcije (HTMX)
This commit is contained in:
@@ -277,6 +277,8 @@ func main() {
|
|||||||
r.Get("/admin/podesavanja/izgled", h.PodesavanjaIzgled)
|
r.Get("/admin/podesavanja/izgled", h.PodesavanjaIzgled)
|
||||||
r.Get("/admin/podesavanja/sistem", h.PodesavanjaSistem)
|
r.Get("/admin/podesavanja/sistem", h.PodesavanjaSistem)
|
||||||
r.Get("/admin/podesavanja/servis", h.PodesavanjaServis)
|
r.Get("/admin/podesavanja/servis", h.PodesavanjaServis)
|
||||||
|
r.Get("/admin/podesavanja/fiskalizacija", h.PodesavanjaFiskalizacija)
|
||||||
|
r.Get("/podesavanja/fiskalizacija/test", h.TestFiskalizacije)
|
||||||
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)
|
||||||
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/{id}/izmeni", h.IzmeniPdvStopu)
|
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/{id}/izmeni", h.IzmeniPdvStopu)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ var saSidebar = []string{
|
|||||||
"klijenti", "klijent_forma",
|
"klijenti", "klijent_forma",
|
||||||
"magacin", "magacin_forma", "magacin_kartica",
|
"magacin", "magacin_forma", "magacin_kartica",
|
||||||
"nabavke", "nabavka_forma", "nabavka_detalji",
|
"nabavke", "nabavka_forma", "nabavka_detalji",
|
||||||
"podesavanja", "podesavanja_opste", "podesavanja_izgled", "podesavanja_sistem", "podesavanja_servis",
|
"podesavanja", "podesavanja_opste", "podesavanja_izgled", "podesavanja_sistem", "podesavanja_servis", "podesavanja_fiskalizacija",
|
||||||
"pdv_stope",
|
"pdv_stope",
|
||||||
"pdv_kir", "pdv_kir_forma",
|
"pdv_kir", "pdv_kir_forma",
|
||||||
"pdv_kpr", "pdv_kpr_forma",
|
"pdv_kpr", "pdv_kpr_forma",
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
ntechsqlite "ntech/internal/db/sqlite"
|
ntechsqlite "ntech/internal/db/sqlite"
|
||||||
"ntech/internal/middleware"
|
"ntech/internal/middleware"
|
||||||
"ntech/internal/model"
|
"ntech/internal/model"
|
||||||
@@ -69,6 +71,8 @@ type PodaciPodesavanja struct {
|
|||||||
ServisUslovi string
|
ServisUslovi string
|
||||||
ServisKlauzulaPredracuna string
|
ServisKlauzulaPredracuna string
|
||||||
QrBazniUrl string
|
QrBazniUrl string
|
||||||
|
PfrUrl string
|
||||||
|
PfrTip string
|
||||||
LoginPozadina string
|
LoginPozadina string
|
||||||
LoginPozadinaOpacity string
|
LoginPozadinaOpacity string
|
||||||
LoginPozadinaBlurPozadine string
|
LoginPozadinaBlurPozadine string
|
||||||
@@ -324,6 +328,9 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
|||||||
"firma_pdv_obveznik": r.FormValue("firma_pdv_obveznik"),
|
"firma_pdv_obveznik": r.FormValue("firma_pdv_obveznik"),
|
||||||
"firma_fiskalizacija": r.FormValue("firma_fiskalizacija"),
|
"firma_fiskalizacija": r.FormValue("firma_fiskalizacija"),
|
||||||
"firma_rezim": r.FormValue("firma_rezim"),
|
"firma_rezim": r.FormValue("firma_rezim"),
|
||||||
|
// fiskalizacija — L-PFR podešavanja
|
||||||
|
"pfr_url": r.FormValue("pfr_url"),
|
||||||
|
"pfr_tip": r.FormValue("pfr_tip"),
|
||||||
}
|
}
|
||||||
|
|
||||||
for kljuc, vrednost := range polja {
|
for kljuc, vrednost := range polja {
|
||||||
@@ -342,6 +349,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
|||||||
"/admin/podesavanja/sistem": "/admin/podesavanja/sistem",
|
"/admin/podesavanja/sistem": "/admin/podesavanja/sistem",
|
||||||
"/admin/podesavanja/servis": "/admin/podesavanja/servis",
|
"/admin/podesavanja/servis": "/admin/podesavanja/servis",
|
||||||
"/admin/podesavanja/kalkulacija-pdv": "/admin/podesavanja/kalkulacija-pdv",
|
"/admin/podesavanja/kalkulacija-pdv": "/admin/podesavanja/kalkulacija-pdv",
|
||||||
|
"/admin/podesavanja/fiskalizacija": "/admin/podesavanja/fiskalizacija",
|
||||||
"/podesavanja": "/podesavanja",
|
"/podesavanja": "/podesavanja",
|
||||||
}
|
}
|
||||||
sledeci := "/podesavanja"
|
sledeci := "/podesavanja"
|
||||||
@@ -852,6 +860,8 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
|
|||||||
ServisUslovi: vrednostIliDefault(podesavanja, "servis_uslovi", podrazumevaniUsloviServisa),
|
ServisUslovi: vrednostIliDefault(podesavanja, "servis_uslovi", podrazumevaniUsloviServisa),
|
||||||
ServisKlauzulaPredracuna: vrednostIliDefault(podesavanja, "servis_klauzula_predracuna", podrazumevanaKlauzulaPredracuna),
|
ServisKlauzulaPredracuna: vrednostIliDefault(podesavanja, "servis_klauzula_predracuna", podrazumevanaKlauzulaPredracuna),
|
||||||
QrBazniUrl: podesavanja["qr_bazni_url"],
|
QrBazniUrl: podesavanja["qr_bazni_url"],
|
||||||
|
PfrUrl: vrednostIliDefault(podesavanja, "pfr_url", "http://127.0.0.1:4566"),
|
||||||
|
PfrTip: vrednostIliDefault(podesavanja, "pfr_tip", "teron"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,6 +893,59 @@ func (h *Handler) PodesavanjaIzgled(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.renderujTemplate(w, "podesavanja_izgled", podaci)
|
h.renderujTemplate(w, "podesavanja_izgled", podaci)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PodesavanjaFiskalizacija renderuje stranicu sa podešavanjima L-PFR veze
|
||||||
|
func (h *Handler) PodesavanjaFiskalizacija(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.pregled"); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
podaci, err := h.napuniPodaciPodesavanja(r, "Podešavanja — Fiskalizacija")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
podaci.Stranica = "podesavanja-fiskalizacija"
|
||||||
|
h.renderujTemplate(w, "podesavanja_fiskalizacija", podaci)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFiskalizacije udara /api/status na konfigurisanom PFR URL-u i vraća HTMX fragment
|
||||||
|
func (h *Handler) TestFiskalizacije(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.pregled"); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Greška", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pfrURL := vrednostIliDefault(podesavanja, "pfr_url", "http://127.0.0.1:4566")
|
||||||
|
|
||||||
|
klijent := &http.Client{Timeout: 5 * time.Second}
|
||||||
|
resp, err := klijent.Get(pfrURL + "/api/status")
|
||||||
|
if err != nil {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
fmt.Fprintf(w, `<div class="fisk-status greska">✗ Nije dostupan — %s</div>`, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var status map[string]any
|
||||||
|
_ = json.NewDecoder(resp.Body).Decode(&status)
|
||||||
|
|
||||||
|
sdcDateTime, _ := status["sdcDateTime"].(string)
|
||||||
|
lastInvoice, _ := status["lastInvoiceNumber"].(string)
|
||||||
|
tin, _ := status["tin"].(string)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
fmt.Fprintf(w, `<div class="fisk-status uspeh">
|
||||||
|
✓ Povezano — %s
|
||||||
|
<div class="fisk-status-detalji">
|
||||||
|
<span>TIN: %s</span>
|
||||||
|
<span>Poslednji račun: %s</span>
|
||||||
|
<span>Vreme: %s</span>
|
||||||
|
</div>
|
||||||
|
</div>`, pfrURL, tin, lastInvoice, sdcDateTime)
|
||||||
|
}
|
||||||
|
|
||||||
// PodesavanjaSistem renderuje stranicu sa sistemskim podešavanjima (backup)
|
// PodesavanjaSistem renderuje stranicu sa sistemskim podešavanjima (backup)
|
||||||
func (h *Handler) PodesavanjaSistem(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) PodesavanjaSistem(w http.ResponseWriter, r *http.Request) {
|
||||||
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.pregled"); !ok {
|
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.pregled"); !ok {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- podešavanja L-PFR veze (Teron i kompatibilni)
|
||||||
|
INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('pfr_url', 'http://127.0.0.1:4566');
|
||||||
|
INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('pfr_tip', 'teron');
|
||||||
@@ -211,7 +211,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="nav-tooltip">Podešavanja</span>
|
<span class="nav-tooltip">Podešavanja</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="nav-podmeni {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "podesavanja-servis") (eq .Stranica "dozvole")}}otvoren{{end}}">
|
<div class="nav-podmeni {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "podesavanja-servis") (eq .Stranica "podesavanja-fiskalizacija") (eq .Stranica "dozvole")}}otvoren{{end}}">
|
||||||
<a href="/admin/podesavanja/opste" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-opste"}}aktivan{{end}}">
|
<a href="/admin/podesavanja/opste" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-opste"}}aktivan{{end}}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V7a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14"/><path d="M9 21v-5h6v5"/><path d="M9 9h.01M15 9h.01M9 13h.01M15 13h.01"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V7a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14"/><path d="M9 21v-5h6v5"/><path d="M9 9h.01M15 9h.01M9 13h.01M15 13h.01"/></svg>
|
||||||
<span>Opšte</span>
|
<span>Opšte</span>
|
||||||
@@ -237,6 +237,11 @@
|
|||||||
<span>Servis</span>
|
<span>Servis</span>
|
||||||
<span class="nav-tooltip">Servis</span>
|
<span class="nav-tooltip">Servis</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/admin/podesavanja/fiskalizacija" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-fiskalizacija"}}aktivan{{end}}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/></svg>
|
||||||
|
<span>Fiskalizacija</span>
|
||||||
|
<span class="nav-tooltip">Fiskalizacija</span>
|
||||||
|
</a>
|
||||||
{{if or (eq .KorisnikUloga "superadmin") (eq .KorisnikUloga "admin")}}
|
{{if or (eq .KorisnikUloga "superadmin") (eq .KorisnikUloga "admin")}}
|
||||||
<a href="/admin/dozvole" class="nav-stavka nav-podstavka {{if eq .Stranica "dozvole"}}aktivan{{end}}">
|
<a href="/admin/dozvole" class="nav-stavka nav-podstavka {{if eq .Stranica "dozvole"}}aktivan{{end}}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
{{template "base" .}}
|
||||||
|
|
||||||
|
{{define "naslov"}}Podešavanja — Fiskalizacija — NTech{{end}}
|
||||||
|
|
||||||
|
{{define "sadrzaj"}}
|
||||||
|
<div class="stranica-stack" style="width:100%;max-width:100%;">
|
||||||
|
|
||||||
|
<div class="kartica animiraj">
|
||||||
|
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/></svg>
|
||||||
|
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">L-PFR veza</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" action="/podesavanja/sacuvaj">
|
||||||
|
<input type="hidden" name="_next" value="/admin/podesavanja/fiskalizacija">
|
||||||
|
|
||||||
|
<div style="display:flex;flex-direction:column;gap:16px;max-width:480px;">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="pfr_tip" class="polje-labela">Tip fiskalnog servera</label>
|
||||||
|
<select id="pfr_tip" name="pfr_tip"
|
||||||
|
style="width:100%;padding:8px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-glavni);font-size:14px;">
|
||||||
|
<option value="teron" {{if eq .PfrTip "teron"}}selected{{end}}>Teron L-PFR</option>
|
||||||
|
</select>
|
||||||
|
<div class="pomocni-tekst" style="margin-top:4px;">Trenutno podržan samo Teron L-PFR.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="pfr_url" class="polje-labela">URL fiskalnog servera</label>
|
||||||
|
<input type="text" id="pfr_url" name="pfr_url" value="{{.PfrUrl}}"
|
||||||
|
placeholder="http://127.0.0.1:4566"
|
||||||
|
style="width:100%;padding:8px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-glavni);font-size:14px;box-sizing:border-box;">
|
||||||
|
<div class="pomocni-tekst" style="margin-top:4px;">
|
||||||
|
Lokalno: <code>http://127.0.0.1:4566</code> ·
|
||||||
|
Docker: <code>http://teron-mock:4566</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
|
||||||
|
<button type="submit"
|
||||||
|
style="padding:9px 18px;background:var(--sb-akcent);border:none;border-radius:8px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;">
|
||||||
|
Sačuvaj
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
hx-get="/podesavanja/fiskalizacija/test"
|
||||||
|
hx-target="#fisk-test-rezultat"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
hx-indicator="#fisk-test-spinner"
|
||||||
|
style="padding:9px 18px;background:var(--kartica);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-sporedni);font-size:13px;cursor:pointer;">
|
||||||
|
Testiraj vezu
|
||||||
|
</button>
|
||||||
|
<span id="fisk-test-spinner" class="htmx-indicator" style="font-size:12px;color:var(--tekst-sporedni);">Proveravam...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="fisk-test-rezultat" style="margin-top:16px;"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fisk-status {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.fisk-status.uspeh {
|
||||||
|
background: color-mix(in srgb, var(--zelena, #22c55e) 12%, transparent);
|
||||||
|
color: var(--zelena, #22c55e);
|
||||||
|
border: 0.5px solid color-mix(in srgb, var(--zelena, #22c55e) 30%, transparent);
|
||||||
|
}
|
||||||
|
.fisk-status.greska {
|
||||||
|
background: color-mix(in srgb, var(--crvena, #ef4444) 12%, transparent);
|
||||||
|
color: var(--crvena, #ef4444);
|
||||||
|
border: 0.5px solid color-mix(in srgb, var(--crvena, #ef4444) 30%, transparent);
|
||||||
|
}
|
||||||
|
.fisk-status-detalji {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{{end}}
|
||||||
Reference in New Issue
Block a user