diff --git a/cmd/ntech/main.go b/cmd/ntech/main.go index 9e880a8..7b8edac 100644 --- a/cmd/ntech/main.go +++ b/cmd/ntech/main.go @@ -342,6 +342,8 @@ func main() { r.With(doz("servis.izmeni")).Post("/servis/{id}/delovi", h.DodajDeloNalogu) r.With(doz("servis.izmeni")).Post("/servis/{id}/delovi/{deo_id}/obrisi", h.ObrisiDeloNaloga) r.Get("/izvestaji", h.Izvestaji) + r.Get("/izvestaji/prometni-list", h.PrometniListMagacina) + r.Get("/izvestaji/stanje-zaliha", h.StanjeZalihaIzvestaj) r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja", h.Prodaja) r.Get("/prodaja/nova", h.NovaProdaja) r.With(doz("prodaja.dodaj")).Post("/prodaja/nova", h.SacuvajProdaju) diff --git a/internal/db/repository.go b/internal/db/repository.go index be52b6e..bd18afa 100644 --- a/internal/db/repository.go +++ b/internal/db/repository.go @@ -222,6 +222,9 @@ type IzvestajRepository interface { StariOtvoreniNalozi(ctx context.Context) ([]model.StariNalogRed, error) TopArtikli(ctx context.Context, limit int) ([]model.TopArtikalRed, error) TopKlijenti(ctx context.Context, limit int) ([]model.TopKlijentRed, error) + // magacinski izveštaji + PrometniList(ctx context.Context, od, do time.Time) ([]model.PrometniRed, error) + StanjeZaliha(ctx context.Context) ([]model.StanjeZalihaRed, error) } // PodsetnikRepository definiše operacije nad podsetnicima diff --git a/internal/db/sqlite/izvestaj.go b/internal/db/sqlite/izvestaj.go index 4999d98..1d636f0 100644 --- a/internal/db/sqlite/izvestaj.go +++ b/internal/db/sqlite/izvestaj.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "time" "ntech/internal/model" ) @@ -232,3 +233,59 @@ func (r *sqliteIzvestajRepo) TopKlijenti(ctx context.Context, limit int) ([]mode } return lista, rows.Err() } + +// PrometniList vraća sve magacinske promene u zadatom periodu +func (r *sqliteIzvestajRepo) PrometniList(ctx context.Context, od, do time.Time) ([]model.PrometniRed, error) { + rows, err := r.db.QueryContext(ctx, ` + SELECT mp.datum, a.naziv, COALESCE(a.sifra, ''), mp.tip_promene, + mp.promena_kolicine, mp.stanje_pre, mp.stanje_posle, + COALESCE(mp.napomena, '') + FROM magacinske_promene mp + JOIN artikli a ON a.id = mp.artikal_id + WHERE DATE(mp.datum) >= DATE(?) AND DATE(mp.datum) <= DATE(?) + ORDER BY mp.datum ASC`, + od.Format("2006-01-02"), do.Format("2006-01-02"), + ) + if err != nil { + return nil, fmt.Errorf("ntech: IzvestajRepo.PrometniList: %w", err) + } + defer rows.Close() + + var lista []model.PrometniRed + for rows.Next() { + var p model.PrometniRed + if err := rows.Scan(&p.Datum, &p.ArtikalNaziv, &p.ArtikalSifra, &p.TipPromene, + &p.PromenaKolicine, &p.StanjePre, &p.StanjePosle, &p.Napomena); err != nil { + return nil, fmt.Errorf("ntech: IzvestajRepo.PrometniList: scan: %w", err) + } + lista = append(lista, p) + } + return lista, rows.Err() +} + +// StanjeZaliha vraća trenutno stanje svih artikala sa vrednostima +func (r *sqliteIzvestajRepo) StanjeZaliha(ctx context.Context) ([]model.StanjeZalihaRed, error) { + rows, err := r.db.QueryContext(ctx, ` + SELECT a.naziv, COALESCE(a.sifra, ''), COALESCE(k.naziv, ''), + a.kolicina, a.kolicina_min, a.nabavna_cena, a.prodajna_cena, + a.kolicina * a.nabavna_cena AS vrednost + FROM artikli a + LEFT JOIN kategorije k ON k.id = a.kategorija_id + ORDER BY k.naziv ASC, a.naziv ASC`, + ) + if err != nil { + return nil, fmt.Errorf("ntech: IzvestajRepo.StanjeZaliha: %w", err) + } + defer rows.Close() + + var lista []model.StanjeZalihaRed + for rows.Next() { + var s model.StanjeZalihaRed + if err := rows.Scan(&s.Naziv, &s.Sifra, &s.Kategorija, + &s.Kolicina, &s.KolicinMin, &s.NabavnaCena, &s.ProdajnaCena, &s.VrednostZalihe); err != nil { + return nil, fmt.Errorf("ntech: IzvestajRepo.StanjeZaliha: scan: %w", err) + } + lista = append(lista, s) + } + return lista, rows.Err() +} diff --git a/internal/handler/izvestaji.go b/internal/handler/izvestaji.go index 8f5e1d0..e87e2ae 100644 --- a/internal/handler/izvestaji.go +++ b/internal/handler/izvestaji.go @@ -202,3 +202,96 @@ func (h *Handler) Izvestaji(w http.ResponseWriter, r *http.Request) { h.renderujTemplate(w, "izvestaji", podaci) } + +// PodaciPrometногLista su podaci za prometni list magacina +type PodaciPrometногLista struct { + model.PodaciStranice + Promene []model.PrometniRed + Od string + Do string + Ukupno int +} + +// PrometniListMagacina renderuje prometni list magacina za odabrani period +func (h *Handler) PrometniListMagacina(w http.ResponseWriter, r *http.Request) { + if _, ok := h.zahtevajDozvolu(w, r, "izvestaj.pregled"); !ok { + return + } + + danas := time.Now() + odStr := r.URL.Query().Get("od") + doStr := r.URL.Query().Get("do") + + if odStr == "" { + odStr = danas.Format("2006-01-02")[:7] + "-01" + } + if doStr == "" { + doStr = danas.Format("2006-01-02") + } + + od, err := time.Parse("2006-01-02", odStr) + if err != nil { + od = time.Now() + } + do, err := time.Parse("2006-01-02", doStr) + if err != nil { + do = time.Now() + } + + promene, err := h.IzvestajRepo.PrometniList(r.Context(), od, do) + if err != nil { + slog.Error("prometni list: greška", "error", err) + promene = nil + } + + podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "izvestaji" + ps.NaslovStranice = "Prometni list" + + h.renderujTemplate(w, "prometni_list", PodaciPrometногLista{ + PodaciStranice: ps, + Promene: promene, + Od: odStr, + Do: doStr, + Ukupno: len(promene), + }) +} + +// PodaciStanjaZaliha su podaci za izveštaj o stanju zaliha +type PodaciStanjaZaliha struct { + model.PodaciStranice + Zalihe []model.StanjeZalihaRed + UkupnaVrednost float64 + BrojArtikala int +} + +// StanjeZalihaIzvestaj renderuje izveštaj o trenutnom stanju zaliha +func (h *Handler) StanjeZalihaIzvestaj(w http.ResponseWriter, r *http.Request) { + if _, ok := h.zahtevajDozvolu(w, r, "izvestaj.pregled"); !ok { + return + } + + zalihe, err := h.IzvestajRepo.StanjeZaliha(r.Context()) + if err != nil { + slog.Error("stanje zaliha: greška", "error", err) + zalihe = nil + } + + var ukupnaVrednost float64 + for _, z := range zalihe { + ukupnaVrednost += z.VrednostZalihe + } + + podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "izvestaji" + ps.NaslovStranice = "Stanje zaliha" + + h.renderujTemplate(w, "stanje_zaliha", PodaciStanjaZaliha{ + PodaciStranice: ps, + Zalihe: zalihe, + UkupnaVrednost: ukupnaVrednost, + BrojArtikala: len(zalihe), + }) +} diff --git a/internal/handler/kes.go b/internal/handler/kes.go index b6db5e2..3b68b8d 100644 --- a/internal/handler/kes.go +++ b/internal/handler/kes.go @@ -19,7 +19,7 @@ var saSidebar = []string{ "admin_korisnici", "admin_profil", "admin_login_istorija", "admin_dozvole", "dashboard", "dobavljaci", "dobavljac_forma", - "izvestaji", + "izvestaji", "prometni_list", "stanje_zaliha", "kategorije", "klijenti", "klijent_forma", "magacin", "magacin_forma", "magacin_kartica", diff --git a/internal/model/izvestaj.go b/internal/model/izvestaj.go index 39f0b77..ad3fc30 100644 --- a/internal/model/izvestaj.go +++ b/internal/model/izvestaj.go @@ -58,3 +58,27 @@ type TopKlijentRed struct { UkupnoVrednost float64 BrojNaloga int } + +// PrometniRed je jedan red prometnog lista magacina +type PrometniRed struct { + Datum time.Time + ArtikalNaziv string + ArtikalSifra string + TipPromene string + PromenaKolicine int + StanjePre int + StanjePosle int + Napomena string +} + +// StanjeZalihaRed je jedan red izveštaja o stanju zaliha +type StanjeZalihaRed struct { + Naziv string + Sifra string + Kategorija string + Kolicina int + KolicinMin int + NabavnaCena float64 + ProdajnaCena float64 + VrednostZalihe float64 // kolicina × nabavna_cena +} diff --git a/web/templates/stranice/izvestaji.html b/web/templates/stranice/izvestaji.html index e1f93f6..3ed14f1 100644 --- a/web/templates/stranice/izvestaji.html +++ b/web/templates/stranice/izvestaji.html @@ -22,6 +22,12 @@ {{define "sadrzaj"}}
+ +
+ Prometni list magacina + Stanje zaliha +
+
diff --git a/web/templates/stranice/prometni_list.html b/web/templates/stranice/prometni_list.html new file mode 100644 index 0000000..b799300 --- /dev/null +++ b/web/templates/stranice/prometni_list.html @@ -0,0 +1,88 @@ +{{template "base" .}} + +{{define "naslov"}}Prometni list — NTech{{end}} + +{{define "sadrzaj"}} +
+ + +
+
+
+ + +
+
+ + +
+ + ← Izveštaji +
+
+ + +
+
+ Prometni list magacina + {{.Od}} — {{.Do}} · {{.Ukupno}} promena +
+ + {{if not .Promene}} +
+ Nema promena u odabranom periodu. +
+ {{else}} +
+ + + + + + + + + + + + + + + {{range .Promene}} + + + + + + + + + + + {{end}} + +
DatumArtikalŠifraVrstaPromenaPrePosleNapomena
+ {{.Datum.Format "02.01.2006. 15:04"}} + {{.ArtikalNaziv}}{{if .ArtikalSifra}}{{.ArtikalSifra}}{{else}}—{{end}} + {{if eq .TipPromene "ulaz_nabavka"}} + Ulaz + {{else if eq .TipPromene "izlaz_prodaja"}} + Prodaja + {{else if eq .TipPromene "izlaz_servis"}} + Servis + {{else if eq .TipPromene "povracaj"}} + Povraćaj + {{else if eq .TipPromene "korekcija"}} + Korekcija + {{else}} + {{.TipPromene}} + {{end}} + + {{if gt .PromenaKolicine 0}}+{{end}}{{.PromenaKolicine}} + {{.StanjePre}}{{.StanjePosle}}{{.Napomena}}
+
+ {{end}} +
+ +
+{{end}} diff --git a/web/templates/stranice/stanje_zaliha.html b/web/templates/stranice/stanje_zaliha.html new file mode 100644 index 0000000..afdc950 --- /dev/null +++ b/web/templates/stranice/stanje_zaliha.html @@ -0,0 +1,79 @@ +{{template "base" .}} + +{{define "naslov"}}Stanje zaliha — NTech{{end}} + +{{define "sadrzaj"}} +
+ + +
+ +
+
+
Broj artikala
+
{{.BrojArtikala}}
+
+
+
Ukupna vrednost zalihe
+
{{printf "%.2f" .UkupnaVrednost}} din
+
+
+
+ + +
+
+ Stanje zaliha +
+ + {{if not .Zalihe}} +
+ Nema artikala u magacinu. +
+ {{else}} +
+ + + + + + + + + + + + + + + {{range .Zalihe}} + + + + + + + + + + + {{end}} + + + + + + + +
ArtikalŠifraKategorijaStanjeMin.Nab. cenaProd. cenaVrednost
{{.Naziv}}{{if .Sifra}}{{.Sifra}}{{else}}—{{end}}{{if .Kategorija}}{{.Kategorija}}{{else}}—{{end}}{{.Kolicina}}{{.KolicinMin}}{{printf "%.2f" .NabavnaCena}}{{printf "%.2f" .ProdajnaCena}}{{printf "%.2f" .VrednostZalihe}}
Ukupna vrednost zalihe{{printf "%.2f" .UkupnaVrednost}} din
+
+ {{end}} +
+ +
+{{end}}