PDV KIR: zaštita od duplog upisa dnevnog pazara maloprodaje

SacuvajDnevniPazarKir je upisivao ručni zbirni KIR zapis za dati dan bez provere
da li već postoji — za razliku od auto-KIR-a iz pojedinačnih faktura (koji ima
PostojiZaIzvor zaštitu), ovde ništa nije sprečavalo duplo klikanje/ponovni unos
istog dana, što bi duplo precenilo PDV osnov u zvaničnoj evidenciji.

Broj dokumenta (FISK-YYYYMMDD) je već deterministički po danu, pa se koristi kao
prirodni ključ za proveru. Forma sad prikazuje upozorenje i sakriva dugme za upis
ako je dan već zaveden.
This commit is contained in:
2026-07-01 11:54:35 +02:00
parent bd5a63511b
commit 77320d2030
4 changed files with 55 additions and 6 deletions
+3
View File
@@ -70,6 +70,9 @@ type PdvKirRepository interface {
ObrisiPoIzvoru(ctx context.Context, izvor string, izvorID int64) error
// PostojiZaIzvor vraća true ako postoji bar jedan zapis za dati izvor i izvorID
PostojiZaIzvor(ctx context.Context, izvor string, izvorID int64) (bool, error)
// PostojiPoBrojuDokumenta vraća true ako postoji zapis sa datim brojem dokumenta
// (npr. zaštita od duplog unosa dnevnog pazara "FISK-YYYYMMDD")
PostojiPoBrojuDokumenta(ctx context.Context, brojDokumenta string) (bool, error)
}
// PdvKprRepository definiše operacije nad knjigom primljenih računa (KPR)
+12
View File
@@ -148,6 +148,18 @@ func (r *PdvKirRepo) PostojiZaIzvor(ctx context.Context, izvor string, izvorID i
return n > 0, nil
}
// PostojiPoBrojuDokumenta vraća true ako postoji KIR zapis sa datim brojem dokumenta
func (r *PdvKirRepo) PostojiPoBrojuDokumenta(ctx context.Context, brojDokumenta string) (bool, error) {
var n int
err := r.db.QueryRowContext(ctx,
"SELECT COUNT(*) FROM pdv_kir WHERE broj_dokumenta = ?", brojDokumenta,
).Scan(&n)
if err != nil {
return false, fmt.Errorf("ntech: PdvKirRepo.PostojiPoBrojuDokumenta: %w", err)
}
return n > 0, nil
}
// izvorIliRucno vraća izvor ili podrazumevano „rucno" (izvor kolona je NOT NULL)
func izvorIliRucno(izvor string) string {
if izvor == "" {
+27 -2
View File
@@ -254,6 +254,7 @@ type PodaciDnevniPazar struct {
Datum string
Promet model.DnevniPrometKir
Ukupno float64
VecUpisano bool // true: za ovaj datum već postoji zbirni KIR zapis
}
// DnevniPazarKir prikazuje formu za potvrdu zbirnog KIR unosa za izabrani datum.
@@ -281,6 +282,11 @@ func (h *Handler) DnevniPazarKir(w http.ResponseWriter, r *http.Request) {
ukupno := math.Round((promet.OsnovicaOpsta+promet.PdvOpsta+promet.OsnovicaPosebna+promet.PdvPosebna)*100) / 100
var vecUpisano bool
if brojDok, e := brojDokumentaDnevniPazar(datum); e == nil {
vecUpisano, _ = h.PdvKirRepo.PostojiPoBrojuDokumenta(r.Context(), brojDok)
}
ps := h.popuniPodaciStranice(r, podesavanja)
ps.Stranica = "pdv-kir"
ps.NaslovStranice = "Dnevni pazar — KIR unos"
@@ -289,9 +295,20 @@ func (h *Handler) DnevniPazarKir(w http.ResponseWriter, r *http.Request) {
Datum: datum,
Promet: promet,
Ukupno: ukupno,
VecUpisano: vecUpisano,
})
}
// brojDokumentaDnevniPazar gradi deterministički broj dokumenta za zbirni KIR unos
// dnevnog pazara ("FISK-YYYYMMDD") — koristi se i za upis i za proveru duplikata.
func brojDokumentaDnevniPazar(datum string) (string, error) {
d, err := time.Parse("2006-01-02", datum)
if err != nil {
return "", err
}
return fmt.Sprintf("FISK-%s", d.Format("20060102")), nil
}
// SacuvajDnevniPazarKir prima POST i upisuje zbirni KIR zapis za dnevni pazar.
func (h *Handler) SacuvajDnevniPazarKir(w http.ResponseWriter, r *http.Request) {
if _, ok := h.zahtevajDozvolu(w, r, "pdv.dodaj"); !ok {
@@ -302,17 +319,25 @@ func (h *Handler) SacuvajDnevniPazarKir(w http.ResponseWriter, r *http.Request)
return
}
datum, e1 := time.Parse("2006-01-02", strings.TrimSpace(r.FormValue("datum")))
datumStr := strings.TrimSpace(r.FormValue("datum"))
datum, e1 := time.Parse("2006-01-02", datumStr)
if e1 != nil {
middleware.SetFlash(w, r, h.DB, "greska", "Datum je obavezan.")
http.Redirect(w, r, "/pdv/kir/dnevni-pazar", http.StatusSeeOther)
return
}
brojDokumenta := fmt.Sprintf("FISK-%s", datum.Format("20060102"))
if postoji, _ := h.PdvKirRepo.PostojiPoBrojuDokumenta(r.Context(), brojDokumenta); postoji {
middleware.SetFlash(w, r, h.DB, "greska", fmt.Sprintf("Dnevni pazar za %s je već upisan u KIR.", datum.Format("02.01.2006.")))
http.Redirect(w, r, "/pdv/kir/dnevni-pazar?datum="+datumStr, http.StatusSeeOther)
return
}
z := model.PdvKir{
DatumPrometa: datum,
DatumKnjizenja: datum,
BrojDokumenta: fmt.Sprintf("FISK-%s", datum.Format("20060102")),
BrojDokumenta: brojDokumenta,
KupacNaziv: "Promet fizičkim licima",
OsnovicaOpsta: parsiraIznos(r.FormValue("osnovica_opsta")),
PdvOpsta: parsiraIznos(r.FormValue("pdv_opsta")),
@@ -78,7 +78,16 @@
</div>
</div>
{{if gt .Promet.BrojNaloga 0}}
{{if .VecUpisano}}
<div class="kartica animiraj" style="margin-bottom:16px;border:1.5px solid #f59e0b;background:color-mix(in srgb,#fef3c7 60%,var(--pozadina));">
<div style="padding:16px;">
<div style="font-size:14px;font-weight:600;color:#92400e;margin-bottom:4px;">⚠ Već upisano</div>
<div style="font-size:13px;color:#78350f;">Dnevni pazar za {{.Datum}} je već upisan u KIR. Ponovni upis bi duplirao promet za ovaj dan.</div>
</div>
</div>
{{end}}
{{if and (gt .Promet.BrojNaloga 0) (not .VecUpisano)}}
<!-- forma za upis -->
<form method="POST" action="/pdv/kir/dnevni-pazar">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">