diff --git a/internal/db/errors.go b/internal/db/errors.go index c29c195..16a07d1 100644 --- a/internal/db/errors.go +++ b/internal/db/errors.go @@ -6,5 +6,5 @@ type ErrNedovoljnoKolicine struct { } func (e *ErrNedovoljnoKolicine) Error() string { - return "Nedovoljno količine na stanju za artikal: " + e.ArtikalNaziv + return "ntech: nedovoljno količine na stanju za artikal: " + e.ArtikalNaziv } diff --git a/internal/db/repository.go b/internal/db/repository.go index 4e4ec0f..e966420 100644 --- a/internal/db/repository.go +++ b/internal/db/repository.go @@ -29,6 +29,9 @@ var ErrArtikalDuplikatSifre = errors.New("ntech: šifra ili barkod artikla već var ErrUslugaDuplikatSifre = errors.New("ntech: šifra usluge već postoji") var ErrTrosakDuplikatSifre = errors.New("ntech: šifra troška već postoji") +// ErrArtikalNePostoji se vraća kada se menja cena nepostojećeg artikla +var ErrArtikalNePostoji = errors.New("ntech: artikal ne postoji") + // ArtikalRepository definiše operacije nad artiklima type ArtikalRepository interface { Lista(ctx context.Context, filter ArtikalFilter) ([]model.ArtikalSaKategorijom, error) diff --git a/internal/db/sqlite/nivelacija.go b/internal/db/sqlite/nivelacija.go index da0dedd..4b296c3 100644 --- a/internal/db/sqlite/nivelacija.go +++ b/internal/db/sqlite/nivelacija.go @@ -7,6 +7,7 @@ import ( "fmt" "time" + "ntech/internal/db" "ntech/internal/model" ) @@ -20,9 +21,6 @@ func NoviNivelacijaRepo(db *sql.DB) *NivelacijaRepo { return &NivelacijaRepo{db: db} } -// ErrArtikalNePostoji se vraća kada se menja cena nepostojećeg artikla -var ErrArtikalNePostoji = errors.New("artikal ne postoji") - // PromeniCenu transakciono menja prodajnu cenu artikla i upisuje nivelacioni zapis. // Stara cena se čita iz baze unutar transakcije; izvor je "rucno". func (r *NivelacijaRepo) PromeniCenu(ctx context.Context, artikalID int64, novaCena float64, razlog string, korisnikID *int64) (*model.Nivelacija, error) { @@ -35,7 +33,7 @@ func (r *NivelacijaRepo) PromeniCenu(ctx context.Context, artikalID int64, novaC var stara float64 err = tx.QueryRowContext(ctx, "SELECT prodajna_cena FROM artikli WHERE id = ?", artikalID).Scan(&stara) if errors.Is(err, sql.ErrNoRows) { - return nil, ErrArtikalNePostoji + return nil, db.ErrArtikalNePostoji } if err != nil { return nil, fmt.Errorf("ntech: NivelacijaRepo.PromeniCenu: čitanje cene: %w", err) diff --git a/internal/db/sqlite/nivelacija_test.go b/internal/db/sqlite/nivelacija_test.go index 0f97d51..0863f29 100644 --- a/internal/db/sqlite/nivelacija_test.go +++ b/internal/db/sqlite/nivelacija_test.go @@ -5,6 +5,7 @@ import ( "errors" "testing" + "ntech/internal/db" "ntech/internal/model" ) @@ -62,7 +63,7 @@ func TestPromeniCenu_NepostojeciArtikal(t *testing.T) { nivRepo := NoviNivelacijaRepo(baza) _, err := nivRepo.PromeniCenu(ctx, 9999, 500, "test", nil) - if !errors.Is(err, ErrArtikalNePostoji) { + if !errors.Is(err, db.ErrArtikalNePostoji) { t.Errorf("greška = %v, očekivano ErrArtikalNePostoji", err) } diff --git a/internal/handler/blokirane_ip.go b/internal/handler/blokirane_ip.go index 2cb44e7..e243904 100644 --- a/internal/handler/blokirane_ip.go +++ b/internal/handler/blokirane_ip.go @@ -5,7 +5,7 @@ import ( "strings" "time" - ntechsqlite "ntech/internal/db/sqlite" + "ntech/internal/db/sqlite" "ntech/internal/middleware" "ntech/internal/model" ) @@ -28,7 +28,7 @@ func (h *Handler) AdminBlokiraneIP(w http.ResponseWriter, r *http.Request) { if _, ok := h.zahtevajDozvolu(w, r, "bezbednost.odblokiraj"); !ok { return } - podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) if err != nil { http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError) return diff --git a/internal/handler/fiskal_pazar.go b/internal/handler/fiskal_pazar.go index 7bb6ccd..65ef3a7 100644 --- a/internal/handler/fiskal_pazar.go +++ b/internal/handler/fiskal_pazar.go @@ -10,7 +10,7 @@ import ( "unicode" "ntech/internal/config" - ntechsqlite "ntech/internal/db/sqlite" + "ntech/internal/db/sqlite" "ntech/internal/fiskal" "ntech/internal/middleware" "ntech/internal/model" @@ -46,7 +46,7 @@ func (h *Handler) FiskalniPazar(w http.ResponseWriter, r *http.Request) { if _, ok := h.zahtevajDozvolu(w, r, "fiskal.pazar"); !ok { return } - podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) if err != nil { http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError) return diff --git a/internal/handler/nivelacija.go b/internal/handler/nivelacija.go index cd548b7..377ed2d 100644 --- a/internal/handler/nivelacija.go +++ b/internal/handler/nivelacija.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "ntech/internal/db" "ntech/internal/db/sqlite" "ntech/internal/middleware" "ntech/internal/model" @@ -91,7 +92,7 @@ func (h *Handler) PromeniCenuArtikla(w http.ResponseWriter, r *http.Request) { korisnikID := &k.ID _, err = h.NivelacijaRepo.PromeniCenu(r.Context(), id, novaCena, razlog, korisnikID) switch { - case errors.Is(err, sqlite.ErrArtikalNePostoji): + case errors.Is(err, db.ErrArtikalNePostoji): middleware.SetFlash(w, r, h.DB, "greska", "Artikal nije pronađen.") http.Redirect(w, r, "/magacin", http.StatusSeeOther) case err != nil: diff --git a/internal/handler/podesavanja.go b/internal/handler/podesavanja.go index 795c559..3544ac1 100644 --- a/internal/handler/podesavanja.go +++ b/internal/handler/podesavanja.go @@ -24,7 +24,7 @@ import ( "ntech/internal/auth" "ntech/internal/db" - ntechsqlite "ntech/internal/db/sqlite" + "ntech/internal/db/sqlite" "ntech/internal/middleware" "ntech/internal/model" ) @@ -314,7 +314,7 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) { if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.pregled"); !ok { return } - podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) if err != nil { http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError) return @@ -450,7 +450,7 @@ func (h *Handler) VratiBackup(w http.ResponseWriter, r *http.Request) { os.Remove(h.PutanjaBaze + "-wal") os.Remove(h.PutanjaBaze + "-shm") - novaDB, err := ntechsqlite.OtvoriDB(h.PutanjaBaze) + novaDB, err := sqlite.OtvoriDB(h.PutanjaBaze) if err != nil { slog.Error("vrati backup: greška pri otvaranju nove baze (potreban restart)", "error", err) return @@ -567,7 +567,7 @@ func (h *Handler) UvezizBazu(w http.ResponseWriter, r *http.Request) { os.Remove(h.PutanjaBaze + "-wal") os.Remove(h.PutanjaBaze + "-shm") - novaDB, err := ntechsqlite.OtvoriDB(h.PutanjaBaze) + novaDB, err := sqlite.OtvoriDB(h.PutanjaBaze) if err != nil { slog.Error("uvezi bazu: greška pri otvaranju nove baze (potreban restart)", "error", err) return @@ -699,7 +699,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { if vrednost == "" { continue } - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, kljuc, vrednost); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, kljuc, vrednost); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -728,7 +728,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, sledeci, http.StatusSeeOther) return } - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "backup_interval_sati", strconv.Itoa(n)); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "backup_interval_sati", strconv.Itoa(n)); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -740,7 +740,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, sledeci, http.StatusSeeOther) return } - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "backup_broj_kopija", strconv.Itoa(n)); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "backup_broj_kopija", strconv.Itoa(n)); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -754,7 +754,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, sledeci, http.StatusSeeOther) return } - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "kalkulacija_marza", strconv.FormatFloat(marza, 'f', -1, 64)); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "kalkulacija_marza", strconv.FormatFloat(marza, 'f', -1, 64)); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -768,7 +768,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, sledeci, http.StatusSeeOther) return } - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_garancija_dana", strconv.Itoa(n)); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_garancija_dana", strconv.Itoa(n)); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -782,7 +782,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, sledeci, http.StatusSeeOther) return } - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_cena_dijagnostike", strconv.FormatFloat(cena, 'f', -1, 64)); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_cena_dijagnostike", strconv.FormatFloat(cena, 'f', -1, 64)); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -796,7 +796,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, sledeci, http.StatusSeeOther) return } - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "predracun_rok_dana", strconv.Itoa(n)); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "predracun_rok_dana", strconv.Itoa(n)); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -809,7 +809,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, sledeci, http.StatusSeeOther) return } - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "predvidjen_rok_dana", strconv.Itoa(n)); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "predvidjen_rok_dana", strconv.Itoa(n)); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -819,7 +819,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { // pa korisnik može i da skrati ili isprazni tekst if _, ima := r.Form["servis_uslovi"]; ima { uslovi := strings.TrimSpace(r.FormValue("servis_uslovi")) - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_uslovi", uslovi); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_uslovi", uslovi); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -828,7 +828,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { // klauzula na predračunu (slobodan tekst); čuva se uvek if _, ima := r.Form["servis_klauzula_predracuna"]; ima { klauzula := strings.TrimSpace(r.FormValue("servis_klauzula_predracuna")) - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_klauzula_predracuna", klauzula); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_klauzula_predracuna", klauzula); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -837,7 +837,7 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) { // bazna adresa za QR kod (npr. http://192.168.1.25:3000); prazno → koristi se host iz zahteva if _, ima := r.Form["qr_bazni_url"]; ima { bazni := strings.TrimSpace(r.FormValue("qr_bazni_url")) - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "qr_bazni_url", bazni); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "qr_bazni_url", bazni); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } @@ -943,7 +943,7 @@ func (h *Handler) OtpremiLogo(w http.ResponseWriter, r *http.Request) { // timestamp u URL-u sprečava browser da koristi staru keširanu sliku putanja := fmt.Sprintf("/static/uploads/logo%s?v=%d", ext, time.Now().Unix()) - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "logo_putanja", putanja); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "logo_putanja", putanja); err != nil { slog.Error("upload loga: greška pri čuvanju putanje", "error", err) http.Redirect(w, r, "/admin/podesavanja/opste?logo_greska=Greška+pri+čuvanju+podešavanja", http.StatusSeeOther) return @@ -961,7 +961,7 @@ func (h *Handler) UkloniLogo(w http.ResponseWriter, r *http.Request) { for _, s := range stari { os.Remove(s) } - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "logo_putanja", ""); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "logo_putanja", ""); err != nil { slog.Error("ukloni logo: greška pri čuvanju", "error", err) } http.Redirect(w, r, "/admin/podesavanja/opste?sacuvano=1", http.StatusSeeOther) @@ -1047,7 +1047,7 @@ func (h *Handler) OtpremiLoginPozadinu(w http.ResponseWriter, r *http.Request) { } // briše staru pozadinu sa diska ako postoji - staraPodesavanja, _ := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + staraPodesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) if stara := staraPodesavanja["login_pozadina"]; stara != "" { // putanja u bazi je oblika /static/uploads/ime.ext?v=..., izvlačimo samo ime fajla deoBezverzije, _, _ := strings.Cut(stara, "?") @@ -1081,7 +1081,7 @@ func (h *Handler) OtpremiLoginPozadinu(w http.ResponseWriter, r *http.Request) { } putanja := fmt.Sprintf("/static/uploads/%s?v=%d", novoIme, time.Now().Unix()) - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "login_pozadina", putanja); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "login_pozadina", putanja); err != nil { slog.Error("upload login pozadine: greška pri čuvanju putanje", "error", err) middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju podešavanja.") http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther) @@ -1098,7 +1098,7 @@ func (h *Handler) UkloniLoginPozadinu(w http.ResponseWriter, r *http.Request) { return } - podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) if err == nil { if stara := podesavanja["login_pozadina"]; stara != "" { deoBezverzije, _, _ := strings.Cut(stara, "?") @@ -1107,7 +1107,7 @@ func (h *Handler) UkloniLoginPozadinu(w http.ResponseWriter, r *http.Request) { } } - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "login_pozadina", ""); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, "login_pozadina", ""); err != nil { slog.Error("ukloni login pozadinu: greška pri čuvanju", "error", err) middleware.SetFlash(w, r, h.DB, "greska", "Greška pri uklanjanju slike.") http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther) @@ -1164,7 +1164,7 @@ func (h *Handler) SacuvajLoginPozadinaStilove(w http.ResponseWriter, r *http.Req "login_pozadina_opacity": opacityStr, "login_pozadina_zatamnjenje_kartice": zatamnjenjeKarticeStr, } { - if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, kljuc, vrednost); err != nil { + if err := sqlite.SacuvajPodesavanje(r.Context(), h.DB, kljuc, vrednost); err != nil { slog.Error("greška pri čuvanju stila login pozadine", "kljuc", kljuc, "error", err) middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju podešavanja.") http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther) @@ -1177,7 +1177,7 @@ func (h *Handler) SacuvajLoginPozadinaStilove(w http.ResponseWriter, r *http.Req // napuniPodaciPodesavanja učitava sva podešavanja i kreira strukturu za template func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (PodaciPodesavanja, error) { - podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) if err != nil { return PodaciPodesavanja{}, err } @@ -1282,7 +1282,7 @@ func (h *Handler) TestFiskalizacije(w http.ResponseWriter, r *http.Request) { } pfrURL := strings.TrimSpace(r.FormValue("pfr_url")) if pfrURL == "" { - podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) if err != nil { http.Error(w, "Greška", http.StatusInternalServerError) return diff --git a/internal/handler/prijava.go b/internal/handler/prijava.go index 719c4a1..eeb8e44 100644 --- a/internal/handler/prijava.go +++ b/internal/handler/prijava.go @@ -11,7 +11,7 @@ import ( "time" "ntech/internal/auth" - ntechsqlite "ntech/internal/db/sqlite" + "ntech/internal/db/sqlite" "ntech/internal/middleware" ) @@ -39,7 +39,7 @@ func (h *Handler) PrikazPrijave(w http.ResponseWriter, r *http.Request) { loginBlurPozadine := "0" loginBlurKartice := "12" loginZatamnjenjeKartice := "0" - if podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(context.Background(), h.DB); err == nil { + if podesavanja, err := sqlite.DohvatiSvaPodesavanja(context.Background(), h.DB); err == nil { loginPozadina = podesavanja["login_pozadina"] if v := podesavanja["login_pozadina_opacity"]; v != "" { loginOpacity = v diff --git a/internal/handler/prodaja.go b/internal/handler/prodaja.go index 241c485..7ac9c4b 100644 --- a/internal/handler/prodaja.go +++ b/internal/handler/prodaja.go @@ -613,9 +613,8 @@ func parseFormuProdaje(r *http.Request, validneStope map[float64]bool) (model.Pr // StornoProdaje stornira prodajni nalog: vraća artikle na stanje i označava nalog kao storniran func (h *Handler) StornoProdaje(w http.ResponseWriter, r *http.Request) { - k := middleware.KorisnikIzKonteksta(r.Context()) - if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "prodaja.storno") { - http.Error(w, "Nemate dozvolu za storniranje prodaje.", http.StatusForbidden) + k, ok := h.zahtevajDozvolu(w, r, "prodaja.storno") + if !ok { return } id, err := parseID(chi.URLParam(r, "id")) diff --git a/web/templates/stranice/magacin.html b/web/templates/stranice/magacin.html index 354a8f0..40ec83f 100644 --- a/web/templates/stranice/magacin.html +++ b/web/templates/stranice/magacin.html @@ -317,7 +317,7 @@ function toggleCenaPdv() {

Promeni prodajnu cenu

-
+
Trenutna cena: {{dinariCeli .Cena}} din