diff --git a/internal/db/repository.go b/internal/db/repository.go
index 0d13869..58c4465 100644
--- a/internal/db/repository.go
+++ b/internal/db/repository.go
@@ -200,6 +200,7 @@ type ServisRepository interface {
AzurirajNalazDijagnostike(ctx context.Context, id int64, tekst string) error
AzurirajUradjeno(ctx context.Context, id int64, tekst string) error
AzurirajCenaDijagnostike(ctx context.Context, id int64, cena float64) error
+ AzurirajCenuKonacnu(ctx context.Context, id int64, cena float64) error
OdbijPopravku(ctx context.Context, id int64, cena float64) error
AzurirajKomentarKlijenta(ctx context.Context, id int64, tekst string) error
SacuvajOdlukuKlijenta(ctx context.Context, id int64, odluka string, odgovor string) error
diff --git a/internal/db/sqlite/izvestaj.go b/internal/db/sqlite/izvestaj.go
index b9ae6f1..28104c3 100644
--- a/internal/db/sqlite/izvestaj.go
+++ b/internal/db/sqlite/izvestaj.go
@@ -27,7 +27,7 @@ func (r *sqliteIzvestajRepo) BrojArtikala(ctx context.Context) (int, error) {
func (r *sqliteIzvestajRepo) BrojAktivnihServisa(ctx context.Context) (int, error) {
var n int
err := r.db.QueryRowContext(ctx,
- `SELECT COUNT(*) FROM servisni_nalozi WHERE status != 'Završeno'`).Scan(&n)
+ `SELECT COUNT(*) FROM servisni_nalozi WHERE status NOT IN ('Završeno', 'Preuzeto')`).Scan(&n)
if err != nil {
return 0, fmt.Errorf("ntech: izvestaj.BrojAktivnihServisa: %w", err)
}
@@ -145,7 +145,7 @@ func (r *sqliteIzvestajRepo) MesecniPrihodProdaja(ctx context.Context) ([]model.
func (r *sqliteIzvestajRepo) MesecniPrihodServis(ctx context.Context) ([]model.MesecniIznos, error) {
return r.mesecniPrihod(ctx, `
- SELECT substr(datum_zavrsetka, 1, 7), SUM(cena_konacna)
+ SELECT substr(datum_zavrsetka, 1, 7), SUM(COALESCE(cena_konacna, 0))
FROM servisni_nalozi
WHERE datum_zavrsetka IS NOT NULL
AND status = 'Preuzeto'
@@ -269,7 +269,9 @@ func (r *sqliteIzvestajRepo) StanjeZaliha(ctx context.Context) ([]model.StanjeZa
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
+ a.cena_sa_pdv,
+ a.kolicina * a.nabavna_cena AS vrednost,
+ a.kolicina * a.cena_sa_pdv AS vrednost_sa_pdv
FROM artikli a
LEFT JOIN kategorije k ON k.id = a.kategorija_id
ORDER BY k.naziv ASC, a.naziv ASC`,
@@ -283,7 +285,8 @@ func (r *sqliteIzvestajRepo) StanjeZaliha(ctx context.Context) ([]model.StanjeZa
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 {
+ &s.Kolicina, &s.KolicinMin, &s.NabavnaCena, &s.ProdajnaCena,
+ &s.CenaSaPdv, &s.VrednostZalihe, &s.VrednostSaPdv); err != nil {
return nil, fmt.Errorf("ntech: IzvestajRepo.StanjeZaliha: scan: %w", err)
}
lista = append(lista, s)
diff --git a/internal/db/sqlite/servis.go b/internal/db/sqlite/servis.go
index 0756850..c61f2a9 100644
--- a/internal/db/sqlite/servis.go
+++ b/internal/db/sqlite/servis.go
@@ -339,6 +339,16 @@ func (r *ServisRepo) AzurirajCenaDijagnostike(ctx context.Context, id int64, cen
return nil
}
+func (r *ServisRepo) AzurirajCenuKonacnu(ctx context.Context, id int64, cena float64) error {
+ _, err := r.db.ExecContext(ctx,
+ "UPDATE servisni_nalozi SET cena_konacna = ? WHERE id = ?", cena, id,
+ )
+ if err != nil {
+ return fmt.Errorf("ntech: ServisRepo.AzurirajCenuKonacnu: %w", err)
+ }
+ return nil
+}
+
// AzurirajKomentarKlijenta čuva poruku klijenta uz prihvatanje/odbijanje predloga
func (r *ServisRepo) AzurirajKomentarKlijenta(ctx context.Context, id int64, tekst string) error {
_, err := r.db.ExecContext(ctx,
diff --git a/internal/handler/izvestaji.go b/internal/handler/izvestaji.go
index f519073..a599a6a 100644
--- a/internal/handler/izvestaji.go
+++ b/internal/handler/izvestaji.go
@@ -264,9 +264,10 @@ func (h *Handler) PrometniListMagacina(w http.ResponseWriter, r *http.Request) {
// PodaciStanjaZaliha su podaci za izveštaj o stanju zaliha
type PodaciStanjaZaliha struct {
model.PodaciStranice
- Zalihe []model.StanjeZalihaRed
- UkupnaVrednost float64
- BrojArtikala int
+ Zalihe []model.StanjeZalihaRed
+ UkupnaVrednost float64
+ UkupnaVrednostSaPdv float64
+ BrojArtikala int
}
// StanjeZalihaIzvestaj renderuje izveštaj o trenutnom stanju zaliha
@@ -282,8 +283,10 @@ func (h *Handler) StanjeZalihaIzvestaj(w http.ResponseWriter, r *http.Request) {
}
var ukupnaVrednost float64
+ var ukupnaVrednostSaPdv float64
for _, z := range zalihe {
ukupnaVrednost += z.VrednostZalihe
+ ukupnaVrednostSaPdv += z.VrednostSaPdv
}
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
@@ -292,10 +295,11 @@ func (h *Handler) StanjeZalihaIzvestaj(w http.ResponseWriter, r *http.Request) {
ps.NaslovStranice = "Stanje zaliha"
h.renderujTemplate(w, "stanje_zaliha", PodaciStanjaZaliha{
- PodaciStranice: ps,
- Zalihe: zalihe,
- UkupnaVrednost: ukupnaVrednost,
- BrojArtikala: len(zalihe),
+ PodaciStranice: ps,
+ Zalihe: zalihe,
+ UkupnaVrednost: ukupnaVrednost,
+ UkupnaVrednostSaPdv: ukupnaVrednostSaPdv,
+ BrojArtikala: len(zalihe),
})
}
diff --git a/internal/handler/servis.go b/internal/handler/servis.go
index fecade2..66479f7 100644
--- a/internal/handler/servis.go
+++ b/internal/handler/servis.go
@@ -1969,8 +1969,23 @@ func (h *Handler) PromeniStatus(w http.ResponseWriter, r *http.Request) {
return
}
- // pri prelasku u Preuzeto — sačuvaj naplatu i fiskalizuj
+ // pri prelasku u Preuzeto — auto-izračunaj cenu_konacnu ako nije uneta, pa sačuvaj naplatu i fiskalizuj
if noviStatus == model.StatusPreuzeto {
+ nalog, _ := h.ServisRepo.DohvatiID(r.Context(), id)
+ if nalog != nil && nalog.CenaKonacna == nil {
+ // auto-izračunaj: dijagnostika + radovi + delovi
+ radovi, _ := h.ServisniRadoviRepo.DohvatiZaNalog(r.Context(), id)
+ delovi, _ := h.ServisniDeloviRepo.DohvatiZaNalog(r.Context(), id)
+ ukupno := nalog.CenaDijagnostike
+ for _, rad := range radovi {
+ ukupno += rad.Ukupno()
+ }
+ for _, d := range delovi {
+ ukupno += d.Ukupno()
+ }
+ h.ServisRepo.AzurirajCenuKonacnu(r.Context(), id, ukupno)
+ nalog.CenaKonacna = &ukupno
+ }
nacin := strings.TrimSpace(r.FormValue("nacin_placanja"))
if nacin == "" {
nacin = "Gotovina"
diff --git a/internal/model/izvestaj.go b/internal/model/izvestaj.go
index aced473..c210cfd 100644
--- a/internal/model/izvestaj.go
+++ b/internal/model/izvestaj.go
@@ -81,5 +81,7 @@ type StanjeZalihaRed struct {
KolicinMin int
NabavnaCena float64
ProdajnaCena float64
+ CenaSaPdv float64
VrednostZalihe float64 // kolicina × nabavna_cena
+ VrednostSaPdv float64 // kolicina × cena_sa_pdv
}
diff --git a/migrations/088_popuni_cena_konacna_preuzeto.sql b/migrations/088_popuni_cena_konacna_preuzeto.sql
new file mode 100644
index 0000000..598e6a0
--- /dev/null
+++ b/migrations/088_popuni_cena_konacna_preuzeto.sql
@@ -0,0 +1,8 @@
+-- Popuni cena_konacna za postojeće „Preuzeto" naloge:
+-- dijagnostika + radovi + delovi
+UPDATE servisni_nalozi
+SET cena_konacna = COALESCE(cena_dijagnostike, 0)
+ + COALESCE((SELECT SUM(cena_komada * kolicina) FROM servis_radovi WHERE nalog_id = servisni_nalozi.id), 0)
+ + COALESCE((SELECT SUM(cena_komada * kolicina) FROM servisni_delovi WHERE nalog_id = servisni_nalozi.id), 0)
+WHERE status = 'Preuzeto'
+ AND cena_konacna IS NULL;
diff --git a/web/templates/stranice/dashboard.html b/web/templates/stranice/dashboard.html
index d1e8c45..0658537 100644
--- a/web/templates/stranice/dashboard.html
+++ b/web/templates/stranice/dashboard.html
@@ -12,6 +12,15 @@
.dash-kartica:nth-child(1) { animation-delay: 0.20s; }
.dash-kartica:nth-child(2) { animation-delay: 0.28s; }
.dash-kartica:nth-child(3) { animation-delay: 0.36s; }
+ /* prihod cifra — sakrivena, prikazuje se posle 1s hovera */
+ .prihod-cifra {
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ }
+ .dash-stat:hover .prihod-cifra {
+ opacity: 1;
+ transition-delay: 1s;
+ }
{{ end }}
@@ -50,7 +59,7 @@