feat(podesavanja): profil firme — pravni i poreski status (Faza 0)

Dodata kartica „Pravni i poreski status" na Podešavanja → Opšte:
pravni oblik, režim rada, PDV obveznik, fiskalizacija. Čuva se u
postojećoj key-value tabeli podesavanja (bez migracije). Fiskalizacija
se zasivi i forsira na „Ne" u režimu „samo evidencija".
This commit is contained in:
2026-06-13 19:27:26 +02:00
parent 1feab0d3dd
commit 290e5c085a
2 changed files with 184 additions and 50 deletions
+63 -50
View File
@@ -23,25 +23,30 @@ import (
// PodaciPodesavanja su podaci za stranicu podešavanja // PodaciPodesavanja su podaci za stranicu podešavanja
type PodaciPodesavanja struct { type PodaciPodesavanja struct {
model.PodaciStranice model.PodaciStranice
NazivFirme string NazivFirme string
Podnazlov string Podnazlov string
Adresa string Adresa string
Telefon string Telefon string
PIB string PIB string
LogoTip string LogoTip string
LogoPutanja string LogoPutanja string
Sacuvano bool // profil firme — pravni/poreski status (Faza 0); određuje koji se zakonski moduli pale
Verzija string FirmaPravniOblik string
LogoGreska string FirmaPdvObveznik string
BackupVracen bool FirmaFiskalizacija string
Backupi []BackupInfo FirmaRezim string
BackupIntervalSati string Sacuvano bool
BackupBrojKopija string Verzija string
LoginPozadina string LogoGreska string
LoginPozadinaOpacity string BackupVracen bool
LoginPozadinaBlurPozadine string Backupi []BackupInfo
LoginPozadinaBlurKartice string BackupIntervalSati string
LoginPozadinaZatamnjenjeKartice string BackupBrojKopija string
LoginPozadina string
LoginPozadinaOpacity string
LoginPozadinaBlurPozadine string
LoginPozadinaBlurKartice string
LoginPozadinaZatamnjenjeKartice string
} }
// BackupInfo opisuje jedan backup fajl // BackupInfo opisuje jedan backup fajl
@@ -69,19 +74,19 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) {
ps.Stranica = "podesavanja" ps.Stranica = "podesavanja"
ps.NaslovStranice = "Podešavanja" ps.NaslovStranice = "Podešavanja"
podaci := PodaciPodesavanja{ podaci := PodaciPodesavanja{
PodaciStranice: ps, PodaciStranice: ps,
NazivFirme: podesavanja["naziv_firme"], NazivFirme: podesavanja["naziv_firme"],
Podnazlov: podesavanja["podnazlov"], Podnazlov: podesavanja["podnazlov"],
Adresa: podesavanja["adresa"], Adresa: podesavanja["adresa"],
Telefon: podesavanja["telefon"], Telefon: podesavanja["telefon"],
PIB: podesavanja["pib"], PIB: podesavanja["pib"],
LogoTip: podesavanja["logo_tip"], LogoTip: podesavanja["logo_tip"],
LogoPutanja: podesavanja["logo_putanja"], LogoPutanja: podesavanja["logo_putanja"],
Sacuvano: r.URL.Query().Get("sacuvano") == "1", Sacuvano: r.URL.Query().Get("sacuvano") == "1",
BackupVracen: r.URL.Query().Get("sacuvano") == "vraceno", BackupVracen: r.URL.Query().Get("sacuvano") == "vraceno",
Verzija: h.Verzija, Verzija: h.Verzija,
LogoGreska: r.URL.Query().Get("logo_greska"), LogoGreska: r.URL.Query().Get("logo_greska"),
Backupi: ucitajListuBackupa(), Backupi: ucitajListuBackupa(),
LoginPozadina: podesavanja["login_pozadina"], LoginPozadina: podesavanja["login_pozadina"],
LoginPozadinaOpacity: vrednostIliDefault(podesavanja, "login_pozadina_opacity", "50"), LoginPozadinaOpacity: vrednostIliDefault(podesavanja, "login_pozadina_opacity", "50"),
LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"), LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"),
@@ -234,6 +239,11 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
"telefon": r.FormValue("telefon"), "telefon": r.FormValue("telefon"),
"pib": r.FormValue("pib"), "pib": r.FormValue("pib"),
"logo_tip": r.FormValue("logo_tip"), "logo_tip": r.FormValue("logo_tip"),
// profil firme (Faza 0) — radio dugmad uvek šalju vrednost, pa se uredno čuvaju
"firma_pravni_oblik": r.FormValue("firma_pravni_oblik"),
"firma_pdv_obveznik": r.FormValue("firma_pdv_obveznik"),
"firma_fiskalizacija": r.FormValue("firma_fiskalizacija"),
"firma_rezim": r.FormValue("firma_rezim"),
} }
for kljuc, vrednost := range polja { for kljuc, vrednost := range polja {
@@ -566,10 +576,10 @@ func (h *Handler) SacuvajLoginPozadinaStilove(w http.ResponseWriter, r *http.Req
} }
for kljuc, vrednost := range map[string]string{ for kljuc, vrednost := range map[string]string{
"login_pozadina_blur_pozadine": blurPozadineStr, "login_pozadina_blur_pozadine": blurPozadineStr,
"login_pozadina_blur_kartice": blurKarticeStr, "login_pozadina_blur_kartice": blurKarticeStr,
"login_pozadina_opacity": opacityStr, "login_pozadina_opacity": opacityStr,
"login_pozadina_zatamnjenje_kartice": zatamnjenjeKarticeStr, "login_pozadina_zatamnjenje_kartice": zatamnjenjeKarticeStr,
} { } {
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, kljuc, vrednost); err != nil { if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, kljuc, vrednost); err != nil {
slog.Error("greška pri čuvanju stila login pozadine", "kljuc", kljuc, "error", err) slog.Error("greška pri čuvanju stila login pozadine", "kljuc", kljuc, "error", err)
@@ -593,19 +603,23 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
ps.Stranica = "podesavanja" ps.Stranica = "podesavanja"
ps.NaslovStranice = naslov ps.NaslovStranice = naslov
return PodaciPodesavanja{ return PodaciPodesavanja{
PodaciStranice: ps, PodaciStranice: ps,
NazivFirme: podesavanja["naziv_firme"], NazivFirme: podesavanja["naziv_firme"],
Podnazlov: podesavanja["podnazlov"], Podnazlov: podesavanja["podnazlov"],
Adresa: podesavanja["adresa"], Adresa: podesavanja["adresa"],
Telefon: podesavanja["telefon"], Telefon: podesavanja["telefon"],
PIB: podesavanja["pib"], PIB: podesavanja["pib"],
LogoTip: podesavanja["logo_tip"], LogoTip: podesavanja["logo_tip"],
LogoPutanja: podesavanja["logo_putanja"], LogoPutanja: podesavanja["logo_putanja"],
Sacuvano: r.URL.Query().Get("sacuvano") == "1", FirmaPravniOblik: vrednostIliDefault(podesavanja, "firma_pravni_oblik", "pausalac"),
BackupVracen: r.URL.Query().Get("sacuvano") == "vraceno", FirmaPdvObveznik: vrednostIliDefault(podesavanja, "firma_pdv_obveznik", "ne"),
Verzija: h.Verzija, FirmaFiskalizacija: vrednostIliDefault(podesavanja, "firma_fiskalizacija", "ne"),
LogoGreska: r.URL.Query().Get("logo_greska"), FirmaRezim: vrednostIliDefault(podesavanja, "firma_rezim", "samo_evidencija"),
Backupi: ucitajListuBackupa(), Sacuvano: r.URL.Query().Get("sacuvano") == "1",
BackupVracen: r.URL.Query().Get("sacuvano") == "vraceno",
Verzija: h.Verzija,
LogoGreska: r.URL.Query().Get("logo_greska"),
Backupi: ucitajListuBackupa(),
LoginPozadina: podesavanja["login_pozadina"], LoginPozadina: podesavanja["login_pozadina"],
LoginPozadinaOpacity: vrednostIliDefault(podesavanja, "login_pozadina_opacity", "50"), LoginPozadinaOpacity: vrednostIliDefault(podesavanja, "login_pozadina_opacity", "50"),
LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"), LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"),
@@ -657,4 +671,3 @@ func (h *Handler) PodesavanjaSistem(w http.ResponseWriter, r *http.Request) {
podaci.Stranica = "podesavanja-sistem" podaci.Stranica = "podesavanja-sistem"
h.renderujTemplate(w, "podesavanja_sistem", podaci) h.renderujTemplate(w, "podesavanja_sistem", podaci)
} }
@@ -117,6 +117,94 @@
</div> </div>
</form> </form>
<!-- profil firme: pravni/poreski status (Faza 0) — određuje koji se zakonski moduli pale -->
<form method="POST" action="/podesavanja/sacuvaj">
<input type="hidden" name="_next" value="/admin/podesavanja/opste">
<div class="kartica animiraj" style="margin-bottom:16px;">
<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="7" width="20" height="14" rx="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Pravni i poreski status</span>
</div>
<div class="kolona" style="gap:18px;">
<!-- pravni oblik -->
<div>
<label class="polje-labela">Pravni oblik</label>
<div style="display:flex;gap:10px;flex-wrap:wrap;">
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .FirmaPravniOblik "pausalac"}}border-color:var(--sb-akcent);background:var(--pozadina);{{end}}">
<input type="radio" name="firma_pravni_oblik" value="pausalac" {{if eq .FirmaPravniOblik "pausalac"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<span style="font-size:13px;color:var(--tekst-glavni);">Paušalac</span>
</label>
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .FirmaPravniOblik "preduzetnik_knjige"}}border-color:var(--sb-akcent);background:var(--pozadina);{{end}}">
<input type="radio" name="firma_pravni_oblik" value="preduzetnik_knjige" {{if eq .FirmaPravniOblik "preduzetnik_knjige"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<span style="font-size:13px;color:var(--tekst-glavni);">Preduzetnik (knjige)</span>
</label>
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .FirmaPravniOblik "doo"}}border-color:var(--sb-akcent);background:var(--pozadina);{{end}}">
<input type="radio" name="firma_pravni_oblik" value="doo" {{if eq .FirmaPravniOblik "doo"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<span style="font-size:13px;color:var(--tekst-glavni);">DOO</span>
</label>
</div>
</div>
<!-- režim rada -->
<div>
<label class="polje-labela">Režim rada</label>
<div style="display:flex;gap:10px;flex-wrap:wrap;">
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .FirmaRezim "pun"}}border-color:var(--sb-akcent);background:var(--pozadina);{{end}}">
<input type="radio" name="firma_rezim" value="pun" {{if eq .FirmaRezim "pun"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<span style="font-size:13px;color:var(--tekst-glavni);">Pun (vodi poslovne knjige)</span>
</label>
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .FirmaRezim "samo_evidencija"}}border-color:var(--sb-akcent);background:var(--pozadina);{{end}}">
<input type="radio" name="firma_rezim" value="samo_evidencija" {{if eq .FirmaRezim "samo_evidencija"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<span style="font-size:13px;color:var(--tekst-glavni);">Samo evidencija</span>
</label>
</div>
<div class="pomocni-tekst" style="font-size:12px;margin-top:6px;">„Samo evidencija" gasi ceo zakonski sloj — program vodi servis i zalihe, ali ne izdaje fiskalne ni poreske dokumente.</div>
</div>
<!-- PDV obveznik -->
<div>
<label class="polje-labela">U sistemu PDV-a</label>
<div style="display:flex;gap:10px;flex-wrap:wrap;">
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .FirmaPdvObveznik "da"}}border-color:var(--sb-akcent);background:var(--pozadina);{{end}}">
<input type="radio" name="firma_pdv_obveznik" value="da" {{if eq .FirmaPdvObveznik "da"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<span style="font-size:13px;color:var(--tekst-glavni);">Da</span>
</label>
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .FirmaPdvObveznik "ne"}}border-color:var(--sb-akcent);background:var(--pozadina);{{end}}">
<input type="radio" name="firma_pdv_obveznik" value="ne" {{if eq .FirmaPdvObveznik "ne"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<span style="font-size:13px;color:var(--tekst-glavni);">Ne</span>
</label>
</div>
</div>
<!-- fiskalizacija — zasivljena i forsirana na „Ne" kad je režim „samo evidencija" (vidi skriptu na dnu) -->
<div id="fiskalizacija-grupa">
<label class="polje-labela">Izdaje fiskalne račune (promet građanima)</label>
<div style="display:flex;gap:10px;flex-wrap:wrap;">
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .FirmaFiskalizacija "da"}}border-color:var(--sb-akcent);background:var(--pozadina);{{end}}">
<input type="radio" name="firma_fiskalizacija" value="da" {{if eq .FirmaFiskalizacija "da"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<span style="font-size:13px;color:var(--tekst-glavni);">Da</span>
</label>
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .FirmaFiskalizacija "ne"}}border-color:var(--sb-akcent);background:var(--pozadina);{{end}}">
<input type="radio" name="firma_fiskalizacija" value="ne" {{if eq .FirmaFiskalizacija "ne"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<span style="font-size:13px;color:var(--tekst-glavni);">Ne</span>
</label>
</div>
</div>
</div>
<div style="display:flex;justify-content:flex-end;margin-top:20px;">
<button type="submit"
style="background:var(--sb-akcent);color:#fff;border:none;padding:10px 24px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:opacity 0.2s;"
onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'">
Sačuvaj status
</button>
</div>
</div>
</form>
</div> </div>
{{if .LogoGreska}} {{if .LogoGreska}}
@@ -135,4 +223,37 @@
})(); })();
</script> </script>
{{end}} {{end}}
<script>
// Fiskalizacija ima smisla samo u punom režimu. Kad je „samo evidencija", grupa se
// zasivi i forsira na „Ne". Ne koristimo disabled (jer disabled polje se ne šalje u
// POST pa bi stara vrednost ostala u bazi) — umesto toga pointer-events:none, a radio
// „Ne" ostaje aktivan i čekiran, pa se uredno sačuva „ne".
(function() {
var rezim = document.querySelectorAll('input[name="firma_rezim"]');
var grupa = document.getElementById('fiskalizacija-grupa');
var fisk = document.querySelectorAll('input[name="firma_fiskalizacija"]');
if (!grupa || !rezim.length) return;
function azuriraj() {
var izabran = document.querySelector('input[name="firma_rezim"]:checked');
var samoEvidencija = izabran && izabran.value === 'samo_evidencija';
grupa.style.opacity = samoEvidencija ? '0.45' : '';
grupa.style.pointerEvents = samoEvidencija ? 'none' : '';
if (samoEvidencija) {
fisk.forEach(function(r) {
r.checked = (r.value === 'ne');
var lab = r.closest('label');
if (lab) {
lab.style.borderColor = r.checked ? 'var(--sb-akcent)' : 'var(--ivica)';
lab.style.background = r.checked ? 'var(--pozadina)' : 'transparent';
}
});
}
}
rezim.forEach(function(r) { r.addEventListener('change', azuriraj); });
azuriraj();
})();
</script>
{{end}} {{end}}