diff --git a/internal/config/moduli.go b/internal/config/moduli.go index dcf5fcd..db15eca 100644 --- a/internal/config/moduli.go +++ b/internal/config/moduli.go @@ -54,3 +54,14 @@ func ModulUkljucen(podesavanja map[string]string, modul string) bool { return false } } + +// SviModuli vraća mapu svih poznatih modula → da li su uključeni za dati profil firme. +// Koristi se da šabloni uslovno prikazuju stavke menija (analogno mapi Dozvole). +func SviModuli(podesavanja map[string]string) map[string]bool { + moduli := []string{ModulPdv, ModulFiskalizacija, ModulKpo, ModulDvojno} + m := make(map[string]bool, len(moduli)) + for _, modul := range moduli { + m[modul] = ModulUkljucen(podesavanja, modul) + } + return m +} diff --git a/internal/config/moduli_test.go b/internal/config/moduli_test.go index f90dae2..4f89f6c 100644 --- a/internal/config/moduli_test.go +++ b/internal/config/moduli_test.go @@ -66,3 +66,31 @@ func TestModulUkljucen(t *testing.T) { }) } } + +func TestSviModuli(t *testing.T) { + // pun režim, doo, PDV obveznik, fiskalizacija → pdv, fiskalizacija, dvojno uključeni; kpo ne + pod := map[string]string{ + KljucRezim: "pun", + KljucPdvObveznik: "da", + KljucFiskalizacija: "da", + KljucPravniOblik: "doo", + } + m := SviModuli(pod) + if len(m) != 4 { + t.Fatalf("SviModuli vraća %d modula, očekivano 4", len(m)) + } + ocek := map[string]bool{ModulPdv: true, ModulFiskalizacija: true, ModulKpo: false, ModulDvojno: true} + for modul, want := range ocek { + if m[modul] != want { + t.Errorf("SviModuli[%q] = %v, očekivano %v", modul, m[modul], want) + } + } + + // samo evidencija → svi ugašeni + prazna := SviModuli(map[string]string{KljucRezim: "samo_evidencija"}) + for modul, ukljucen := range prazna { + if ukljucen { + t.Errorf("u režimu samo_evidencija modul %q ne sme biti uključen", modul) + } + } +} diff --git a/internal/handler/handler.go b/internal/handler/handler.go index fa7dd79..565ce5f 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -7,6 +7,7 @@ import ( "net/http" "sync" + "ntech/internal/config" "ntech/internal/db" "ntech/internal/db/sqlite" "ntech/internal/middleware" @@ -159,6 +160,8 @@ func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]s ps.CsrfToken = middleware.CsrfToken(r.Context()) ps.AssetV = h.AssetV ps.Flash = middleware.GetFlash(r, h.DB) + // uključeni zakonski moduli prema profilu firme — šabloni ih koriste za uslovni meni + ps.Moduli = config.SviModuli(podesavanja) // logika pozadine: // - lična pozadina → uvek se prikazuje i forsira tamnu temu, bez obzira na KoristiLokalnuTemu diff --git a/internal/middleware/moduli.go b/internal/middleware/moduli.go new file mode 100644 index 0000000..6dacc7b --- /dev/null +++ b/internal/middleware/moduli.go @@ -0,0 +1,27 @@ +package middleware + +import ( + "context" + "net/http" +) + +// RequireModul je chi middleware koji propušta zahtev samo ako je traženi zakonski +// modul uključen za firmu (prema profilu firme iz podešavanja). Ovo je sloj IZNAD +// RBAC-a: „da li firma uopšte koristi modul", nezavisno od „da li korisnik sme" +// (RequireDozvola). Zahtev mora proći oba sloja. +// +// Provera se prosleđuje kao funkcija (proveri) da paket middleware ne zavisi od +// config/sqlite — isti obrazac kao RequireDozvola. U praksi je to closure koja +// učita podešavanja i pozove config.ModulUkljucen. +func RequireModul(proveri func(ctx context.Context, modul string) bool, modul string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !proveri(r.Context(), modul) { + postaviFlashGresku(w, "Ovaj modul nije uključen za vašu firmu.") + http.Redirect(w, r, "/dashboard", http.StatusSeeOther) + return + } + next.ServeHTTP(w, r) + }) + } +} diff --git a/internal/model/stranica.go b/internal/model/stranica.go index 02a707c..3474113 100644 --- a/internal/model/stranica.go +++ b/internal/model/stranica.go @@ -39,11 +39,12 @@ type PodaciStranice struct { LogoTip string // "sa_nazivom", "bez_naziva", "slika" LogoPutanja string // putanja do slike, koristi se samo kada je LogoTip "slika" Korisnik string - KorisnikIme string // korisničko ime prijavljenog korisnika + KorisnikIme string // korisničko ime prijavljenog korisnika KorisnikUloga string // uloga: "superadmin", "admin", "radnik" CsrfToken string // CSRF zaštitni token za forme AssetV string // verzija statičkih fajlova (cache-busting za CSS/JS) Dozvole map[string]bool // mapa akcija → dozvoljeno/nije + Moduli map[string]bool // mapa zakonskih modula → uključen za firmu (profil firme) Flash *FlashPoruka // jednokratna poruka nakon redirecta // app pozadina — popunjava se iz podešavanja za sve stranice AppPozadina string