diff --git a/internal/handler/servis.go b/internal/handler/servis.go index ac7836d..2d1b62f 100644 --- a/internal/handler/servis.go +++ b/internal/handler/servis.go @@ -85,7 +85,8 @@ type PodaciDetaljiNaloga struct { CenaDijagnostikePredlog string // podrazumevana cena dijagnostike iz podešavanja (za prefill input-a) KorisceneUsluge map[int64]bool // ID-evi usluga već dodatih na nalog — izostavljaju se iz dropdown-a SviStatusi []string - FiskalniRacun *model.FiskalniRacun // nil ako nije fiskalizovano + FiskalniRacun *model.FiskalniRacun // konačan (Normal/Sale) ako postoji, inače najnoviji izdat; nil ako nije fiskalizovano + PovracajAvansa *model.FiskalniRacun // avansni povraćaj (Advance/Refund) — kad je avans premašio konačnu cenu RokPodizanja *time.Time // DatumZavrsetka + 30 dana; nil dok nije završeno FiskalGreska bool // true: fiskalizacija aktivna, status Preuzeto, ali nema fiskalnog računa PotrebnaIdentKupca bool // true: refundacija zahteva ručni unos PIB/JMBG kupca (nema ga iz klijenta) @@ -891,11 +892,20 @@ func (h *Handler) DetaljiNaloga(w http.ResponseWriter, r *http.Request) { RokPodizanja: rokPodizanja(nalog.DatumZavrsetka), } - // učitaj fiskalni račun ako postoji (za prikaz u detaljima) - if fr, err := h.FiskalRepo.DohvatiPoServisu(r.Context(), id); fr != nil { - podaci.FiskalniRacun = fr - } else if err != nil { + // učitaj fiskalni račun ako postoji (za prikaz u detaljima) — preferira konačan + // (Normal/Sale) nad bilo kojim drugim, jer avansni povraćaj viška (upisan POSLE + // konačnog računa, kad avans premaši cenu) ne sme da ga "prekrije" u prikazu + if fr, err := h.FiskalRepo.DohvatiPoServisuITip(r.Context(), id, "Normal", "Sale"); err != nil { slog.Error("greška pri učitavanju fiskalnog računa za servis", "servis_id", id, "error", err) + } else if fr != nil { + podaci.FiskalniRacun = fr + } else if fr2, err2 := h.FiskalRepo.DohvatiPoServisu(r.Context(), id); err2 != nil { + slog.Error("greška pri učitavanju fiskalnog računa za servis", "servis_id", id, "error", err2) + } else { + podaci.FiskalniRacun = fr2 + } + if fr3, err := h.FiskalRepo.DohvatiPoServisuITip(r.Context(), id, "Advance", "Refund"); err == nil { + podaci.PovracajAvansa = fr3 } // fiskalna greška: modul aktivan, nalog preuzet, ali nema KONAČNOG (Normal/Sale) // fiskalnog računa — avansni (Advance/Sale) se ne računa kao konačan, inače bi @@ -2812,28 +2822,40 @@ func (h *Handler) StampaFiskalnog(w http.ResponseWriter, r *http.Request) { http.Error(w, "Neispravan ID", http.StatusBadRequest) return } - fr, err := h.FiskalRepo.DohvatiPoServisu(r.Context(), id) - if err != nil || fr == nil { - http.Error(w, "Fiskalni račun nije pronađen", http.StatusNotFound) - return - } - nalog, _ := h.ServisRepo.DohvatiID(r.Context(), id) - journal := "" - if fr.SiroviOdgovor != "" { - var raw map[string]any - if json.Unmarshal([]byte(fr.SiroviOdgovor), &raw) == nil { - if j, ok := raw["journal"].(string); ok { - journal = j + var racuni []*model.FiskalniRacun + if r.URL.Query().Get("tip") == "povracaj" { + // avansni povraćaj viška — eksplicitno traženo (drugi dokument, ne konačan račun) + fr, err := h.FiskalRepo.DohvatiPoServisuITip(r.Context(), id, "Advance", "Refund") + if err != nil || fr == nil { + http.Error(w, "Fiskalni račun nije pronađen", http.StatusNotFound) + return + } + racuni = []*model.FiskalniRacun{fr} + } else { + // podrazumevano: konačan (Normal/Sale) račun — ako još ne postoji (npr. samo avans + // je fiskalizovan pre preuzimanja), padni nazad na najnoviji izdat + fr, err := h.FiskalRepo.DohvatiPoServisuITip(r.Context(), id, "Normal", "Sale") + if err != nil { + http.Error(w, "Fiskalni račun nije pronađen", http.StatusNotFound) + return + } + if fr == nil { + if fr, err = h.FiskalRepo.DohvatiPoServisu(r.Context(), id); err != nil || fr == nil { + http.Error(w, "Fiskalni račun nije pronađen", http.StatusNotFound) + return + } + } + racuni = append(racuni, fr) + // avans premašio cenu → povraćaj viška je DRUGI fiskalni dokument, izdat odmah + // posle konačnog računa — štampa se u istom prozoru (izbegava se drugi popup, + // koji brauzer bez pravog user-gesture-a često blokira) + if fr.TipRacuna == "Normal" { + if povracaj, err := h.FiskalRepo.DohvatiPoServisuITip(r.Context(), id, "Advance", "Refund"); err == nil && povracaj != nil { + racuni = append(racuni, povracaj) } } } - if nalog != nil { - journal += "\n\n--- NTech servisni nalog: " + nalog.BrojNaloga + " ---\n" - } - - // Podeli journal na deo pre i posle QR placeholder-a - const qrPlaceholder = "{{{{QR-KOD}}}}" - pre, post, hasQR := strings.Cut(journal, qrPlaceholder) + nalog, _ := h.ServisRepo.DohvatiID(r.Context(), id) w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprint(w, `Fiskalni račun @@ -2841,21 +2863,42 @@ func (h *Handler) StampaFiskalnog(w http.ResponseWriter, r *http.Request) { *{box-sizing:border-box;} body{font-family:monospace;font-size:12px;padding:20px;max-width:max-content;margin:0 auto;} pre{white-space:pre;margin:0;padding:0;font-family:inherit;font-size:inherit;display:block;} -@media print{body{font-size:11px;padding:10px;}} +.ntech-razdvajac{margin:24px 0;border-top:2px dashed #999;padding-top:24px;} +@media print{body{font-size:11px;padding:10px;}.ntech-razdvajac{page-break-before:always;}} `) - fmt.Fprint(w, `
`)
-	fmt.Fprint(w, pre)
-	fmt.Fprint(w, `
`) + const qrPlaceholder = "{{{{QR-KOD}}}}" + for i, fr := range racuni { + if i > 0 { + fmt.Fprint(w, `
`) + } + journal := "" + if fr.SiroviOdgovor != "" { + var raw map[string]any + if json.Unmarshal([]byte(fr.SiroviOdgovor), &raw) == nil { + if j, ok := raw["journal"].(string); ok { + journal = j + } + } + } + if nalog != nil { + journal += "\n\n--- NTech servisni nalog: " + nalog.BrojNaloga + " ---\n" + } + pre, post, hasQR := strings.Cut(journal, qrPlaceholder) - if hasQR && fr.QRKod != "" { - fmt.Fprintf(w, `
`, fr.QRKod) + fmt.Fprint(w, `
`)
+		fmt.Fprint(w, pre)
+		fmt.Fprint(w, `
`) + + if hasQR && fr.QRKod != "" { + fmt.Fprintf(w, `
`, fr.QRKod) + } + + fmt.Fprint(w, `
`)
+		fmt.Fprint(w, post)
+		fmt.Fprint(w, `
`) } - fmt.Fprint(w, `
`)
-	fmt.Fprint(w, post)
-	fmt.Fprint(w, `
`) - fmt.Fprint(w, `

`) } func (h *Handler) AzurirajGaranciju(w http.ResponseWriter, r *http.Request) { diff --git a/internal/handler/uskladjivanje.go b/internal/handler/uskladjivanje.go index d9a27be..b032e25 100644 --- a/internal/handler/uskladjivanje.go +++ b/internal/handler/uskladjivanje.go @@ -243,10 +243,10 @@ func (h *Handler) PraznineKnjigovodstva(ctx context.Context) (model.PrazninaKnji if h.modulUkljucen(ctx, "fiskalizacija") { if m, err := h.FiskalRepo.ProdajeBezFiskalnog(ctx); err == nil { - p.BezFiskalnog += len(m) + p.BezFiskalnogProdaja += len(m) } if m, err := h.FiskalRepo.ServisiBezFiskalnog(ctx); err == nil { - p.BezFiskalnog += len(m) + p.BezFiskalnogServis += len(m) } if n, err := h.stornoBezRefunda(ctx); err == nil { p.BezRefunda = n diff --git a/internal/model/stranica.go b/internal/model/stranica.go index afc07d6..7846551 100644 --- a/internal/model/stranica.go +++ b/internal/model/stranica.go @@ -78,16 +78,23 @@ type PodaciDashboarda struct { // sumnjivih duplikata (naloga koje automatski backfill namerno preskače) — prikazuje // se kao kartica na dashboard-u. type PrazninaKnjigovodstva struct { - BezKir int - BezKpo int - BezFiskalnog int - BezRefunda int // stornirani nalozi čiji fiskalni refund nije uspeo (best-effort poziv pao) - SumnjiviDupli int + BezKir int + BezKpo int + BezFiskalnogProdaja int // prodajni nalozi bez izdatog fiskalnog računa + BezFiskalnogServis int // preuzeti servisni nalozi bez izdatog fiskalnog računa + BezRefunda int // stornirani nalozi čiji fiskalni refund nije uspeo (best-effort poziv pao) + SumnjiviDupli int +} + +// BezFiskalnog vraća ukupan broj naloga (prodaja + servis) bez izdatog fiskalnog +// računa — koristi se za prikaz zbirnog broja na dashboard-u. +func (p PrazninaKnjigovodstva) BezFiskalnog() int { + return p.BezFiskalnogProdaja + p.BezFiskalnogServis } // Ukupno vraća zbir svih praznina UKLJUČUJUĆI sumnjive duplikate — koristi se da se // odluči da li se kartica uopšte prikazuje na dashboard-u (i kad ostanu samo // duplikati za ručnu proveru, kartica mora da ostane vidljiva). func (p PrazninaKnjigovodstva) Ukupno() int { - return p.BezKir + p.BezKpo + p.BezFiskalnog + p.BezRefunda + p.SumnjiviDupli + return p.BezKir + p.BezKpo + p.BezFiskalnog() + p.BezRefunda + p.SumnjiviDupli } diff --git a/web/templates/stranice/dashboard.html b/web/templates/stranice/dashboard.html index fc16031..e6540cb 100644 --- a/web/templates/stranice/dashboard.html +++ b/web/templates/stranice/dashboard.html @@ -25,14 +25,15 @@
Knjigovodstvo — praznine u evidenciji
- {{ if gt .PrazninaKnjige.BezKir 0 }}{{ .PrazninaKnjige.BezKir }} bez KIR upisa{{ if or (gt .PrazninaKnjige.BezKpo 0) (gt .PrazninaKnjige.BezFiskalnog 0) (gt .PrazninaKnjige.BezRefunda 0) }}, {{ end }}{{ end }}{{ if gt .PrazninaKnjige.BezKpo 0 }}{{ .PrazninaKnjige.BezKpo }} bez KPO upisa{{ if or (gt .PrazninaKnjige.BezFiskalnog 0) (gt .PrazninaKnjige.BezRefunda 0) }}, {{ end }}{{ end }}{{ if gt .PrazninaKnjige.BezFiskalnog 0 }}{{ .PrazninaKnjige.BezFiskalnog }} bez fiskalnog računa{{ if gt .PrazninaKnjige.BezRefunda 0 }}, {{ end }}{{ end }}{{ if gt .PrazninaKnjige.BezRefunda 0 }}{{ .PrazninaKnjige.BezRefunda }} storno bez fiskalnog refunda{{ end }} - {{ if gt .PrazninaKnjige.SumnjiviDupli 0 }}{{ if or (gt .PrazninaKnjige.BezKir 0) (gt .PrazninaKnjige.BezKpo 0) (gt .PrazninaKnjige.BezFiskalnog 0) (gt .PrazninaKnjige.BezRefunda 0) }} · {{ end }}{{ .PrazninaKnjige.SumnjiviDupli }} sumnjivih duplikata (proveri ručno){{ end }} + {{ if gt .PrazninaKnjige.BezKir 0 }}{{ .PrazninaKnjige.BezKir }} bez KIR upisa{{ if or (gt .PrazninaKnjige.BezKpo 0) (gt .PrazninaKnjige.BezFiskalnogProdaja 0) (gt .PrazninaKnjige.BezFiskalnogServis 0) (gt .PrazninaKnjige.BezRefunda 0) }}, {{ end }}{{ end }}{{ if gt .PrazninaKnjige.BezKpo 0 }}{{ .PrazninaKnjige.BezKpo }} bez KPO upisa{{ if or (gt .PrazninaKnjige.BezFiskalnogProdaja 0) (gt .PrazninaKnjige.BezFiskalnogServis 0) (gt .PrazninaKnjige.BezRefunda 0) }}, {{ end }}{{ end }}{{ if gt .PrazninaKnjige.BezFiskalnogProdaja 0 }}{{ .PrazninaKnjige.BezFiskalnogProdaja }} prodaja bez fiskalnog računa{{ if or (gt .PrazninaKnjige.BezFiskalnogServis 0) (gt .PrazninaKnjige.BezRefunda 0) }}, {{ end }}{{ end }}{{ if gt .PrazninaKnjige.BezFiskalnogServis 0 }}{{ .PrazninaKnjige.BezFiskalnogServis }} servis bez fiskalnog računa{{ if gt .PrazninaKnjige.BezRefunda 0 }}, {{ end }}{{ end }}{{ if gt .PrazninaKnjige.BezRefunda 0 }}{{ .PrazninaKnjige.BezRefunda }} storno bez fiskalnog refunda{{ end }} + {{ if gt .PrazninaKnjige.SumnjiviDupli 0 }}{{ if or (gt .PrazninaKnjige.BezKir 0) (gt .PrazninaKnjige.BezKpo 0) (gt .PrazninaKnjige.BezFiskalnogProdaja 0) (gt .PrazninaKnjige.BezFiskalnogServis 0) (gt .PrazninaKnjige.BezRefunda 0) }} · {{ end }}{{ .PrazninaKnjige.SumnjiviDupli }} sumnjivih duplikata (proveri ručno){{ end }}
{{ if gt .PrazninaKnjige.BezKir 0 }}KIR{{ end }} {{ if gt .PrazninaKnjige.BezKpo 0 }}KPO{{ end }} - {{ if gt .PrazninaKnjige.BezFiskalnog 0 }}Fiskalizacija{{ end }} + {{ if gt .PrazninaKnjige.BezFiskalnogProdaja 0 }}Fiskalizacija — prodaja{{ end }} + {{ if gt .PrazninaKnjige.BezFiskalnogServis 0 }}Fiskalizacija — servis{{ end }} {{ if gt .PrazninaKnjige.BezRefunda 0 }}Storno bez refunda{{ end }}
diff --git a/web/templates/stranice/servis_detalji.html b/web/templates/stranice/servis_detalji.html index 08a91cf..595e483 100644 --- a/web/templates/stranice/servis_detalji.html +++ b/web/templates/stranice/servis_detalji.html @@ -138,10 +138,12 @@
Ovaj iznos se automatski vraća kroz avansni povraćaj na fiskalnom računu.
{{end}} + {{$zaNaplatu := .PreostaloSve}}{{if index .Moduli "pdv"}}{{$zaNaplatu = .PreostaloSveSaPdv}}{{end}}
- + + {{if gt $zaNaplatu 0.0}}
@@ -150,6 +152,11 @@ Kusur (vratiti) 0,00 din
+ {{else}} +
+ Avans pokriva ceo iznos — ništa se dodatno ne naplaćuje. +
+ {{end}}