KPO: knjiga o ostvarenom prometu — lista, ručni unos, backfill, auto-upis iz prodaje i servisa
This commit is contained in:
@@ -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.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"), doz("pdv.dodaj")).Post("/pdv/kpr/backfill-nabavke", h.KprBackfillNabavke)
|
||||||
r.With(modul("pdv")).Get("/pdv/obracun", h.PdvObracunStranica)
|
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("/magacin", h.Magacin)
|
||||||
r.Get("/usluge", h.Usluge)
|
r.Get("/usluge", h.Usluge)
|
||||||
r.Get("/usluge/nova", h.NovaUsluga)
|
r.Get("/usluge/nova", h.NovaUsluga)
|
||||||
|
|||||||
@@ -385,3 +385,12 @@ type FiskalRepository interface {
|
|||||||
DohvatiPoServisu(ctx context.Context, servisID int64) (*model.FiskalniRacun, error)
|
DohvatiPoServisu(ctx context.Context, servisID int64) (*model.FiskalniRacun, error)
|
||||||
OznačiKaoStorniran(ctx context.Context, id int64) 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -46,6 +46,7 @@ type Handler struct {
|
|||||||
PdvKprRepo db.PdvKprRepository
|
PdvKprRepo db.PdvKprRepository
|
||||||
NivelacijaRepo db.NivelacijaRepository
|
NivelacijaRepo db.NivelacijaRepository
|
||||||
FiskalRepo db.FiskalRepository
|
FiskalRepo db.FiskalRepository
|
||||||
|
KpoRepo db.KpoRepository
|
||||||
Verzija string
|
Verzija string
|
||||||
JelDemo bool
|
JelDemo bool
|
||||||
AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju)
|
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),
|
PdvKprRepo: sqlite.NoviPdvKprRepo(baza),
|
||||||
NivelacijaRepo: sqlite.NoviNivelacijaRepo(baza),
|
NivelacijaRepo: sqlite.NoviNivelacijaRepo(baza),
|
||||||
FiskalRepo: sqlite.NoviFiskalRepo(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.PdvKprRepo = sqlite.NoviPdvKprRepo(novaDB)
|
||||||
h.NivelacijaRepo = sqlite.NoviNivelacijaRepo(novaDB)
|
h.NivelacijaRepo = sqlite.NoviNivelacijaRepo(novaDB)
|
||||||
h.FiskalRepo = sqlite.NoviFiskalRepo(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.
|
// modulUkljucen vraća da li je zakonski modul (npr. „pdv") uključen za firmu prema profilu.
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ var saSidebar = []string{
|
|||||||
"pdv_kir", "pdv_kir_forma", "pdv_kir_dnevni_pazar",
|
"pdv_kir", "pdv_kir_forma", "pdv_kir_dnevni_pazar",
|
||||||
"pdv_kpr", "pdv_kpr_forma",
|
"pdv_kpr", "pdv_kpr_forma",
|
||||||
"pdv_obracun",
|
"pdv_obracun",
|
||||||
|
"kpo", "kpo_forma",
|
||||||
"nivelacije",
|
"nivelacije",
|
||||||
"podsetnici", "podsetnik_forma",
|
"podsetnici", "podsetnik_forma",
|
||||||
"profil_tema",
|
"profil_tema",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"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)
|
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)
|
http.Redirect(w, r, "/prodaja/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
http.Redirect(w, r, "/servis/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -99,9 +99,10 @@
|
|||||||
|
|
||||||
{{/* Knjigovodstvo — grupa: KIR/KPR/Obračun (modul „pdv") + Nivelacije (dozvola izmene cena) */}}
|
{{/* Knjigovodstvo — grupa: KIR/KPR/Obračun (modul „pdv") + Nivelacije (dozvola izmene cena) */}}
|
||||||
{{$pdvVid := and (index .Moduli "pdv") (index .Dozvole "pdv.pregled")}}
|
{{$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}}
|
{{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")}}
|
||||||
<div>
|
<div>
|
||||||
<button type="button" data-podmeni-dugme
|
<button type="button" data-podmeni-dugme
|
||||||
class="nav-stavka {{if $knjAktivan}}aktivan{{end}}"
|
class="nav-stavka {{if $knjAktivan}}aktivan{{end}}"
|
||||||
@@ -124,6 +125,13 @@
|
|||||||
<span class="nav-tooltip">Nivelacije — promene cena</span>
|
<span class="nav-tooltip">Nivelacije — promene cena</span>
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if $kpoVid}}
|
||||||
|
<a href="/kpo" class="nav-stavka nav-podstavka {{if eq .Stranica "kpo"}}aktivan{{end}}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><line x1="8" y1="10" x2="16" y2="10"/><line x1="8" y1="14" x2="16" y2="14"/></svg>
|
||||||
|
<span>KPO</span>
|
||||||
|
<span class="nav-tooltip">KPO — knjiga o ostvarenom prometu</span>
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
{{if $pdvVid}}
|
{{if $pdvVid}}
|
||||||
<a href="/pdv/kir" class="nav-stavka nav-podstavka {{if eq .Stranica "pdv-kir"}}aktivan{{end}}">
|
<a href="/pdv/kir" class="nav-stavka nav-podstavka {{if eq .Stranica "pdv-kir"}}aktivan{{end}}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
{{template "base" .}}
|
||||||
|
|
||||||
|
{{define "naslov"}}KPO — knjiga o ostvarenom prometu — NTech{{end}}
|
||||||
|
|
||||||
|
{{define "sadrzaj"}}
|
||||||
|
<div class="stranica-stack" style="width:100%;max-width:100%;">
|
||||||
|
|
||||||
|
<div class="kartica animiraj" style="margin-bottom:16px;">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:flex-end;gap:12px;flex-wrap:wrap;">
|
||||||
|
<form method="GET" action="/kpo" style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;">
|
||||||
|
<div>
|
||||||
|
<label class="polje-labela">Od datuma</label>
|
||||||
|
<input type="date" name="od" value="{{.Od}}" style="padding:8px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="polje-labela">Do datuma</label>
|
||||||
|
<input type="date" name="do" value="{{.Do}}" style="padding:8px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
|
||||||
|
</div>
|
||||||
|
<button type="submit" style="padding:8px 16px;background:var(--sb-aktivan);color:var(--tekst-jak);border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;cursor:pointer;">Prikaži</button>
|
||||||
|
{{if or .Od .Do}}<a href="/kpo" class="nazad-link" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" style="margin-bottom:0;">Poništi filter</a>{{end}}
|
||||||
|
</form>
|
||||||
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
||||||
|
{{if index .Dozvole "kpo.dodaj"}}
|
||||||
|
<form method="POST" action="/kpo/backfill" data-full-reload>
|
||||||
|
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
|
||||||
|
<button type="submit" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;white-space:nowrap;"
|
||||||
|
data-potvrda="Uvesti sve prodaje i naplaćene servisne naloge koji još nisu u KPO-u?">
|
||||||
|
Uvezi postojeće
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{if index .Dozvole "kpo.dodaj"}}
|
||||||
|
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML"
|
||||||
|
href="/kpo/nova" class="btn-primarno" style="font-size:13px;padding:8px 16px;white-space:nowrap;">
|
||||||
|
+ Ručni unos
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .Zapisi}}
|
||||||
|
<div class="kartica animiraj" style="margin-bottom:16px;padding:12px 18px;">
|
||||||
|
<div style="display:flex;gap:32px;align-items:center;flex-wrap:wrap;">
|
||||||
|
<div>
|
||||||
|
<span class="polje-labela">Ukupan prihod</span>
|
||||||
|
<div style="font-size:22px;font-weight:700;color:var(--zelena);">{{dinari .Sume.Prihod}} RSD</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="polje-labela">Broj unosa</span>
|
||||||
|
<div style="font-size:22px;font-weight:700;">{{.Sume.Broj}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="kartica animiraj">
|
||||||
|
{{if .Zapisi}}
|
||||||
|
<div class="tabela-omotac">
|
||||||
|
<table class="tabela">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Datum</th>
|
||||||
|
<th>Br. dokumenta</th>
|
||||||
|
<th>Opis</th>
|
||||||
|
<th>Način plaćanja</th>
|
||||||
|
<th style="text-align:right;">Prihod (RSD)</th>
|
||||||
|
<th>Izvor</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range $z := .Zapisi}}
|
||||||
|
<tr>
|
||||||
|
<td style="white-space:nowrap;">{{$z.DatumPrometa.Format "02.01.2006."}}</td>
|
||||||
|
<td style="font-weight:500;">{{$z.BrojDokumenta}}</td>
|
||||||
|
<td style="color:var(--tekst-slab);font-size:13px;">{{$z.Opis}}</td>
|
||||||
|
<td style="font-size:13px;">{{$z.NacinPlacanja}}</td>
|
||||||
|
<td style="text-align:right;font-weight:600;color:var(--zelena);">{{dinari $z.Prihod}}</td>
|
||||||
|
<td>
|
||||||
|
{{if eq $z.Izvor "prodaja"}}<span class="bedz bedz-plava">prodaja</span>
|
||||||
|
{{else if eq $z.Izvor "servis"}}<span class="bedz bedz-narandzasta">servis</span>
|
||||||
|
{{else}}<span class="bedz">ručno</span>{{end}}
|
||||||
|
</td>
|
||||||
|
<td style="text-align:right;">
|
||||||
|
{{if index $.Dozvole "kpo.obrisi"}}
|
||||||
|
{{if eq $z.Izvor "rucno"}}
|
||||||
|
<form method="POST" action="/kpo/obrisi/{{$z.ID}}" style="display:inline;" data-full-reload>
|
||||||
|
<input type="hidden" name="csrf_token" value="{{$.CsrfToken}}">
|
||||||
|
<button type="submit" class="btn-opasno-mali"
|
||||||
|
data-potvrda="Obrisati ovaj KPO unos?">Obriši</button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div style="text-align:center;padding:48px 16px;color:var(--tekst-slab);">
|
||||||
|
<div style="font-size:32px;margin-bottom:12px;">📒</div>
|
||||||
|
<div>Nema unosa za odabrani period.</div>
|
||||||
|
{{if index .Dozvole "kpo.dodaj"}}
|
||||||
|
<div style="margin-top:12px;">
|
||||||
|
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML"
|
||||||
|
href="/kpo/nova" class="btn-primarno" style="font-size:13px;">+ Ručni unos</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
{{template "base" .}}
|
||||||
|
|
||||||
|
{{define "naslov"}}Novi KPO unos — NTech{{end}}
|
||||||
|
|
||||||
|
{{define "sadrzaj"}}
|
||||||
|
<div class="stranica-stack" style="max-width:640px;">
|
||||||
|
|
||||||
|
<div style="margin-bottom:12px;">
|
||||||
|
<a href="/kpo" class="nazad-link" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
|
||||||
|
← KPO knjiga
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="kartica animiraj">
|
||||||
|
<h2 style="margin:0 0 20px;font-size:18px;">Novi KPO unos</h2>
|
||||||
|
|
||||||
|
<form method="POST" action="/kpo/nova">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
|
||||||
|
|
||||||
|
<div class="polje-grupa" style="margin-bottom:16px;">
|
||||||
|
<label class="polje-labela">Datum prometa *</label>
|
||||||
|
<input type="date" name="datum_prometa" value="{{.Danas}}" required
|
||||||
|
style="width:100%;padding:10px 12px;border:0.5px solid var(--ivica);border-radius:8px;font-size:14px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;box-sizing:border-box;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="polje-grupa" style="margin-bottom:16px;">
|
||||||
|
<label class="polje-labela">Broj dokumenta *</label>
|
||||||
|
<input type="text" name="broj_dokumenta" required autocomplete="off"
|
||||||
|
placeholder="npr. 2025/001 ili gotovina"
|
||||||
|
style="width:100%;padding:10px 12px;border:0.5px solid var(--ivica);border-radius:8px;font-size:14px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;box-sizing:border-box;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="polje-grupa" style="margin-bottom:16px;">
|
||||||
|
<label class="polje-labela">Opis (opcionalno)</label>
|
||||||
|
<input type="text" name="opis" autocomplete="off"
|
||||||
|
placeholder="Kratki opis prihoda"
|
||||||
|
style="width:100%;padding:10px 12px;border:0.5px solid var(--ivica);border-radius:8px;font-size:14px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;box-sizing:border-box;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="polje-grupa" style="margin-bottom:16px;">
|
||||||
|
<label class="polje-labela">Prihod (RSD) *</label>
|
||||||
|
<input type="number" name="prihod" step="0.01" min="0" required
|
||||||
|
style="width:100%;padding:10px 12px;border:0.5px solid var(--ivica);border-radius:8px;font-size:14px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;box-sizing:border-box;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="polje-grupa" style="margin-bottom:16px;">
|
||||||
|
<label class="polje-labela">Način plaćanja</label>
|
||||||
|
<select name="nacin_placanja"
|
||||||
|
style="width:100%;padding:10px 12px;border:0.5px solid var(--ivica);border-radius:8px;font-size:14px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;box-sizing:border-box;">
|
||||||
|
<option value="">— nije navedeno —</option>
|
||||||
|
<option value="Gotovina">Gotovina</option>
|
||||||
|
<option value="Kartica">Kartica</option>
|
||||||
|
<option value="Virman">Virman</option>
|
||||||
|
<option value="Ostalo">Ostalo</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="polje-grupa" style="margin-bottom:24px;">
|
||||||
|
<label class="polje-labela">Napomena</label>
|
||||||
|
<textarea name="napomena" rows="2"
|
||||||
|
style="width:100%;padding:10px 12px;border:0.5px solid var(--ivica);border-radius:8px;font-size:14px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;box-sizing:border-box;resize:vertical;"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex;gap:10px;justify-content:flex-end;">
|
||||||
|
<a href="/kpo" class="btn-sekundarno" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
|
||||||
|
Otkaži
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn-primarno">Sačuvaj</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
Reference in New Issue
Block a user