Izveštaji: prometni list magacina i stanje zaliha
- Prometni list: sve promene magacina po periodu (filter od/do datuma), bojama označeni tipovi promena (ulaz/prodaja/servis/povraćaj/korekcija) - Stanje zaliha: svi artikli sa stanjem, min. količinom, cenama i ukupnom vrednošću zalihe; kritične zalihe istaknute crvenom bojom - Brzi linkovi na oba izveštaja sa glavne stranice izveštaja
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user