From 7e11a349c1a639ee4488450b50df3e1b0179e142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Sat, 27 Jun 2026 00:26:46 +0200 Subject: [PATCH] PDV modul: uslovno prikazivanje + auto cena_konacna + stanje zaliha sa PDV-om MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- internal/db/repository.go | 1 + internal/db/sqlite/izvestaj.go | 11 ++++--- internal/db/sqlite/servis.go | 10 ++++++ internal/handler/izvestaji.go | 18 +++++++---- internal/handler/servis.go | 17 +++++++++- internal/model/izvestaj.go | 2 ++ .../088_popuni_cena_konacna_preuzeto.sql | 8 +++++ web/templates/stranice/dashboard.html | 11 ++++++- web/templates/stranice/magacin.html | 32 ++++++++++++------- web/templates/stranice/prodaja_detalji.html | 8 ++--- web/templates/stranice/stanje_zaliha.html | 19 +++++++---- web/templates/stranice/usluge.html | 4 +-- web/templates/stranice/usluge_forma.html | 2 ++ 13 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 migrations/088_popuni_cena_konacna_preuzeto.sql 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 @@ -
{{ dinariCeli .PrihodOvogMeseca }} din
+
{{ dinariCeli .PrihodOvogMeseca }} din
Prihod ovog meseca
{{ end }} diff --git a/web/templates/stranice/magacin.html b/web/templates/stranice/magacin.html index de4423a..6b07f20 100644 --- a/web/templates/stranice/magacin.html +++ b/web/templates/stranice/magacin.html @@ -79,8 +79,9 @@ Naziv Kategorija Količina - Prodajna - {{if index .Moduli "pdv"}}Sa PDV-om{{end}} + + Cena ▾ bez PDV + Lokacija Akcije @@ -102,8 +103,9 @@ {{if eq .Tip "usluga"}}usluga{{else}}trošak{{end}} {{end}} - {{dinari .ProdajnaCena}} din - {{if index $.Moduli "pdv"}}{{dinari .CenaSaPdv}} din{{end}} + + {{dinari .ProdajnaCena}} din + {{if .Lokacija}}{{.Lokacija}}{{else}}—{{end}} @@ -136,7 +138,7 @@ {{else}} - + {{if or $.Filter.Pretraga $.KategorijaIDStr $.Filter.SamoKriticni}}Za datu pretragu nema stavki u bazi.{{else}}Nema stavki. Dodaj prvu.{{end}} @@ -194,13 +196,9 @@ {{end}}
- Cena: {{dinari .ProdajnaCena}} din + Cena: + {{dinari .ProdajnaCena}} din
- {{if index $.Moduli "pdv"}} -
- Sa PDV: {{dinari .CenaSaPdv}} din -
- {{end}} {{if .Lokacija}}
Lokacija: {{.Lokacija}} @@ -260,6 +258,18 @@ document.body.addEventListener('htmx:afterSwap', function (e) { void rez.offsetHeight; rez.classList.remove('bez-anim'); }); + +// toggle cena: bez PDV ↔ sa PDV +var cenaPdvRezim = false; // false = bez PDV +function toggleCenaPdv() { + cenaPdvRezim = !cenaPdvRezim; + var bez = document.querySelectorAll('.cena-bez-pdv'); + var sa = document.querySelectorAll('.cena-sa-pdv'); + var tgl = document.getElementById('cena-toggle'); + for (var i = 0; i < bez.length; i++) { bez[i].style.display = cenaPdvRezim ? 'none' : ''; } + for (var j = 0; j < sa.length; j++) { sa[j].style.display = cenaPdvRezim ? '' : 'none'; } + if (tgl) tgl.textContent = cenaPdvRezim ? '▾ sa PDV' : '▾ bez PDV'; +} {{end}} diff --git a/web/templates/stranice/prodaja_detalji.html b/web/templates/stranice/prodaja_detalji.html index 4c8bcdb..a0ffc3e 100644 --- a/web/templates/stranice/prodaja_detalji.html +++ b/web/templates/stranice/prodaja_detalji.html @@ -192,9 +192,9 @@ "> Cena/kom - + {{if index .Moduli "pdv"}} PDV % - + {{end}} Ukupno @@ -206,9 +206,9 @@ {{.ArtikalNaziv}} {{.Kolicina}} {{dinari .CenaPoKomadu}} din - + {{if index $.Moduli "pdv"}} {{if .PdvStopa}}{{printf "%.0f" .PdvStopa}}%{{else}}—{{end}} - + {{end}} {{dinari .Ukupno}} din {{end}} diff --git a/web/templates/stranice/stanje_zaliha.html b/web/templates/stranice/stanje_zaliha.html index 91d87f1..33b42a3 100644 --- a/web/templates/stranice/stanje_zaliha.html +++ b/web/templates/stranice/stanje_zaliha.html @@ -14,13 +14,13 @@
-
-
Broj artikala
-
{{.BrojArtikala}}
+
+
Broj artikala
+
{{.BrojArtikala}}
-
-
Ukupna vrednost zalihe
-
{{dinari .UkupnaVrednost}} din
+
+
Ukupna vrednost zalihe
+
{{dinari .UkupnaVrednost}} din
@@ -47,7 +47,9 @@ Min. Nab. cena Prod. cena + {{if index .Moduli "pdv"}}Cena sa PDV{{end}} Vrednost + {{if index .Moduli "pdv"}}Vred. sa PDV{{end}} @@ -60,14 +62,17 @@ {{.KolicinMin}} {{dinari .NabavnaCena}} {{dinari .ProdajnaCena}} + {{if index $.Moduli "pdv"}}{{dinari .CenaSaPdv}}{{end}} {{dinari .VrednostZalihe}} + {{if index $.Moduli "pdv"}}{{dinari .VrednostSaPdv}}{{end}} {{end}} - Ukupna vrednost zalihe + Ukupna vrednost zalihe {{dinari .UkupnaVrednost}} din + {{if index .Moduli "pdv"}}{{dinari .UkupnaVrednostSaPdv}} din{{end}} diff --git a/web/templates/stranice/usluge.html b/web/templates/stranice/usluge.html index 60daa89..7abaf96 100644 --- a/web/templates/stranice/usluge.html +++ b/web/templates/stranice/usluge.html @@ -40,7 +40,7 @@ Naziv JM Cena - PDV + {{if index .Moduli "pdv"}}PDV{{end}} Akcije @@ -51,7 +51,7 @@ {{.Naziv}} {{.JedinicaMere}} {{dinari .Cena}} din - {{.PdvStopa}}% + {{if index $.Moduli "pdv"}}{{.PdvStopa}}%{{end}}
Izmeni diff --git a/web/templates/stranice/usluge_forma.html b/web/templates/stranice/usluge_forma.html index 247983f..4ad39ad 100644 --- a/web/templates/stranice/usluge_forma.html +++ b/web/templates/stranice/usluge_forma.html @@ -50,6 +50,7 @@
+ {{if index .Moduli "pdv"}}
{{$pdv := .Usluga.PdvStopa}} @@ -59,6 +60,7 @@
+ {{end}}