PDV modul: uslovno prikazivanje + auto cena_konacna + stanje zaliha sa PDV-om

- PDV pravilo (Moduli.pdv) primenjeno na: magacin, usluge, prodaja_detalji, usluge_forma
- magacin: toggle cena bez/sa PDV u zaglavlju kolone, jedna kolona
- stanje_zaliha: Cena sa PDV i Vred. sa PDV kolone, tamne kartice za zaglavlje
- PromeniStatus: auto-izračunavanje cena_konacna (dijagnostika+radovi+delovi) pri Preuzeto
- migrations/088: popravlja postojeće Preuzeto naloge bez cene
- dashboard: prihod cifra sakrivena, hover 1s → prikaz
- izvestaji: aktivni servisi bez Preuzeto, COALESCE u SQL, 0→—
- izvestaji.html: vraćeno — umesto 0 din
This commit is contained in:
2026-06-27 00:26:46 +02:00
parent 34a427e046
commit 6a1ea08dc3
13 changed files with 106 additions and 37 deletions
+1
View File
@@ -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
+7 -4
View File
@@ -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)
+10
View File
@@ -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,
+11 -7
View File
@@ -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),
})
}
+16 -1
View File
@@ -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"
+2
View File
@@ -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
}