Troškovi: zaseban modul (šifrarnik vrsta troškova)
Opcija 1 — katalog dodatnih troškova (Prevoz, Carina, Ambalaža) koji se vezuju za radni nalog i ulaze u cenu koštanja. Zaseban entitet: tabela troskovi (šifra TRO-001…999, naziv, podrazumevana cena, opis), repo, handleri, forma i lista. Stari tip-filter handler uklonjen iz magacina. (Knjiga rashoda firme je zaseban budući modul — Opcija 2.)
This commit is contained in:
@@ -21,6 +21,7 @@ type Handler struct {
|
||||
PutanjaBaze string
|
||||
Artikli db.ArtikalRepository
|
||||
UslugeRepo db.UslugaRepository
|
||||
TroskoviRepo db.TrosakRepository
|
||||
KategorijeRepo db.KategorijaRepository
|
||||
DobavljaciRepo db.DobavljacRepository
|
||||
NabavkeRepo db.NabavkaRepository
|
||||
@@ -86,6 +87,7 @@ func Novi(baza *sql.DB, totpKljuc []byte) *Handler {
|
||||
totpKljuc: totpKljuc,
|
||||
Artikli: sqlite.NoviArtikalRepo(baza),
|
||||
UslugeRepo: sqlite.NoviUslugaRepo(baza),
|
||||
TroskoviRepo: sqlite.NoviTrosakRepo(baza),
|
||||
KategorijeRepo: sqlite.NovaKategorijaRepo(baza),
|
||||
DobavljaciRepo: sqlite.NoviDobavljacRepo(baza),
|
||||
NabavkeRepo: sqlite.NoviNabavkaRepo(baza),
|
||||
@@ -116,6 +118,7 @@ func (h *Handler) reinicijalizujRepozitorijume(novaDB *sql.DB) {
|
||||
h.DB = novaDB
|
||||
h.Artikli = sqlite.NoviArtikalRepo(novaDB)
|
||||
h.UslugeRepo = sqlite.NoviUslugaRepo(novaDB)
|
||||
h.TroskoviRepo = sqlite.NoviTrosakRepo(novaDB)
|
||||
h.KategorijeRepo = sqlite.NovaKategorijaRepo(novaDB)
|
||||
h.DobavljaciRepo = sqlite.NoviDobavljacRepo(novaDB)
|
||||
h.NabavkeRepo = sqlite.NoviNabavkaRepo(novaDB)
|
||||
|
||||
@@ -38,6 +38,7 @@ var saSidebar = []string{
|
||||
"prodaja", "prodaja_detalji", "prodaja_forma",
|
||||
"servis", "servis_arhiva", "servis_forma", "servis_detalji",
|
||||
"usluge", "usluge_forma",
|
||||
"troskovi", "troskovi_forma",
|
||||
}
|
||||
|
||||
// standalone su šabloni bez base layouta
|
||||
|
||||
@@ -42,11 +42,6 @@ func (h *Handler) Magacin(w http.ResponseWriter, r *http.Request) {
|
||||
h.magacinPrikaz(w, r, model.TipProizvod, "magacin", "Magacin", "/magacin")
|
||||
}
|
||||
|
||||
// Troskovi renderuje listu troškova (tip 'trosak')
|
||||
func (h *Handler) Troskovi(w http.ResponseWriter, r *http.Request) {
|
||||
h.magacinPrikaz(w, r, model.TipTrosak, "troskovi", "Troškovi", "/troskovi")
|
||||
}
|
||||
|
||||
// magacinPrikaz je zajednička logika liste artikala, parametrizovana tipom; isti
|
||||
// šablon služi za proizvode, usluge i troškove (razlikuju se samo tip i bazna ruta)
|
||||
func (h *Handler) magacinPrikaz(w http.ResponseWriter, r *http.Request, tip, stranica, naslov, osnovniUrl string) {
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"ntech/internal/db"
|
||||
"ntech/internal/db/sqlite"
|
||||
"ntech/internal/model"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// PodaciTroskovi su podaci za listu troškova
|
||||
type PodaciTroskovi struct {
|
||||
model.PodaciStranice
|
||||
Troskovi []model.Trosak
|
||||
Pretraga string
|
||||
Sacuvano bool
|
||||
Obrisan bool
|
||||
}
|
||||
|
||||
// PodaciFormeTroska su podaci za formu novog/izmenjenog troška
|
||||
type PodaciFormeTroska struct {
|
||||
model.PodaciStranice
|
||||
Trosak model.Trosak
|
||||
Greska string
|
||||
Izmena bool
|
||||
}
|
||||
|
||||
// Troskovi renderuje listu šifrarnika vrsta troškova
|
||||
func (h *Handler) Troskovi(w http.ResponseWriter, r *http.Request) {
|
||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
pretraga := r.URL.Query().Get("pretraga")
|
||||
troskovi, err := h.TroskoviRepo.Lista(r.Context(), db.TrosakFilter{Pretraga: pretraga})
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju troškova", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "troskovi"
|
||||
ps.NaslovStranice = "Troškovi"
|
||||
h.renderujTemplate(w, "troskovi", PodaciTroskovi{
|
||||
PodaciStranice: ps,
|
||||
Troskovi: troskovi,
|
||||
Pretraga: pretraga,
|
||||
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
||||
Obrisan: r.URL.Query().Get("obrisan") == "1",
|
||||
})
|
||||
}
|
||||
|
||||
// NoviTrosak prikazuje praznu formu sa predloženom auto-šifrom (TRO-NNN)
|
||||
func (h *Handler) NoviTrosak(w http.ResponseWriter, r *http.Request) {
|
||||
sifra, err := h.TroskoviRepo.SledecaSifra(r.Context())
|
||||
if err != nil {
|
||||
sifra = "TRO-001"
|
||||
}
|
||||
h.renderujFormuTroska(w, r, model.Trosak{Sifra: sifra}, false, "")
|
||||
}
|
||||
|
||||
// IzmeniTrosak prikazuje formu sa postojećim troškom
|
||||
func (h *Handler) IzmeniTrosak(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID troška", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
trosak, err := h.TroskoviRepo.DohvatiID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Trošak nije pronađen", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
h.renderujFormuTroska(w, r, *trosak, true, "")
|
||||
}
|
||||
|
||||
// SacuvajTrosak prima POST i kreira nov trošak
|
||||
func (h *Handler) SacuvajTrosak(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "artikal.dodaj"); !ok {
|
||||
return
|
||||
}
|
||||
trosak, greska := parseFormuTroska(r)
|
||||
if greska != "" {
|
||||
h.renderujFormuTroska(w, r, trosak, false, greska)
|
||||
return
|
||||
}
|
||||
if _, err := h.TroskoviRepo.Kreiraj(r.Context(), &trosak); err != nil {
|
||||
h.renderujFormuTroska(w, r, trosak, false, "Greška pri čuvanju troška. Pokušajte ponovo.")
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/troskovi?sacuvano=1", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// SacuvajIzmenuTroska prima POST i ažurira postojeći trošak
|
||||
func (h *Handler) SacuvajIzmenuTroska(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "artikal.izmeni"); !ok {
|
||||
return
|
||||
}
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID troška", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
trosak, greska := parseFormuTroska(r)
|
||||
trosak.ID = id
|
||||
if greska != "" {
|
||||
h.renderujFormuTroska(w, r, trosak, true, greska)
|
||||
return
|
||||
}
|
||||
if err := h.TroskoviRepo.Izmeni(r.Context(), &trosak); err != nil {
|
||||
h.renderujFormuTroska(w, r, trosak, true, "Greška pri čuvanju troška. Pokušajte ponovo.")
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/troskovi?sacuvano=1", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// ObrisiTrosak briše trošak po ID-u
|
||||
func (h *Handler) ObrisiTrosak(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "artikal.obrisi"); !ok {
|
||||
return
|
||||
}
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID troška", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := h.TroskoviRepo.Obrisi(r.Context(), id); err != nil {
|
||||
http.Error(w, "Greška pri brisanju troška", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/troskovi?obrisan=1", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// parseFormuTroska čita i validira polja forme troška
|
||||
func parseFormuTroska(r *http.Request) (model.Trosak, string) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return model.Trosak{}, "Greška pri čitanju forme."
|
||||
}
|
||||
naziv := strings.TrimSpace(r.FormValue("naziv"))
|
||||
if naziv == "" {
|
||||
return model.Trosak{}, "Naziv troška je obavezan."
|
||||
}
|
||||
cena, err := strconv.ParseFloat(strings.TrimSpace(r.FormValue("cena")), 64)
|
||||
if err != nil || cena < 0 {
|
||||
cena = 0
|
||||
}
|
||||
return model.Trosak{
|
||||
Sifra: strings.TrimSpace(r.FormValue("sifra")),
|
||||
Naziv: naziv,
|
||||
Cena: cena,
|
||||
Opis: strings.TrimSpace(r.FormValue("opis")),
|
||||
}, ""
|
||||
}
|
||||
|
||||
// renderujFormuTroska prikazuje formu troška (zajedničko za nov i izmenu)
|
||||
func (h *Handler) renderujFormuTroska(w http.ResponseWriter, r *http.Request, trosak model.Trosak, izmena bool, greska string) {
|
||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "troskovi"
|
||||
if izmena {
|
||||
ps.NaslovStranice = "Izmena troška"
|
||||
} else {
|
||||
ps.NaslovStranice = "Nov trošak"
|
||||
}
|
||||
h.renderujTemplate(w, "troskovi_forma", PodaciFormeTroska{
|
||||
PodaciStranice: ps,
|
||||
Trosak: trosak,
|
||||
Greska: greska,
|
||||
Izmena: izmena,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user