From 432a559902835d0be6fb812d0dd0a102aa32bf8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Tue, 30 Jun 2026 21:13:16 +0200 Subject: [PATCH] =?UTF-8?q?KPO:=20knjiga=20o=20ostvarenom=20prometu=20?= =?UTF-8?q?=E2=80=94=20lista,=20ru=C4=8Dni=20unos,=20backfill,=20auto-upis?= =?UTF-8?q?=20iz=20prodaje=20i=20servisa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/ntech/main.go | 6 + internal/db/repository.go | 9 + internal/db/sqlite/kpo.go | 124 ++++++++++++++ internal/handler/handler.go | 3 + internal/handler/kes.go | 1 + internal/handler/kpo.go | 235 ++++++++++++++++++++++++++ internal/handler/prodaja.go | 24 +++ internal/handler/servis.go | 18 ++ internal/model/kpo.go | 24 +++ migrations/094_kpo.sql | 20 +++ web/templates/komponente/sidebar.html | 12 +- web/templates/stranice/kpo.html | 116 +++++++++++++ web/templates/stranice/kpo_forma.html | 74 ++++++++ 13 files changed, 664 insertions(+), 2 deletions(-) create mode 100644 internal/db/sqlite/kpo.go create mode 100644 internal/handler/kpo.go create mode 100644 internal/model/kpo.go create mode 100644 migrations/094_kpo.sql create mode 100644 web/templates/stranice/kpo.html create mode 100644 web/templates/stranice/kpo_forma.html diff --git a/cmd/ntech/main.go b/cmd/ntech/main.go index 8fa663e..189062f 100644 --- a/cmd/ntech/main.go +++ b/cmd/ntech/main.go @@ -320,6 +320,12 @@ func main() { r.With(modul("pdv"), doz("pdv.obrisi")).Post("/pdv/kpr/obrisi/{id}", h.ObrisiPdvKpr) r.With(modul("pdv"), doz("pdv.dodaj")).Post("/pdv/kpr/backfill-nabavke", h.KprBackfillNabavke) r.With(modul("pdv")).Get("/pdv/obracun", h.PdvObracunStranica) + // KPO — knjiga o ostvarenom prometu (paušalci i prosto knjigovodstvo) + r.With(modul("kpo")).Get("/kpo", h.Kpo) + r.With(modul("kpo"), doz("kpo.dodaj")).Get("/kpo/nova", h.NoviKpoUnos) + r.With(modul("kpo"), doz("kpo.dodaj")).Post("/kpo/nova", h.SacuvajKpoUnos) + r.With(modul("kpo"), doz("kpo.obrisi")).Post("/kpo/obrisi/{id}", h.ObrisiKpoUnos) + r.With(modul("kpo"), doz("kpo.dodaj")).Post("/kpo/backfill", h.KpoBackfill) r.Get("/magacin", h.Magacin) r.Get("/usluge", h.Usluge) r.Get("/usluge/nova", h.NovaUsluga) diff --git a/internal/db/repository.go b/internal/db/repository.go index d26e44f..eafcdbc 100644 --- a/internal/db/repository.go +++ b/internal/db/repository.go @@ -385,3 +385,12 @@ type FiskalRepository interface { DohvatiPoServisu(ctx context.Context, servisID int64) (*model.FiskalniRacun, error) OznačiKaoStorniran(ctx context.Context, id int64) error } + +// KpoRepository definiše operacije nad knjigom o ostvarenom prometu. +type KpoRepository interface { + Lista(ctx context.Context, od, do time.Time) ([]model.KpoZapis, error) + Kreiraj(ctx context.Context, z *model.KpoZapis) (int64, error) + Obrisi(ctx context.Context, id int64) error + PostojiZaIzvor(ctx context.Context, izvor string, izvorID int64) (bool, error) + ObrisiPoIzvoru(ctx context.Context, izvor string, izvorID int64) error +} diff --git a/internal/db/sqlite/kpo.go b/internal/db/sqlite/kpo.go new file mode 100644 index 0000000..c530e95 --- /dev/null +++ b/internal/db/sqlite/kpo.go @@ -0,0 +1,124 @@ +package sqlite + +import ( + "context" + "database/sql" + "fmt" + "time" + + "ntech/internal/model" +) + +// KpoRepo je SQLite implementacija KpoRepository interfejsa. +type KpoRepo struct { + db *sql.DB +} + +// NoviKpoRepo kreira novi KpoRepo. +func NoviKpoRepo(db *sql.DB) *KpoRepo { + return &KpoRepo{db: db} +} + +// Lista vraća KPO unose u zadatom periodu (po datumu prometa); nulti datum = bez granice. +func (r *KpoRepo) Lista(ctx context.Context, od, do time.Time) ([]model.KpoZapis, error) { + q := `SELECT id, datum_prometa, redni_broj, broj_dokumenta, opis, prihod, + nacin_placanja, napomena, izvor, izvor_id, datum_unosa + FROM kpo_unosi WHERE 1=1` + var args []any + if !od.IsZero() { + q += " AND datum_prometa >= ?" + args = append(args, od.Format("2006-01-02")) + } + if !do.IsZero() { + q += " AND datum_prometa <= ?" + args = append(args, do.Format("2006-01-02")) + } + q += " ORDER BY datum_prometa, id" + + redovi, err := r.db.QueryContext(ctx, q, args...) + if err != nil { + return nil, fmt.Errorf("ntech: KpoRepo.Lista: %w", err) + } + defer redovi.Close() + + var rezultat []model.KpoZapis + for redovi.Next() { + var z model.KpoZapis + var opis, nacinPlacanja, napomena sql.NullString + var redniBroj sql.NullInt64 + var izvorID sql.NullInt64 + + err := redovi.Scan( + &z.ID, &z.DatumPrometa, &redniBroj, &z.BrojDokumenta, &opis, &z.Prihod, + &nacinPlacanja, &napomena, &z.Izvor, &izvorID, &z.DatumUnosa, + ) + if err != nil { + return nil, fmt.Errorf("ntech: KpoRepo.Lista: scan: %w", err) + } + if redniBroj.Valid { + n := int(redniBroj.Int64) + z.RedniBroj = &n + } + z.Opis = opis.String + z.NacinPlacanja = nacinPlacanja.String + z.Napomena = napomena.String + if izvorID.Valid { + z.IzvorID = &izvorID.Int64 + } + rezultat = append(rezultat, z) + } + return rezultat, redovi.Err() +} + +// Kreiraj upisuje novi KPO zapis i vraća njegov ID. +func (r *KpoRepo) Kreiraj(ctx context.Context, z *model.KpoZapis) (int64, error) { + rez, err := r.db.ExecContext(ctx, ` + INSERT INTO kpo_unosi + (datum_prometa, broj_dokumenta, opis, prihod, nacin_placanja, napomena, izvor, izvor_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + z.DatumPrometa.Format("2006-01-02"), + z.BrojDokumenta, + nullString(z.Opis), + z.Prihod, + nullString(z.NacinPlacanja), + nullString(z.Napomena), + izvorIliRucno(z.Izvor), + izvorIDArg(z.IzvorID), + ) + if err != nil { + return 0, fmt.Errorf("ntech: KpoRepo.Kreiraj: %w", err) + } + id, err := rez.LastInsertId() + if err != nil { + return 0, fmt.Errorf("ntech: KpoRepo.Kreiraj: id: %w", err) + } + return id, nil +} + +// Obrisi briše KPO zapis po ID-ju. +func (r *KpoRepo) Obrisi(ctx context.Context, id int64) error { + if _, err := r.db.ExecContext(ctx, "DELETE FROM kpo_unosi WHERE id = ?", id); err != nil { + return fmt.Errorf("ntech: KpoRepo.Obrisi: %w", err) + } + return nil +} + +// PostojiZaIzvor vraća true ako postoji bar jedan KPO zapis za dati izvor i izvorID. +func (r *KpoRepo) PostojiZaIzvor(ctx context.Context, izvor string, izvorID int64) (bool, error) { + var n int + err := r.db.QueryRowContext(ctx, + "SELECT COUNT(*) FROM kpo_unosi WHERE izvor = ? AND izvor_id = ?", izvor, izvorID, + ).Scan(&n) + if err != nil { + return false, fmt.Errorf("ntech: KpoRepo.PostojiZaIzvor: %w", err) + } + return n > 0, nil +} + +// ObrisiPoIzvoru briše KPO zapise vezane za dati izvor (npr. pri stornu prodaje). +func (r *KpoRepo) ObrisiPoIzvoru(ctx context.Context, izvor string, izvorID int64) error { + if _, err := r.db.ExecContext(ctx, "DELETE FROM kpo_unosi WHERE izvor = ? AND izvor_id = ?", izvor, izvorID); err != nil { + return fmt.Errorf("ntech: KpoRepo.ObrisiPoIzvoru: %w", err) + } + return nil +} diff --git a/internal/handler/handler.go b/internal/handler/handler.go index f2f7ed2..aa81791 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -46,6 +46,7 @@ type Handler struct { PdvKprRepo db.PdvKprRepository NivelacijaRepo db.NivelacijaRepository FiskalRepo db.FiskalRepository + KpoRepo db.KpoRepository Verzija string JelDemo bool AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju) @@ -115,6 +116,7 @@ func Novi(baza *sql.DB, totpKljuc []byte) *Handler { PdvKprRepo: sqlite.NoviPdvKprRepo(baza), NivelacijaRepo: sqlite.NoviNivelacijaRepo(baza), FiskalRepo: sqlite.NoviFiskalRepo(baza), + KpoRepo: sqlite.NoviKpoRepo(baza), } } @@ -149,6 +151,7 @@ func (h *Handler) reinicijalizujRepozitorijume(novaDB *sql.DB) { h.PdvKprRepo = sqlite.NoviPdvKprRepo(novaDB) h.NivelacijaRepo = sqlite.NoviNivelacijaRepo(novaDB) h.FiskalRepo = sqlite.NoviFiskalRepo(novaDB) + h.KpoRepo = sqlite.NoviKpoRepo(novaDB) } // modulUkljucen vraća da li je zakonski modul (npr. „pdv") uključen za firmu prema profilu. diff --git a/internal/handler/kes.go b/internal/handler/kes.go index 6685252..554ce5d 100644 --- a/internal/handler/kes.go +++ b/internal/handler/kes.go @@ -32,6 +32,7 @@ var saSidebar = []string{ "pdv_kir", "pdv_kir_forma", "pdv_kir_dnevni_pazar", "pdv_kpr", "pdv_kpr_forma", "pdv_obracun", + "kpo", "kpo_forma", "nivelacije", "podsetnici", "podsetnik_forma", "profil_tema", diff --git a/internal/handler/kpo.go b/internal/handler/kpo.go new file mode 100644 index 0000000..149a736 --- /dev/null +++ b/internal/handler/kpo.go @@ -0,0 +1,235 @@ +package handler + +import ( + "fmt" + "log/slog" + "net/http" + "strings" + "time" + + "ntech/internal/db/sqlite" + "ntech/internal/middleware" + "ntech/internal/model" + + "github.com/go-chi/chi/v5" +) + +// PodaciKpo su podaci za pregled KPO knjige +type PodaciKpo struct { + model.PodaciStranice + Zapisi []model.KpoZapis + Sume model.KpoSume + Od string + Do string +} + +// PodaciKpoForma su podaci za formu ručnog unosa u KPO +type PodaciKpoForma struct { + model.PodaciStranice + Danas string +} + +// Kpo renderuje pregled knjige o ostvarenom prometu sa sumama za period. +func (h *Handler) Kpo(w http.ResponseWriter, r *http.Request) { + if _, ok := h.zahtevajDozvolu(w, r, "kpo.pregled"); !ok { + return + } + podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + if err != nil { + http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError) + return + } + + odStr := r.URL.Query().Get("od") + doStr := r.URL.Query().Get("do") + if odStr == "" || doStr == "" { + sada := time.Now() + prvi := time.Date(sada.Year(), sada.Month(), 1, 0, 0, 0, 0, sada.Location()) + poslednji := prvi.AddDate(0, 1, -1) + if odStr == "" { + odStr = prvi.Format("2006-01-02") + } + if doStr == "" { + doStr = poslednji.Format("2006-01-02") + } + } + + zapisi, err := h.KpoRepo.Lista(r.Context(), parsiraDatumOpcionalno(odStr), parsiraDatumOpcionalno(doStr)) + if err != nil { + http.Error(w, "Greška pri učitavanju KPO knjige", http.StatusInternalServerError) + return + } + + var sume model.KpoSume + for _, z := range zapisi { + sume.Prihod += z.Prihod + sume.Broj++ + } + + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "kpo" + ps.NaslovStranice = "KPO — knjiga o ostvarenom prometu" + h.renderujTemplate(w, "kpo", PodaciKpo{ + PodaciStranice: ps, + Zapisi: zapisi, + Sume: sume, + Od: odStr, + Do: doStr, + }) +} + +// NoviKpoUnos prikazuje formu za ručni unos u KPO. +func (h *Handler) NoviKpoUnos(w http.ResponseWriter, r *http.Request) { + if _, ok := h.zahtevajDozvolu(w, r, "kpo.dodaj"); !ok { + return + } + podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + if err != nil { + http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError) + return + } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "kpo" + ps.NaslovStranice = "Novi KPO unos" + h.renderujTemplate(w, "kpo_forma", PodaciKpoForma{ + PodaciStranice: ps, + Danas: time.Now().Format("2006-01-02"), + }) +} + +// SacuvajKpoUnos prima POST i upisuje ručni KPO zapis. +func (h *Handler) SacuvajKpoUnos(w http.ResponseWriter, r *http.Request) { + if _, ok := h.zahtevajDozvolu(w, r, "kpo.dodaj"); !ok { + return + } + if err := r.ParseForm(); err != nil { + http.Error(w, "Greška pri čitanju forme", http.StatusBadRequest) + return + } + + datum, err := time.Parse("2006-01-02", strings.TrimSpace(r.FormValue("datum_prometa"))) + if err != nil { + middleware.SetFlash(w, r, h.DB, "greska", "Datum prometa je obavezan.") + http.Redirect(w, r, "/kpo/nova", http.StatusSeeOther) + return + } + brojDok := strings.TrimSpace(r.FormValue("broj_dokumenta")) + if brojDok == "" { + middleware.SetFlash(w, r, h.DB, "greska", "Broj dokumenta je obavezan.") + http.Redirect(w, r, "/kpo/nova", http.StatusSeeOther) + return + } + + z := model.KpoZapis{ + DatumPrometa: datum, + BrojDokumenta: brojDok, + Opis: strings.TrimSpace(r.FormValue("opis")), + Prihod: parsiraIznos(r.FormValue("prihod")), + NacinPlacanja: strings.TrimSpace(r.FormValue("nacin_placanja")), + Napomena: strings.TrimSpace(r.FormValue("napomena")), + Izvor: "rucno", + } + + if _, err := h.KpoRepo.Kreiraj(r.Context(), &z); err != nil { + http.Error(w, "Greška pri čuvanju unosa", http.StatusInternalServerError) + return + } + http.Redirect(w, r, "/kpo?sacuvano=1", http.StatusSeeOther) +} + +// ObrisiKpoUnos briše KPO zapis. +func (h *Handler) ObrisiKpoUnos(w http.ResponseWriter, r *http.Request) { + if _, ok := h.zahtevajDozvolu(w, r, "kpo.obrisi"); !ok { + return + } + id, err := parseID(chi.URLParam(r, "id")) + if err != nil { + http.Error(w, "Neispravan ID", http.StatusBadRequest) + return + } + if err := h.KpoRepo.Obrisi(r.Context(), id); err != nil { + http.Error(w, "Greška pri brisanju unosa", http.StatusInternalServerError) + return + } + http.Redirect(w, r, "/kpo?obrisan=1", http.StatusSeeOther) +} + +// KpoBackfill kreira KPO unose za sve prodaje i naplaćene servisne naloge koji ih nemaju. +func (h *Handler) KpoBackfill(w http.ResponseWriter, r *http.Request) { + if _, ok := h.zahtevajDozvolu(w, r, "kpo.dodaj"); !ok { + return + } + ctx := r.Context() + var kreirano, preskoceno int + + // prodajni nalozi + nalozi, err := h.ProdajaRepo.Lista(ctx, "") + if err != nil { + http.Error(w, "Greška pri učitavanju prodaje", http.StatusInternalServerError) + return + } + for _, nd := range nalozi { + if nd.Stornirano { + preskoceno++ + continue + } + postoji, _ := h.KpoRepo.PostojiZaIzvor(ctx, "prodaja", nd.ID) + if postoji { + preskoceno++ + continue + } + z := model.KpoZapis{ + DatumPrometa: nd.Datum, + BrojDokumenta: nd.BrojNaloga, + Opis: fmt.Sprintf("Prodaja %s", nd.BrojNaloga), + Prihod: nd.Ukupno, + NacinPlacanja: nd.NacinPlacanja, + Izvor: "prodaja", + IzvorID: &nd.ID, + } + if _, e := h.KpoRepo.Kreiraj(ctx, &z); e != nil { + slog.Error("kpo backfill: prodaja", "id", nd.ID, "error", e) + continue + } + kreirano++ + } + + // naplaćeni servisni nalozi + servisNalozi, err := h.ServisRepo.Lista(ctx, "", "") + if err != nil { + slog.Error("kpo backfill: greška pri učitavanju servisa", "error", err) + } else { + for _, sn := range servisNalozi { + if sn.Status != model.StatusPreuzeto || sn.Naplaceno == 0 { + preskoceno++ + continue + } + postoji, _ := h.KpoRepo.PostojiZaIzvor(ctx, "servis", sn.ID) + if postoji { + preskoceno++ + continue + } + datum := sn.DatumPrijema + if sn.DatumZavrsetka != nil { + datum = *sn.DatumZavrsetka + } + z := model.KpoZapis{ + DatumPrometa: datum, + BrojDokumenta: sn.BrojNaloga, + Opis: fmt.Sprintf("Servis %s", sn.BrojNaloga), + Prihod: sn.Naplaceno, + NacinPlacanja: sn.NacinPlacanja, + Izvor: "servis", + IzvorID: &sn.ID, + } + if _, e := h.KpoRepo.Kreiraj(ctx, &z); e != nil { + slog.Error("kpo backfill: servis", "id", sn.ID, "error", e) + continue + } + kreirano++ + } + } + + middleware.SetFlash(w, r, h.DB, "uspeh", fmt.Sprintf("Backfill završen: %d novih KPO unosa, %d preskočeno.", kreirano, preskoceno)) + http.Redirect(w, r, "/kpo", http.StatusSeeOther) +} diff --git a/internal/handler/prodaja.go b/internal/handler/prodaja.go index 7b5fbe8..9ef5421 100644 --- a/internal/handler/prodaja.go +++ b/internal/handler/prodaja.go @@ -3,6 +3,7 @@ package handler import ( "encoding/json" "errors" + "fmt" "html/template" "log/slog" "net/http" @@ -293,6 +294,22 @@ func (h *Handler) SacuvajProdaju(w http.ResponseWriter, r *http.Request) { } } + // auto-KPO: svaki potvrđeni nalog ide u KPO ako je modul aktivan + if h.modulUkljucen(r.Context(), "kpo") { + kpoZ := model.KpoZapis{ + DatumPrometa: nalog.Datum, + BrojDokumenta: nalog.BrojNaloga, + Opis: fmt.Sprintf("Prodaja %s", nalog.BrojNaloga), + Prihod: nalog.Ukupno, + NacinPlacanja: nalog.NacinPlacanja, + Izvor: "prodaja", + IzvorID: &id, + } + if _, e := h.KpoRepo.Kreiraj(r.Context(), &kpoZ); e != nil { + slog.Error("auto-upis u KPO nije uspeo", "prodaja_id", id, "error", e) + } + } + http.Redirect(w, r, "/prodaja/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther) } @@ -585,6 +602,13 @@ func (h *Handler) StornoProdaje(w http.ResponseWriter, r *http.Request) { } } + // brisanje KPO zapisa na storno + if h.modulUkljucen(r.Context(), "kpo") { + if e := h.KpoRepo.ObrisiPoIzvoru(r.Context(), "prodaja", id); e != nil { + slog.Error("brisanje vezanog KPO zapisa nije uspelo", "prodaja_id", id, "error", e) + } + } + http.Redirect(w, r, "/prodaja/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther) } diff --git a/internal/handler/servis.go b/internal/handler/servis.go index af5d1f3..bf7e6c1 100644 --- a/internal/handler/servis.go +++ b/internal/handler/servis.go @@ -2134,6 +2134,24 @@ func (h *Handler) PromeniStatus(w http.ResponseWriter, r *http.Request) { } } } + // auto-KPO za servisni nalog + if h.modulUkljucen(r.Context(), "kpo") && iznos > 0 { + nalogKpo, _ := h.ServisRepo.DohvatiID(r.Context(), id) + if nalogKpo != nil { + kpoZ := model.KpoZapis{ + DatumPrometa: time.Now(), + BrojDokumenta: nalogKpo.BrojNaloga, + Opis: fmt.Sprintf("Servis %s", nalogKpo.BrojNaloga), + Prihod: iznos, + NacinPlacanja: nacin, + Izvor: "servis", + IzvorID: &id, + } + if _, e := h.KpoRepo.Kreiraj(r.Context(), &kpoZ); e != nil { + slog.Error("auto-upis u KPO za servis nije uspeo", "servis_id", id, "error", e) + } + } + } } http.Redirect(w, r, "/servis/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther) diff --git a/internal/model/kpo.go b/internal/model/kpo.go new file mode 100644 index 0000000..45be523 --- /dev/null +++ b/internal/model/kpo.go @@ -0,0 +1,24 @@ +package model + +import "time" + +// KpoZapis je jedan red u knjizi o ostvarenom prometu. +type KpoZapis struct { + ID int64 + DatumPrometa time.Time + RedniBroj *int + BrojDokumenta string + Opis string + Prihod float64 + NacinPlacanja string + Napomena string + Izvor string // "rucno" | "prodaja" | "servis" + IzvorID *int64 + DatumUnosa time.Time +} + +// KpoSume su zbrojeni prihodi za period. +type KpoSume struct { + Prihod float64 + Broj int +} diff --git a/migrations/094_kpo.sql b/migrations/094_kpo.sql new file mode 100644 index 0000000..23dfe3b --- /dev/null +++ b/migrations/094_kpo.sql @@ -0,0 +1,20 @@ +-- KPO: knjiga o ostvarenom prometu (za paušalce i preduzetnike prostog knjigovodstva). +-- Svaki red = jedan prihod (prodajni nalog, naplaćeni servisni nalog ili ručni unos). +-- izvor: 'rucno' | 'prodaja' | 'servis' +-- izvor_id: id izvornog naloga (NULL za ručni unos) +CREATE TABLE IF NOT EXISTS kpo_unosi ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + datum_prometa DATE NOT NULL, -- datum prometa / naplate + redni_broj INTEGER, -- redni broj u knjizi (opciono, može biti auto) + broj_dokumenta TEXT NOT NULL, -- broj računa / naloga + opis TEXT, -- opis prometa + prihod REAL NOT NULL DEFAULT 0, -- ukupan prihod (sa PDV-om ako je u sistemu PDV-a) + nacin_placanja TEXT, -- Gotovina, Kartica, Virman... + napomena TEXT, + izvor TEXT NOT NULL DEFAULT 'rucno', + izvor_id INTEGER, -- id prodajnog ili servisnog naloga + datum_unosa DATETIME NOT NULL DEFAULT (datetime('now','localtime')) +); + +CREATE INDEX IF NOT EXISTS idx_kpo_datum ON kpo_unosi(datum_prometa); +CREATE INDEX IF NOT EXISTS idx_kpo_izvor ON kpo_unosi(izvor, izvor_id); diff --git a/web/templates/komponente/sidebar.html b/web/templates/komponente/sidebar.html index 36a0a17..4b46d81 100644 --- a/web/templates/komponente/sidebar.html +++ b/web/templates/komponente/sidebar.html @@ -99,9 +99,10 @@ {{/* Knjigovodstvo — grupa: KIR/KPR/Obračun (modul „pdv") + Nivelacije (dozvola izmene cena) */}} {{$pdvVid := and (index .Moduli "pdv") (index .Dozvole "pdv.pregled")}} - {{$knjVid := or $pdvVid (index .Dozvole "artikal.izmeni")}} + {{$kpoVid := and (index .Moduli "kpo") (index .Dozvole "kpo.pregled")}} + {{$knjVid := or $pdvVid $kpoVid (index .Dozvole "artikal.izmeni")}} {{if $knjVid}} - {{$knjAktivan := or (eq .Stranica "nivelacije") (eq .Stranica "pdv-kir") (eq .Stranica "pdv-kpr") (eq .Stranica "pdv-obracun")}} + {{$knjAktivan := or (eq .Stranica "nivelacije") (eq .Stranica "kpo") (eq .Stranica "pdv-kir") (eq .Stranica "pdv-kpr") (eq .Stranica "pdv-obracun")}}
+ {{if or .Od .Do}}Poništi filter{{end}} + +
+ {{if index .Dozvole "kpo.dodaj"}} +
+ + +
+ {{end}} + {{if index .Dozvole "kpo.dodaj"}} + + + Ručni unos + + {{end}} +
+
+ + + {{if .Zapisi}} +
+
+
+ Ukupan prihod +
{{dinari .Sume.Prihod}} RSD
+
+
+ Broj unosa +
{{.Sume.Broj}}
+
+
+
+ {{end}} + +
+ {{if .Zapisi}} +
+ + + + + + + + + + + + + + {{range $z := .Zapisi}} + + + + + + + + + + {{end}} + +
DatumBr. dokumentaOpisNačin plaćanjaPrihod (RSD)Izvor
{{$z.DatumPrometa.Format "02.01.2006."}}{{$z.BrojDokumenta}}{{$z.Opis}}{{$z.NacinPlacanja}}{{dinari $z.Prihod}} + {{if eq $z.Izvor "prodaja"}}prodaja + {{else if eq $z.Izvor "servis"}}servis + {{else}}ručno{{end}} + + {{if index $.Dozvole "kpo.obrisi"}} + {{if eq $z.Izvor "rucno"}} +
+ + +
+ {{end}} + {{end}} +
+
+ {{else}} +
+
📒
+
Nema unosa za odabrani period.
+ {{if index .Dozvole "kpo.dodaj"}} +
+ + Ručni unos +
+ {{end}} +
+ {{end}} +
+ + +{{end}} diff --git a/web/templates/stranice/kpo_forma.html b/web/templates/stranice/kpo_forma.html new file mode 100644 index 0000000..15cdf73 --- /dev/null +++ b/web/templates/stranice/kpo_forma.html @@ -0,0 +1,74 @@ +{{template "base" .}} + +{{define "naslov"}}Novi KPO unos — NTech{{end}} + +{{define "sadrzaj"}} +
+ +
+ + ← KPO knjiga + +
+ +
+

Novi KPO unos

+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + Otkaži + + +
+
+
+ +
+{{end}}