Compare commits
11
Commits
b0250b2917
...
v0.1.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eb472b9e6
|
||
|
|
ac6deeeba4
|
||
|
|
aabe19639a
|
||
|
|
f4a9c1eefe
|
||
|
|
91998a7736
|
||
|
|
32f2235127
|
||
|
|
2727b0da80
|
||
|
|
b4d15f2df2
|
||
|
|
830c51e95e
|
||
|
|
4caadd2ef0
|
||
|
|
fec84f98d5
|
@@ -309,6 +309,8 @@ func main() {
|
||||
r.With(doz("artikal.izmeni")).Post("/magacin/izmeni/{id}", h.SacuvajIzmenuArtikla)
|
||||
r.With(doz("artikal.obrisi")).Get("/magacin/obrisi/{id}", h.ObrisiArtikal)
|
||||
r.With(doz("artikal.obrisi")).Get("/magacin/vrati/{id}", h.VratiArtikal)
|
||||
r.With(doz("artikal.izmeni")).Post("/magacin/kartica/{id}/dobavljac/dodaj", h.DodajDobavljacaArtiklu)
|
||||
r.With(doz("artikal.izmeni")).Post("/magacin/kartica/{id}/dobavljac/obrisi", h.ObrisiDobavljacaArtikla)
|
||||
r.With(doz("artikal.premesti")).Post("/magacin/premesti/{id}", h.PremestiArtikal)
|
||||
r.With(doz("artikal.izmeni")).Post("/magacin/promeni-cenu/{id}", h.PromeniCenuArtikla)
|
||||
r.With(doz("artikal.izmeni")).Get("/nivelacije", h.Nivelacije)
|
||||
|
||||
@@ -31,6 +31,16 @@ type ArtikalRepository interface {
|
||||
SledecaSifra(ctx context.Context, kategorijaID *int64) (string, error)
|
||||
// KorigujKolicinu postavlja novu količinu artikla i upisuje korekciju u magacinske_promene
|
||||
KorigujKolicinu(ctx context.Context, artikalID int64, novaKolicina int, korisnikID *int64, napomena string) error
|
||||
// DobavljaciArtikla vraća ID-jeve dobavljača vezanih za artikal
|
||||
DobavljaciArtikla(ctx context.Context, artikalID int64) ([]int64, error)
|
||||
// PostaviDobavljaceArtikla zamenjuje skup dobavljača artikla datim ID-jevima
|
||||
PostaviDobavljaceArtikla(ctx context.Context, artikalID int64, dobavljaciID []int64) error
|
||||
// PoveziDobavljaca dodaje vezu artikal–dobavljač ako ne postoji (auto pri nabavci)
|
||||
PoveziDobavljaca(ctx context.Context, artikalID, dobavljacID int64) error
|
||||
// OdveziDobavljaca uklanja vezu artikal–dobavljač
|
||||
OdveziDobavljaca(ctx context.Context, artikalID, dobavljacID int64) error
|
||||
// SveDobavljaceArtikala vraća mapu artikal_id → lista dobavljac_id (za filter u nabavci)
|
||||
SveDobavljaceArtikala(ctx context.Context) (map[int64][]int64, error)
|
||||
}
|
||||
|
||||
// KategorijaRepository definiše operacije nad kategorijama
|
||||
@@ -115,6 +125,7 @@ type DobavljacRepository interface {
|
||||
// KlijentFilter definiše parametre za filtriranje liste klijenata
|
||||
type KlijentFilter struct {
|
||||
Pretraga string
|
||||
Tip string // "fizicko", "pravno" ili "" za sve
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
@@ -149,7 +160,7 @@ type ProdajaRepository interface {
|
||||
DohvatiStavke(ctx context.Context, nalogID int64) ([]model.StavkaProdajeSaArtiklom, error)
|
||||
Kreiraj(ctx context.Context, n *model.ProdajniNalog, stavke []model.StavkaProdaje, korisnikID *int64) (int64, error)
|
||||
Storno(ctx context.Context, id int64, razlog string, korisnikID *int64) error
|
||||
Obrisi(ctx context.Context, id int64) error
|
||||
Obrisi(ctx context.Context, id int64, korisnikID *int64) error
|
||||
SledeciBroj(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -309,6 +309,89 @@ func (r *ArtikalRepo) Vrati(ctx context.Context, id int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DobavljaciArtikla vraća ID-jeve dobavljača vezanih za artikal
|
||||
func (r *ArtikalRepo) DobavljaciArtikla(ctx context.Context, artikalID int64) ([]int64, error) {
|
||||
redovi, err := r.db.QueryContext(ctx,
|
||||
"SELECT dobavljac_id FROM artikal_dobavljac WHERE artikal_id = ? ORDER BY dobavljac_id", artikalID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: ArtikalRepo.DobavljaciArtikla: %w", err)
|
||||
}
|
||||
defer redovi.Close()
|
||||
|
||||
var ids []int64
|
||||
for redovi.Next() {
|
||||
var id int64
|
||||
if err := redovi.Scan(&id); err != nil {
|
||||
return nil, fmt.Errorf("ntech: ArtikalRepo.DobavljaciArtikla: scan: %w", err)
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// PostaviDobavljaceArtikla zamenjuje skup dobavljača artikla datim ID-jevima (u transakciji)
|
||||
func (r *ArtikalRepo) PostaviDobavljaceArtikla(ctx context.Context, artikalID int64, dobavljaciID []int64) error {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ArtikalRepo.PostaviDobavljaceArtikla: begin tx: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if _, err := tx.ExecContext(ctx, "DELETE FROM artikal_dobavljac WHERE artikal_id = ?", artikalID); err != nil {
|
||||
return fmt.Errorf("ntech: ArtikalRepo.PostaviDobavljaceArtikla: delete: %w", err)
|
||||
}
|
||||
for _, did := range dobavljaciID {
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
"INSERT OR IGNORE INTO artikal_dobavljac (artikal_id, dobavljac_id) VALUES (?, ?)", artikalID, did); err != nil {
|
||||
return fmt.Errorf("ntech: ArtikalRepo.PostaviDobavljaceArtikla: insert: %w", err)
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("ntech: ArtikalRepo.PostaviDobavljaceArtikla: commit: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PoveziDobavljaca dodaje vezu artikal–dobavljač ako ne postoji (auto pri nabavci)
|
||||
func (r *ArtikalRepo) PoveziDobavljaca(ctx context.Context, artikalID, dobavljacID int64) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
"INSERT OR IGNORE INTO artikal_dobavljac (artikal_id, dobavljac_id) VALUES (?, ?)", artikalID, dobavljacID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ArtikalRepo.PoveziDobavljaca: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OdveziDobavljaca uklanja vezu artikal–dobavljač
|
||||
func (r *ArtikalRepo) OdveziDobavljaca(ctx context.Context, artikalID, dobavljacID int64) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
"DELETE FROM artikal_dobavljac WHERE artikal_id = ? AND dobavljac_id = ?", artikalID, dobavljacID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ArtikalRepo.OdveziDobavljaca: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SveDobavljaceArtikala vraća mapu artikal_id → lista dobavljac_id
|
||||
func (r *ArtikalRepo) SveDobavljaceArtikala(ctx context.Context) (map[int64][]int64, error) {
|
||||
redovi, err := r.db.QueryContext(ctx,
|
||||
"SELECT artikal_id, dobavljac_id FROM artikal_dobavljac ORDER BY artikal_id")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: ArtikalRepo.SveDobavljaceArtikala: %w", err)
|
||||
}
|
||||
defer redovi.Close()
|
||||
|
||||
mapa := make(map[int64][]int64)
|
||||
for redovi.Next() {
|
||||
var aid, did int64
|
||||
if err := redovi.Scan(&aid, &did); err != nil {
|
||||
return nil, fmt.Errorf("ntech: ArtikalRepo.SveDobavljaceArtikala: scan: %w", err)
|
||||
}
|
||||
mapa[aid] = append(mapa[aid], did)
|
||||
}
|
||||
return mapa, nil
|
||||
}
|
||||
|
||||
// KorigujKolicinu postavlja novu količinu i upisuje korekciju u magacinske_promene
|
||||
func (r *ArtikalRepo) KorigujKolicinu(ctx context.Context, artikalID int64, novaKolicina int, korisnikID *int64, napomena string) error {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
|
||||
@@ -153,6 +153,10 @@ func (r *KlijentRepo) ListaFilter(ctx context.Context, filter db.KlijentFilter)
|
||||
p := "%" + filter.Pretraga + "%"
|
||||
args = append(args, p, p, p, p, p, p)
|
||||
}
|
||||
if filter.Tip == "fizicko" || filter.Tip == "pravno" {
|
||||
upit += " AND tip = ?"
|
||||
args = append(args, filter.Tip)
|
||||
}
|
||||
|
||||
upit += " ORDER BY datum_unosa DESC"
|
||||
|
||||
@@ -206,6 +210,10 @@ func (r *KlijentRepo) PrebrojiPoFilteru(ctx context.Context, filter db.KlijentFi
|
||||
p := "%" + filter.Pretraga + "%"
|
||||
args = append(args, p, p, p, p, p, p)
|
||||
}
|
||||
if filter.Tip == "fizicko" || filter.Tip == "pravno" {
|
||||
upit += " AND tip = ?"
|
||||
args = append(args, filter.Tip)
|
||||
}
|
||||
|
||||
var broj int
|
||||
if err := r.db.QueryRowContext(ctx, upit, args...).Scan(&broj); err != nil {
|
||||
|
||||
@@ -328,7 +328,7 @@ func (r *ProdajaRepo) Storno(ctx context.Context, id int64, razlog string, koris
|
||||
}
|
||||
|
||||
// Obrisi briše prodajni nalog i vraća količine artikala na stanje (u transakciji)
|
||||
func (r *ProdajaRepo) Obrisi(ctx context.Context, id int64) error {
|
||||
func (r *ProdajaRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) error {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Obrisi: begin tx: %w", err)
|
||||
@@ -366,13 +366,31 @@ func (r *ProdajaRepo) Obrisi(ctx context.Context, id int64) error {
|
||||
|
||||
for _, p := range stavke {
|
||||
// usluge i troškovi nemaju stanje — vraćamo samo proizvodima
|
||||
_, err := tx.ExecContext(ctx,
|
||||
"UPDATE artikli SET kolicina = kolicina + ? WHERE id = ? AND (tip = 'proizvod' OR tip = '')",
|
||||
p.kolicina, p.artikalID,
|
||||
var stanjePre int
|
||||
var tip string
|
||||
err := tx.QueryRowContext(ctx,
|
||||
"SELECT kolicina, tip FROM artikli WHERE id = ?", p.artikalID,
|
||||
).Scan(&stanjePre, &tip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Obrisi: dohvati stanje: %w", err)
|
||||
}
|
||||
if !(tip == model.TipProizvod || tip == "") {
|
||||
continue
|
||||
}
|
||||
|
||||
stanjePosle := stanjePre + p.kolicina
|
||||
_, err = tx.ExecContext(ctx,
|
||||
"UPDATE artikli SET kolicina = ? WHERE id = ?", stanjePosle, p.artikalID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Obrisi: vrati stanje: %w", err)
|
||||
}
|
||||
|
||||
err = zabeleziMagacinPromenu(ctx, tx, p.artikalID, model.PromenaPovracaj,
|
||||
p.kolicina, stanjePre, stanjePosle, id, korisnikID, "brisanje prodajnog naloga")
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Obrisi: magacin: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"log/slog"
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var bazniSabloni = []string{
|
||||
@@ -53,6 +54,17 @@ var sablonskeFunkcije = template.FuncMap{
|
||||
}
|
||||
return fmt.Sprintf("%d", int64(math.Round(*v)))
|
||||
},
|
||||
// dinari formatira iznos sa separatorom hiljada (tačka) i 2 decimale (zarez):
|
||||
// 1234567.5 → "1.234.567,50"
|
||||
"dinari": func(v float64) string {
|
||||
return formatirajDinare(v, 2)
|
||||
},
|
||||
// dinariCeli formatira iznos sa separatorom hiljada, bez decimala: 1234567 → "1.234.567"
|
||||
"dinariCeli": func(v float64) string {
|
||||
return formatirajDinare(v, 0)
|
||||
},
|
||||
// telefon formatira srpski broj telefona radi lakšeg čitanja: "0641234567" → "064 123 4567"
|
||||
"telefon": formatirajTelefon,
|
||||
// statusPre vraća true ako je `a` pre `b` u redosledu statusa
|
||||
"statusPre": func(a, b string, statusi []string) bool {
|
||||
ia, ib := -1, -1
|
||||
@@ -172,3 +184,98 @@ func (h *Handler) renderujStandalone(w http.ResponseWriter, ime string, podaci a
|
||||
http.Error(w, "Greška pri prikazu stranice", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// formatirajDinare formatira broj sa tačkom kao separatorom hiljada i zarezom
|
||||
// za decimale (srpski format). decimale = broj decimalnih mesta (0 ili 2).
|
||||
func formatirajDinare(v float64, decimale int) string {
|
||||
negativan := v < 0
|
||||
if negativan {
|
||||
v = -v
|
||||
}
|
||||
|
||||
var ceoStr, decStr string
|
||||
if decimale == 2 {
|
||||
// radi u stotinkama da zaokruživanje pravilno prenese (npr. 1234567.999 → 1.234.568,00)
|
||||
stotinke := int64(math.Round(v * 100))
|
||||
ceoStr = fmt.Sprintf("%d", stotinke/100)
|
||||
decStr = fmt.Sprintf("%02d", stotinke%100)
|
||||
} else {
|
||||
ceoStr = fmt.Sprintf("%d", int64(math.Round(v)))
|
||||
}
|
||||
|
||||
// ubaci tačke na svake 3 cifre s desna
|
||||
var sb []byte
|
||||
n := len(ceoStr)
|
||||
for i, c := range ceoStr {
|
||||
if i > 0 && (n-i)%3 == 0 {
|
||||
sb = append(sb, '.')
|
||||
}
|
||||
sb = append(sb, byte(c))
|
||||
}
|
||||
rezultat := string(sb)
|
||||
|
||||
if decimale == 2 {
|
||||
rezultat += "," + decStr
|
||||
}
|
||||
if negativan {
|
||||
rezultat = "-" + rezultat
|
||||
}
|
||||
return rezultat
|
||||
}
|
||||
|
||||
// formatirajTelefon formatira srpski broj telefona radi lakšeg čitanja:
|
||||
// pozivni broj odvojen kosom crtom, ostatak grupisan crticom.
|
||||
// Primeri: "0641234567" → "064/123-4567", "+381641234567" → "+381 64/123-4567".
|
||||
// Ako format nije prepoznat, vraća original.
|
||||
func formatirajTelefon(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// izdvoj cifre i zapamti da li je međunarodni (+)
|
||||
medjunarodni := strings.HasPrefix(s, "+") || strings.HasPrefix(s, "00")
|
||||
var cifre []rune
|
||||
for _, c := range s {
|
||||
if c >= '0' && c <= '9' {
|
||||
cifre = append(cifre, c)
|
||||
}
|
||||
}
|
||||
d := string(cifre)
|
||||
|
||||
// međunarodni srpski prefiks (381): "+381 64/123-4567"
|
||||
if medjunarodni {
|
||||
d = strings.TrimPrefix(d, "00")
|
||||
if strings.HasPrefix(d, "381") {
|
||||
ostatak := d[3:] // bez vodeće nule, npr. "641234567"
|
||||
if len(ostatak) < 7 || len(ostatak) > 9 {
|
||||
return s
|
||||
}
|
||||
return "+381 " + ostatak[:2] + "/" + grupisiTelefon(ostatak[2:])
|
||||
}
|
||||
return s // strani broj — ne diramo
|
||||
}
|
||||
|
||||
// lokalni format: očekujemo vodeću nulu i 8–10 cifara ukupno
|
||||
if !strings.HasPrefix(d, "0") || len(d) < 8 || len(d) > 10 {
|
||||
return s
|
||||
}
|
||||
// pozivni (3 cifre, npr. 064/011) "/" ostatak grupisan crticom
|
||||
return d[:3] + "/" + grupisiTelefon(d[3:])
|
||||
}
|
||||
|
||||
// grupisiTelefon deli niz cifara u grupe od po 3 crticom (poslednja može 4) — "1234567" → "123-4567"
|
||||
func grupisiTelefon(d string) string {
|
||||
if len(d) <= 4 {
|
||||
return d
|
||||
}
|
||||
var delovi []string
|
||||
delovi = append(delovi, d[:3])
|
||||
ostatak := d[3:]
|
||||
for len(ostatak) > 4 {
|
||||
delovi = append(delovi, ostatak[:3])
|
||||
ostatak = ostatak[3:]
|
||||
}
|
||||
delovi = append(delovi, ostatak)
|
||||
return strings.Join(delovi, "-")
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type PodaciKlijenata struct {
|
||||
model.PodaciStranice
|
||||
Klijenti []model.Klijent
|
||||
Pretraga string
|
||||
TipFilter string
|
||||
Sacuvano bool
|
||||
Obrisan bool
|
||||
StranicaBr int
|
||||
@@ -45,8 +46,12 @@ func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
pretraga := r.URL.Query().Get("pretraga")
|
||||
tipFilter := r.URL.Query().Get("tip")
|
||||
if tipFilter != "fizicko" && tipFilter != "pravno" {
|
||||
tipFilter = ""
|
||||
}
|
||||
|
||||
const pageSize = 100
|
||||
const pageSize = 50
|
||||
stranicaBr := 1
|
||||
if p := r.URL.Query().Get("stranica"); p != "" {
|
||||
if v, err := strconv.Atoi(p); err == nil && v > 0 {
|
||||
@@ -56,6 +61,7 @@ func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
filter := db.KlijentFilter{
|
||||
Pretraga: pretraga,
|
||||
Tip: tipFilter,
|
||||
Limit: pageSize,
|
||||
Offset: (stranicaBr - 1) * pageSize,
|
||||
}
|
||||
@@ -78,6 +84,9 @@ func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
|
||||
if pretraga != "" {
|
||||
queryDelići += "&pretraga=" + pretraga
|
||||
}
|
||||
if tipFilter != "" {
|
||||
queryDelići += "&tip=" + tipFilter
|
||||
}
|
||||
|
||||
stranicaPrev := stranicaBr - 1
|
||||
if stranicaPrev < 1 {
|
||||
@@ -95,6 +104,7 @@ func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
|
||||
PodaciStranice: ps,
|
||||
Klijenti: klijenti,
|
||||
Pretraga: pretraga,
|
||||
TipFilter: tipFilter,
|
||||
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
||||
Obrisan: r.URL.Query().Get("obrisan") == "1",
|
||||
StranicaBr: stranicaBr,
|
||||
|
||||
@@ -2,6 +2,7 @@ package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@@ -220,8 +221,10 @@ func (h *Handler) VratiArtikal(w http.ResponseWriter, r *http.Request) {
|
||||
// PodaciMagacinskeKartice su podaci za karticu jednog artikla
|
||||
type PodaciMagacinskeKartice struct {
|
||||
model.PodaciStranice
|
||||
Artikal model.Artikal
|
||||
Promene []model.MagacinskaPromenaSaDetaljem
|
||||
Artikal model.Artikal
|
||||
Promene []model.MagacinskaPromenaSaDetaljem
|
||||
Dobavljaci []model.Dobavljac // dobavljači vezani za artikal
|
||||
DostupniDobavljaci []model.Dobavljac // dobavljači koji još nisu vezani (za dodavanje)
|
||||
}
|
||||
|
||||
// MagacinskaKartica prikazuje sve promene stanja za jedan artikal
|
||||
@@ -250,13 +253,67 @@ func (h *Handler) MagacinskaKartica(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// dobavljači: vezani za artikal i oni koji još nisu vezani (za padajući izbor)
|
||||
sviDobavljaci, _ := h.DobavljaciRepo.Lista(r.Context(), "")
|
||||
vezaniIDs, _ := h.Artikli.DobavljaciArtikla(r.Context(), id)
|
||||
vezanSet := map[int64]bool{}
|
||||
for _, did := range vezaniIDs {
|
||||
vezanSet[did] = true
|
||||
}
|
||||
var vezani, dostupni []model.Dobavljac
|
||||
for _, d := range sviDobavljaci {
|
||||
if vezanSet[d.ID] {
|
||||
vezani = append(vezani, d)
|
||||
} else {
|
||||
dostupni = append(dostupni, d)
|
||||
}
|
||||
}
|
||||
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "magacin"
|
||||
ps.NaslovStranice = "Kartica: " + artikal.Naziv
|
||||
|
||||
h.renderujTemplate(w, "magacin_kartica", PodaciMagacinskeKartice{
|
||||
PodaciStranice: ps,
|
||||
Artikal: *artikal,
|
||||
Promene: promene,
|
||||
PodaciStranice: ps,
|
||||
Artikal: *artikal,
|
||||
Promene: promene,
|
||||
Dobavljaci: vezani,
|
||||
DostupniDobavljaci: dostupni,
|
||||
})
|
||||
}
|
||||
|
||||
// DodajDobavljacaArtiklu veže izabranog dobavljača za artikal
|
||||
func (h *Handler) DodajDobavljacaArtiklu(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID artikla", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
dobID, err := strconv.ParseInt(r.FormValue("dobavljac_id"), 10, 64)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/magacin/kartica/"+chi.URLParam(r, "id"), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if e := h.Artikli.PoveziDobavljaca(r.Context(), id, dobID); e != nil {
|
||||
slog.Error("vezivanje dobavljača nije uspelo", "artikal_id", id, "error", e)
|
||||
}
|
||||
http.Redirect(w, r, "/magacin/kartica/"+chi.URLParam(r, "id"), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// ObrisiDobavljacaArtikla uklanja vezu dobavljača sa artiklom
|
||||
func (h *Handler) ObrisiDobavljacaArtikla(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID artikla", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
dobID, err := strconv.ParseInt(r.FormValue("dobavljac_id"), 10, 64)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/magacin/kartica/"+chi.URLParam(r, "id"), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if e := h.Artikli.OdveziDobavljaca(r.Context(), id, dobID); e != nil {
|
||||
slog.Error("uklanjanje dobavljača nije uspelo", "artikal_id", id, "error", e)
|
||||
}
|
||||
http.Redirect(w, r, "/magacin/kartica/"+chi.URLParam(r, "id"), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ntech/internal/db/sqlite"
|
||||
"ntech/internal/middleware"
|
||||
@@ -16,11 +18,13 @@ import (
|
||||
// PodaciFormeArtikla su podaci za formu novog/izmenjenog artikla
|
||||
type PodaciFormeArtikla struct {
|
||||
model.PodaciStranice
|
||||
Artikal model.Artikal
|
||||
Kategorije []model.Kategorija
|
||||
KategorijaIDStr string
|
||||
Greska string
|
||||
Izmena bool
|
||||
Artikal model.Artikal
|
||||
Kategorije []model.Kategorija
|
||||
KategorijaIDStr string
|
||||
Dobavljaci []model.Dobavljac // svi dobavljači za izbor
|
||||
IzabraniDobavljaci map[int64]bool // dobavljači vezani za artikal (za checked stanje)
|
||||
Greska string
|
||||
Izmena bool
|
||||
}
|
||||
|
||||
// NoviArtikal prikazuje formu za unos novog artikla
|
||||
@@ -43,12 +47,15 @@ func (h *Handler) NoviArtikal(w http.ResponseWriter, r *http.Request) {
|
||||
predlogSifre = "ART-0001"
|
||||
}
|
||||
|
||||
dobavljaci, _ := h.DobavljaciRepo.Lista(r.Context(), "")
|
||||
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "magacin"
|
||||
ps.NaslovStranice = "Novi artikal"
|
||||
h.renderujFormuArtikla(w, PodaciFormeArtikla{
|
||||
PodaciStranice: ps,
|
||||
Kategorije: kategorije,
|
||||
Dobavljaci: dobavljaci,
|
||||
Artikal: model.Artikal{Sifra: predlogSifre, Tip: model.TipProizvod, JedinicaMere: "kom"},
|
||||
Izmena: false,
|
||||
})
|
||||
@@ -70,6 +77,7 @@ func (h *Handler) SacuvajArtikal(w http.ResponseWriter, r *http.Request) {
|
||||
if greska != "" {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
kategorije, _ := h.KategorijeRepo.Lista(r.Context())
|
||||
dobavljaci, _ := h.DobavljaciRepo.Lista(r.Context(), "")
|
||||
katIDStr := ""
|
||||
if artikal.KategorijaID != nil {
|
||||
katIDStr = strconv.FormatInt(*artikal.KategorijaID, 10)
|
||||
@@ -78,33 +86,39 @@ func (h *Handler) SacuvajArtikal(w http.ResponseWriter, r *http.Request) {
|
||||
ps.Stranica = "magacin"
|
||||
ps.NaslovStranice = "Novi artikal"
|
||||
h.renderujFormuArtikla(w, PodaciFormeArtikla{
|
||||
PodaciStranice: ps,
|
||||
Artikal: artikal,
|
||||
Kategorije: kategorije,
|
||||
KategorijaIDStr: katIDStr,
|
||||
Greska: greska,
|
||||
Izmena: false,
|
||||
PodaciStranice: ps,
|
||||
Artikal: artikal,
|
||||
Kategorije: kategorije,
|
||||
KategorijaIDStr: katIDStr,
|
||||
Dobavljaci: dobavljaci,
|
||||
IzabraniDobavljaci: mapaDobavljaca(citajDobavljaceForme(r)),
|
||||
Greska: greska,
|
||||
Izmena: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ako korisnik nije uneo šifru, auto-generišemo pre Kreiraj
|
||||
// (tako je dodela šifre atomična: ako INSERT padne na UNIQUE constraint,
|
||||
// Kreiraj vraća grešku umesto da šifra ostane NULL bez ikakve poruke)
|
||||
if artikal.Sifra == "" {
|
||||
autoSifra, e := h.Artikli.SledecaSifra(r.Context(), artikal.KategorijaID)
|
||||
if e != nil {
|
||||
autoSifra = fmt.Sprintf("ART-%04d", time.Now().UnixMilli()%10000)
|
||||
}
|
||||
artikal.Sifra = autoSifra
|
||||
}
|
||||
|
||||
id, err := h.Artikli.Kreiraj(r.Context(), &artikal)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri čuvanju artikla", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
artikal.ID = id
|
||||
|
||||
// ako korisnik nije uneo šifru, auto-generišemo po prefiksu kategorije
|
||||
if artikal.Sifra == "" {
|
||||
autoSifra, e := h.Artikli.SledecaSifra(r.Context(), artikal.KategorijaID)
|
||||
if e != nil {
|
||||
autoSifra = fmt.Sprintf("ART-%04d", id)
|
||||
}
|
||||
artikal.ID = id
|
||||
artikal.Sifra = autoSifra
|
||||
if err := h.Artikli.Izmeni(r.Context(), &artikal); err != nil {
|
||||
slog.Error("greška pri upisu auto-šifre", "id", id, "err", err)
|
||||
}
|
||||
// veži izabrane dobavljače (forma); modal nema to polje pa ostaje prazno
|
||||
if e := h.Artikli.PostaviDobavljaceArtikla(r.Context(), id, citajDobavljaceForme(r)); e != nil {
|
||||
slog.Error("čuvanje dobavljača artikla nije uspelo", "artikal_id", id, "error", e)
|
||||
}
|
||||
|
||||
// fetch zahtev (iz modala) dobija JSON sa ID-em i nazivom novog artikla
|
||||
@@ -149,15 +163,25 @@ func (h *Handler) IzmeniArtikal(w http.ResponseWriter, r *http.Request) {
|
||||
katIDStr = strconv.FormatInt(*artikal.KategorijaID, 10)
|
||||
}
|
||||
|
||||
dobavljaci, _ := h.DobavljaciRepo.Lista(r.Context(), "")
|
||||
izabrani := map[int64]bool{}
|
||||
if ids, e := h.Artikli.DobavljaciArtikla(r.Context(), id); e == nil {
|
||||
for _, did := range ids {
|
||||
izabrani[did] = true
|
||||
}
|
||||
}
|
||||
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "magacin"
|
||||
ps.NaslovStranice = "Izmeni artikal"
|
||||
h.renderujFormuArtikla(w, PodaciFormeArtikla{
|
||||
PodaciStranice: ps,
|
||||
Artikal: *artikal,
|
||||
Kategorije: kategorije,
|
||||
KategorijaIDStr: katIDStr,
|
||||
Izmena: true,
|
||||
PodaciStranice: ps,
|
||||
Artikal: *artikal,
|
||||
Kategorije: kategorije,
|
||||
KategorijaIDStr: katIDStr,
|
||||
Dobavljaci: dobavljaci,
|
||||
IzabraniDobavljaci: izabrani,
|
||||
Izmena: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -184,6 +208,7 @@ func (h *Handler) SacuvajIzmenuArtikla(w http.ResponseWriter, r *http.Request) {
|
||||
if greska != "" {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
kategorije, _ := h.KategorijeRepo.Lista(r.Context())
|
||||
dobavljaci, _ := h.DobavljaciRepo.Lista(r.Context(), "")
|
||||
artikal.ID = id
|
||||
katIDStr := ""
|
||||
if artikal.KategorijaID != nil {
|
||||
@@ -193,12 +218,14 @@ func (h *Handler) SacuvajIzmenuArtikla(w http.ResponseWriter, r *http.Request) {
|
||||
ps.Stranica = "magacin"
|
||||
ps.NaslovStranice = "Izmeni artikal"
|
||||
h.renderujFormuArtikla(w, PodaciFormeArtikla{
|
||||
PodaciStranice: ps,
|
||||
Artikal: artikal,
|
||||
Kategorije: kategorije,
|
||||
KategorijaIDStr: katIDStr,
|
||||
Greska: greska,
|
||||
Izmena: true,
|
||||
PodaciStranice: ps,
|
||||
Artikal: artikal,
|
||||
Kategorije: kategorije,
|
||||
KategorijaIDStr: katIDStr,
|
||||
Dobavljaci: dobavljaci,
|
||||
IzabraniDobavljaci: mapaDobavljaca(citajDobavljaceForme(r)),
|
||||
Greska: greska,
|
||||
Izmena: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -207,6 +234,13 @@ func (h *Handler) SacuvajIzmenuArtikla(w http.ResponseWriter, r *http.Request) {
|
||||
var staraCena float64
|
||||
if stari, e := h.Artikli.DohvatiID(r.Context(), id); e == nil {
|
||||
staraCena = stari.ProdajnaCena
|
||||
// spreči promenu tipa sa proizvoda na uslugu/trošak ako artikal ima zalihu
|
||||
if stari.PratiLager() && stari.Kolicina > 0 && !artikal.PratiLager() {
|
||||
middleware.SetFlash(w, r, h.DB, "greska",
|
||||
fmt.Sprintf("Artikal ima %d %s na stanju. Prvo koriguj količinu na 0 pre promene tipa.", stari.Kolicina, stari.JedinicaMere))
|
||||
http.Redirect(w, r, "/magacin/izmeni/"+idStr, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
artikal.ID = id
|
||||
@@ -215,6 +249,11 @@ func (h *Handler) SacuvajIzmenuArtikla(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// ažuriraj dobavljače artikla prema formi
|
||||
if e := h.Artikli.PostaviDobavljaceArtikla(r.Context(), id, citajDobavljaceForme(r)); e != nil {
|
||||
slog.Error("čuvanje dobavljača artikla nije uspelo", "artikal_id", id, "error", e)
|
||||
}
|
||||
|
||||
// ako se prodajna cena promenila, automatski upiši nivelacioni zapis (izvor "izmena")
|
||||
if razlika := artikal.ProdajnaCena - staraCena; razlika > 0.005 || razlika < -0.005 {
|
||||
korisnikID := &k.ID
|
||||
@@ -232,6 +271,26 @@ func (h *Handler) SacuvajIzmenuArtikla(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/magacin?sacuvano=1", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// mapaDobavljaca pretvara listu ID-jeva u mapu za checked stanje u formi
|
||||
func mapaDobavljaca(ids []int64) map[int64]bool {
|
||||
m := make(map[int64]bool, len(ids))
|
||||
for _, id := range ids {
|
||||
m[id] = true
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// citajDobavljaceForme čita izabrane dobavljače (checkbox dobavljaci[]) iz forme
|
||||
func citajDobavljaceForme(r *http.Request) []int64 {
|
||||
var ids []int64
|
||||
for _, v := range r.Form["dobavljaci"] {
|
||||
if id, e := strconv.ParseInt(strings.TrimSpace(v), 10, 64); e == nil {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// parseFormuArtikla čita polja iz forme i vraća artikal i eventualnu grešku
|
||||
func parseFormuArtikla(r *http.Request) (model.Artikal, string) {
|
||||
naziv := r.FormValue("naziv")
|
||||
@@ -257,11 +316,8 @@ func parseFormuArtikla(r *http.Request) (model.Artikal, string) {
|
||||
artikal.Tip = model.TipProizvod
|
||||
}
|
||||
|
||||
// jedinica mere — podrazumevano "kom"
|
||||
artikal.JedinicaMere = r.FormValue("jedinica_mere")
|
||||
if artikal.JedinicaMere == "" {
|
||||
artikal.JedinicaMere = "kom"
|
||||
}
|
||||
// jedinica mere — normalizacija: mala slova, samo slova/brojevi, max 4 karaktera
|
||||
artikal.JedinicaMere = normalizujJM(r.FormValue("jedinica_mere"))
|
||||
|
||||
if k := r.FormValue("kolicina"); k != "" {
|
||||
v, err := strconv.Atoi(k)
|
||||
@@ -341,3 +397,22 @@ func (h *Handler) PredlogSifre(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Handler) renderujFormuArtikla(w http.ResponseWriter, podaci PodaciFormeArtikla) {
|
||||
h.renderujTemplate(w, "magacin_forma", podaci)
|
||||
}
|
||||
|
||||
// normalizujJM čisti jedinicu mere: mala slova, samo slova i brojevi, max 4 karaktera.
|
||||
// Ako je rezultat prazan, vraća "kom" kao podrazumevanu vrednost.
|
||||
func normalizujJM(s string) string {
|
||||
s = strings.ToLower(strings.TrimSpace(s))
|
||||
var b strings.Builder
|
||||
for _, r := range s {
|
||||
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
||||
b.WriteRune(r)
|
||||
if b.Len() >= 4 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if b.Len() == 0 {
|
||||
return "kom"
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -46,19 +46,27 @@ type PodaciDetaljiNabavke struct {
|
||||
}
|
||||
|
||||
// artikalUJSON pretvara listu artikala u template.JS vrednost bezbednu za umetanje u <script> tag
|
||||
func artikalUJSON(artikli []model.ArtikalSaKategorijom) template.JS {
|
||||
func artikalUJSON(artikli []model.ArtikalSaKategorijom, vezeDobavljaca map[int64][]int64) template.JS {
|
||||
type stavka struct {
|
||||
ID int64 `json:"id"`
|
||||
Naziv string `json:"naziv"`
|
||||
PdvStopa float64 `json:"pdv_stopa"`
|
||||
NabavnaCena float64 `json:"nabavna_cena"` // poslednja nabavna cena — predlog za Cena/kom
|
||||
Marza *float64 `json:"marza"` // marža artikla; null = nije postavljeno
|
||||
KategorijaMarza *float64 `json:"kategorija_marza"` // marža kategorije; fallback ako artikal nema
|
||||
Dobavljaci []int64 `json:"dobavljaci"` // ID-jevi dobavljača koji isporučuju artikal
|
||||
}
|
||||
lista := make([]stavka, 0, len(artikli))
|
||||
for _, a := range artikli {
|
||||
dob := vezeDobavljaca[a.ID]
|
||||
if dob == nil {
|
||||
dob = []int64{}
|
||||
}
|
||||
lista = append(lista, stavka{
|
||||
ID: a.ID, Naziv: a.Naziv, PdvStopa: a.PdvStopa,
|
||||
Marza: a.Marza, KategorijaMarza: a.KategorijaMarza,
|
||||
NabavnaCena: a.NabavnaCena,
|
||||
Marza: a.Marza, KategorijaMarza: a.KategorijaMarza,
|
||||
Dobavljaci: dob,
|
||||
})
|
||||
}
|
||||
b, _ := json.Marshal(lista)
|
||||
@@ -118,13 +126,15 @@ func (h *Handler) NovaNabavka(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
veze, _ := h.Artikli.SveDobavljaceArtikala(r.Context())
|
||||
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "nabavke"
|
||||
ps.NaslovStranice = "Nova nabavka"
|
||||
h.renderujFormuNabavke(w, PodaciFormeNabavke{
|
||||
PodaciStranice: ps,
|
||||
Artikli: artikli,
|
||||
ArtikliJSON: artikalUJSON(artikli),
|
||||
ArtikliJSON: artikalUJSON(artikli, veze),
|
||||
Dobavljaci: dobavljaci,
|
||||
Kategorije: kategorije,
|
||||
Marza: vrednostIliDefault(podesavanja, "kalkulacija_marza", "20"),
|
||||
@@ -149,13 +159,14 @@ func (h *Handler) SacuvajNabavku(w http.ResponseWriter, r *http.Request) {
|
||||
artikli, _ := h.Artikli.Lista(r.Context(), db.ArtikalFilter{})
|
||||
dobavljaci, _ := h.DobavljaciRepo.Lista(r.Context(), "")
|
||||
kategorije, _ := h.KategorijeRepo.Lista(r.Context())
|
||||
veze, _ := h.Artikli.SveDobavljaceArtikala(r.Context())
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "nabavke"
|
||||
ps.NaslovStranice = "Nova nabavka"
|
||||
h.renderujFormuNabavke(w, PodaciFormeNabavke{
|
||||
PodaciStranice: ps,
|
||||
Artikli: artikli,
|
||||
ArtikliJSON: artikalUJSON(artikli),
|
||||
ArtikliJSON: artikalUJSON(artikli, veze),
|
||||
Dobavljaci: dobavljaci,
|
||||
Kategorije: kategorije,
|
||||
Marza: vrednostIliDefault(podesavanja, "kalkulacija_marza", "20"),
|
||||
@@ -241,6 +252,15 @@ func (h *Handler) SacuvajNabavku(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// auto-veza artikal–dobavljač: svaki nabavljeni artikal se veže za dobavljača nabavke
|
||||
if nabavka.DobavljacID != nil {
|
||||
for _, s := range stavke {
|
||||
if e := h.Artikli.PoveziDobavljaca(r.Context(), s.ArtikalID, *nabavka.DobavljacID); e != nil {
|
||||
slog.Error("auto-veza dobavljača nije upisana", "artikal_id", s.ArtikalID, "error", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/nabavke/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
|
||||
@@ -325,13 +325,14 @@ func (h *Handler) ObrisiProdaju(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "prodaja.obrisi"); !ok {
|
||||
return
|
||||
}
|
||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||
id, err := parseID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.ProdajaRepo.Obrisi(r.Context(), id); err != nil {
|
||||
if err := h.ProdajaRepo.Obrisi(r.Context(), id, &k.ID); err != nil {
|
||||
http.Error(w, "Greška pri brisanju naloga", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
+17
-15
@@ -644,11 +644,7 @@ func (h *Handler) StampaServisa(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// QR kod vodi na javnu status stranicu — dostupnu bez prijave
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
nalogURL := scheme + "://" + r.Host + "/status/" + nalog.JavniToken
|
||||
nalogURL := qrNalogURL(r, nalog.JavniToken)
|
||||
var qrKod string
|
||||
if png, err := qrcode.Encode(nalogURL, qrcode.Medium, 160); err == nil {
|
||||
qrKod = base64.StdEncoding.EncodeToString(png)
|
||||
@@ -754,11 +750,7 @@ func (h *Handler) StampaOtpremnice(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
nalogURL := "http"
|
||||
if r.TLS != nil {
|
||||
nalogURL += "s"
|
||||
}
|
||||
nalogURL += "://" + r.Host + "/status/" + nalog.JavniToken
|
||||
nalogURL := qrNalogURL(r, nalog.JavniToken)
|
||||
var qrKodOtpr string
|
||||
if png, err := qrcode.Encode(nalogURL, qrcode.Medium, 160); err == nil {
|
||||
qrKodOtpr = base64.StdEncoding.EncodeToString(png)
|
||||
@@ -894,11 +886,7 @@ func (h *Handler) StampaPredracuna(w http.ResponseWriter, r *http.Request) {
|
||||
vaziDo := datumIzdavanja.AddDate(0, 0, rok)
|
||||
|
||||
// QR kod vodi na javnu status stranicu — dostupnu bez prijave
|
||||
nalogURL := "http"
|
||||
if r.TLS != nil {
|
||||
nalogURL += "s"
|
||||
}
|
||||
nalogURL += "://" + r.Host + "/status/" + nalog.JavniToken
|
||||
nalogURL := qrNalogURL(r, nalog.JavniToken)
|
||||
var qrKod string
|
||||
if png, err := qrcode.Encode(nalogURL, qrcode.Medium, 160); err == nil {
|
||||
qrKod = base64.StdEncoding.EncodeToString(png)
|
||||
@@ -991,3 +979,17 @@ func (h *Handler) ServisJavniStatus(w http.ResponseWriter, r *http.Request) {
|
||||
SviStatusi: model.SviStatusi,
|
||||
})
|
||||
}
|
||||
|
||||
// qrNalogURL konstruiše URL za QR kod vodeći računa o reverse proxy-ju.
|
||||
// Ako aplikacija radi iza nginx/Caddy/Traefik koji prekida TLS, r.TLS je nil,
|
||||
// ali X-Forwarded-Proto header sadrži stvarnu šemu.
|
||||
func qrNalogURL(r *http.Request, token string) string {
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
if r.Header.Get("X-Forwarded-Proto") == "https" {
|
||||
scheme = "https"
|
||||
}
|
||||
return scheme + "://" + r.Host + "/status/" + token
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
UPDATE kategorije
|
||||
SET kod = UPPER(SUBSTR(REPLACE(naziv, ' ', ''), 1, 4))
|
||||
WHERE (kod IS NULL OR kod = '') AND naziv IS NOT NULL AND naziv != '';
|
||||
@@ -1088,6 +1088,12 @@ select {
|
||||
#magacin-rezultati.bez-anim .tabela tbody tr,
|
||||
#magacin-rezultati.bez-anim .animiraj { animation: none; }
|
||||
|
||||
#klijenti-rezultati.bez-anim .tabela tbody tr,
|
||||
#klijenti-rezultati.bez-anim .animiraj { animation: none; }
|
||||
|
||||
#dobavljaci-rezultati.bez-anim .tabela tbody tr,
|
||||
#dobavljaci-rezultati.bez-anim .animiraj { animation: none; }
|
||||
|
||||
/* korisnikova preferencija animacije: body[data-animacija] nadjačava podrazumevano.
|
||||
Kad korisnik odabere stil, animiraju se i redovi tabela i mobilne kartice. */
|
||||
[data-animacija="bez"] .animiraj,
|
||||
|
||||
+40
-2
@@ -181,6 +181,13 @@ document.addEventListener('alpine:init', () => {
|
||||
},
|
||||
ukupnoSvega() {
|
||||
return this.stavke.reduce((z, s) => z + (parseFloat(s.kolicina) * parseFloat(s.cena) || 0), 0).toFixed(2)
|
||||
},
|
||||
// fmtDin formatira broj sa separatorom hiljada (tačka) i 2 decimale (zarez): 1234567.5 → "1.234.567,50"
|
||||
fmtDin(v) {
|
||||
const n = parseFloat(v) || 0
|
||||
const [ceo, dec] = Math.abs(n).toFixed(2).split('.')
|
||||
const saTackama = ceo.replace(/\B(?=(\d{3})+(?!\d))/g, '.')
|
||||
return (n < 0 ? '-' : '') + saTackama + ',' + dec
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -188,6 +195,8 @@ document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('nabavkaForma', () => ({
|
||||
stavke: [{artikal_id: '', kolicina: 1, cena: 0, marza: 0, prodajna: 0}],
|
||||
artikliOpcije: [],
|
||||
dobavljacId: '', // izabrani dobavljač nabavke — filtrira listu artikala
|
||||
prikaziSveArtikle: false, // true = prikaži sve artikle, ne samo dobavljačeve
|
||||
marzaDefault: 0,
|
||||
troskovi: [], // zavisni troškovi {naziv, iznos}
|
||||
metodRaspodele: 'vrednost', // 'vrednost' ili 'kolicina'
|
||||
@@ -226,9 +235,21 @@ document.addEventListener('alpine:init', () => {
|
||||
})
|
||||
},
|
||||
dodajStavku() {
|
||||
// ne dozvoli novu stavku dok poslednja nema izabran artikal
|
||||
const poslednja = this.stavke[this.stavke.length - 1]
|
||||
if (poslednja && !poslednja.artikal_id) {
|
||||
if (window.ntechToast) window.ntechToast('Prvo izaberi artikal u poslednjoj stavci.', 'greska')
|
||||
return
|
||||
}
|
||||
this.stavke.push({artikal_id: '', kolicina: 1, cena: 0, marza: this.marzaDefault, prodajna: 0})
|
||||
this.preracunajSve()
|
||||
},
|
||||
// artikli za prikaz u stavkama: bez dobavljača (ili uz "prikaži sve") svi, inače samo njegovi
|
||||
artikliZaDobavljaca() {
|
||||
if (!this.dobavljacId || this.prikaziSveArtikle) return this.artikliOpcije
|
||||
const did = Number(this.dobavljacId)
|
||||
return this.artikliOpcije.filter(a => Array.isArray(a.dobavljaci) && a.dobavljaci.includes(did))
|
||||
},
|
||||
// PDV stopa izabranog artikla (iz JSON liste) — za obračun prodajne cene.
|
||||
// Ako firma nije PDV obveznik, PDV se ne dodaje na prodajnu cenu (stopa = 0).
|
||||
pdvStopa(artikalId) {
|
||||
@@ -240,11 +261,13 @@ document.addEventListener('alpine:init', () => {
|
||||
izaberiArtikal(s) {
|
||||
const a = this.artikliOpcije.find(x => String(x.id) === String(s.artikal_id))
|
||||
if (a) {
|
||||
if (a.nabavna_cena != null) s.cena = a.nabavna_cena
|
||||
if (a.marza != null) s.marza = a.marza
|
||||
else if (a.kategorija_marza != null) s.marza = a.kategorija_marza
|
||||
else s.marza = this.marzaDefault
|
||||
}
|
||||
this.izracunajProdajnu(s)
|
||||
this.preracunajSve()
|
||||
},
|
||||
// ukupan zavisni trošak nabavke
|
||||
ukupanTrosak() {
|
||||
@@ -297,7 +320,12 @@ document.addEventListener('alpine:init', () => {
|
||||
this.preracunajSve()
|
||||
},
|
||||
ukloniStavku(i) {
|
||||
if (this.stavke.length > 1) this.stavke.splice(i, 1)
|
||||
if (this.stavke.length > 1) {
|
||||
this.stavke.splice(i, 1)
|
||||
} else {
|
||||
// poslednja stavka — ne brišemo red nego ga resetujemo na prazno
|
||||
this.stavke[0] = {artikal_id: '', kolicina: 1, cena: 0, marza: this.marzaDefault, prodajna: 0}
|
||||
}
|
||||
this.preracunajSve()
|
||||
},
|
||||
ukupnoStavke(s) {
|
||||
@@ -306,6 +334,13 @@ document.addEventListener('alpine:init', () => {
|
||||
ukupnoSvega() {
|
||||
return this.stavke.reduce((z, s) => z + (parseFloat(s.kolicina) * parseFloat(s.cena) || 0), 0).toFixed(2)
|
||||
},
|
||||
// fmtDin formatira broj sa separatorom hiljada (tačka) i 2 decimale (zarez): 1234567.5 → "1.234.567,50"
|
||||
fmtDin(v) {
|
||||
const n = parseFloat(v) || 0
|
||||
const [ceo, dec] = Math.abs(n).toFixed(2).split('.')
|
||||
const saTackama = ceo.replace(/\B(?=(\d{3})+(?!\d))/g, '.')
|
||||
return (n < 0 ? '-' : '') + saTackama + ',' + dec
|
||||
},
|
||||
otvoriModal() {
|
||||
this.modal = true
|
||||
this.modalGreska = ''
|
||||
@@ -352,7 +387,10 @@ document.addEventListener('alpine:init', () => {
|
||||
return
|
||||
}
|
||||
const noviArtikal = await odgovor.json()
|
||||
this.artikliOpcije.push({id: noviArtikal.id, naziv: noviArtikal.naziv})
|
||||
// veži novi artikal za izabranog dobavljača da se odmah prikaže u filtriranoj listi
|
||||
// (veza se trajno upisuje u bazu pri čuvanju nabavke — auto-veza)
|
||||
const dob = this.dobavljacId ? [Number(this.dobavljacId)] : []
|
||||
this.artikliOpcije.push({id: noviArtikal.id, naziv: noviArtikal.naziv, dobavljaci: dob})
|
||||
this.zatvoriModal()
|
||||
} catch {
|
||||
this.modalGreska = 'Greška pri komunikaciji sa serverom.'
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" />
|
||||
</svg>
|
||||
</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{ printf "%.0f" .PrihodOvogMeseca }} din</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{ dinariCeli .PrihodOvogMeseca }} din</div>
|
||||
<div style="font-size:14px;color:var(--tekst-glavni);margin-top:4px;font-weight:500;">Prihod ovog meseca</div>
|
||||
</a>
|
||||
{{ end }}
|
||||
@@ -126,7 +126,7 @@
|
||||
{{ end }}
|
||||
</div>
|
||||
<div style="text-align:right;flex-shrink:0;">
|
||||
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);">{{ printf "%.0f" .Ukupno }} din</div>
|
||||
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);">{{ dinariCeli .Ukupno }} din</div>
|
||||
<div style="font-size:11px;color:var(--tekst-sporedni);margin-top:1px;">{{ .Datum }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,19 +12,21 @@
|
||||
<div class="poruka-uspeh poruka-animacija">Dobavljač je uspešno obrisan.</div>
|
||||
{{end}}
|
||||
|
||||
<!-- gornja traka: dugme + pretraga -->
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
|
||||
<!-- gornja traka: dugme + interaktivna pretraga -->
|
||||
<div id="dobavljaci-filteri" style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
|
||||
<a href="/dobavljaci/novi" class="btn-primarno">+ Novi dobavljač</a>
|
||||
<form method="GET" action="/dobavljaci" style="display:flex;gap:8px;flex:1;min-width:200px;">
|
||||
<form method="GET" action="/dobavljaci" style="display:flex;gap:8px;flex:1;min-width:200px;"
|
||||
hx-get="/dobavljaci" hx-target="#dobavljaci-rezultati" hx-select="#dobavljaci-rezultati" hx-swap="innerHTML" hx-push-url="true">
|
||||
<input type="text" name="pretraga" value="{{.Pretraga}}"
|
||||
placeholder="Pretraži dobavljače..."
|
||||
style="flex:1;">
|
||||
<button type="submit" class="btn-primarno">
|
||||
Traži
|
||||
</button>
|
||||
style="flex:1;"
|
||||
hx-trigger="keyup[target.value.length==0||target.value.length>=3] changed delay:150ms, search"
|
||||
hx-get="/dobavljaci" hx-target="#dobavljaci-rezultati" hx-select="#dobavljaci-rezultati" hx-swap="innerHTML" hx-push-url="true">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="dobavljaci-rezultati">
|
||||
|
||||
<!-- desktop tabela -->
|
||||
<div class="kartica dobavljaci-tabela animiraj" style="padding:0;overflow:hidden;">
|
||||
<div style="overflow-x:auto;">
|
||||
@@ -46,7 +48,7 @@
|
||||
{{if .KontaktOsoba}}{{.KontaktOsoba}}{{else}}—{{end}}
|
||||
</td>
|
||||
<td style="padding:12px 16px;font-size:13px;color:var(--tekst-sporedni);">
|
||||
{{if .Telefon}}{{.Telefon}}{{else}}—{{end}}
|
||||
{{if .Telefon}}{{telefon .Telefon}}{{else}}—{{end}}
|
||||
</td>
|
||||
<td style="padding:12px 16px;font-size:13px;color:var(--tekst-sporedni);">
|
||||
{{if .Email}}{{.Email}}{{else}}—{{end}}
|
||||
@@ -70,7 +72,7 @@
|
||||
{{else}}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:32px;text-align:center;font-size:14px;color:var(--tekst-sporedni);">
|
||||
Nema dobavljača. <a href="/dobavljaci/novi" style="color:var(--sb-akcent);">Dodaj prvog dobavljača.</a>
|
||||
{{if $.Pretraga}}Za datu pretragu nema dobavljača u bazi.{{else}}Nema dobavljača. <a href="/dobavljaci/novi" style="color:var(--sb-akcent);">Dodaj prvog dobavljača.</a>{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
@@ -107,7 +109,7 @@
|
||||
{{end}}
|
||||
{{if .Telefon}}
|
||||
<div class="pomocni-tekst">
|
||||
<span style="color:var(--tekst-glavni);font-weight:500;">Telefon:</span> {{.Telefon}}
|
||||
<span style="color:var(--tekst-glavni);font-weight:500;">Telefon:</span> {{telefon .Telefon}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Email}}
|
||||
@@ -122,10 +124,33 @@
|
||||
</div>
|
||||
{{else}}
|
||||
<div style="padding:32px;text-align:center;font-size:14px;color:var(--tekst-sporedni);">
|
||||
Nema dobavljača. <a href="/dobavljaci/novi" style="color:var(--sb-akcent);">Dodaj prvog dobavljača.</a>
|
||||
{{if $.Pretraga}}Za datu pretragu nema dobavljača u bazi.{{else}}Nema dobavljača. <a href="/dobavljaci/novi" style="color:var(--sb-akcent);">Dodaj prvog dobavljača.</a>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
</div><!-- kraj #dobavljaci-rezultati -->
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Gasi animaciju redova pri pretrazi.
|
||||
document.body.addEventListener('htmx:beforeRequest', function (e) {
|
||||
var elt = e.detail && e.detail.elt;
|
||||
if (elt && elt.name === 'pretraga') {
|
||||
var rez = document.getElementById('dobavljaci-rezultati');
|
||||
if (rez) rez.classList.add('bez-anim');
|
||||
}
|
||||
});
|
||||
// Reflow pri sidebar navigaciji da nema bljeska animacije.
|
||||
document.body.addEventListener('htmx:afterSwap', function (e) {
|
||||
var t = e.detail && e.detail.target;
|
||||
if (t && t.id === 'dobavljaci-rezultati') return;
|
||||
var rez = document.getElementById('dobavljaci-rezultati');
|
||||
if (!rez) return;
|
||||
rez.classList.add('bez-anim');
|
||||
void rez.offsetHeight;
|
||||
rez.classList.remove('bez-anim');
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -61,13 +61,13 @@
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:8px 12px;font-size:13px;color:var(--tekst-glavni);">{{.MesecPrikaz}}</td>
|
||||
<td style="padding:8px 12px;text-align:right;font-size:13px;color:var(--tekst-sporedni);">
|
||||
{{if gt .Prodaja 0.0}}{{printf "%.0f" .Prodaja}} din{{else}}—{{end}}
|
||||
{{if gt .Prodaja 0.0}}{{dinariCeli .Prodaja}} din{{else}}—{{end}}
|
||||
</td>
|
||||
<td style="padding:8px 12px;text-align:right;font-size:13px;color:var(--tekst-sporedni);">
|
||||
{{if gt .Servis 0.0}}{{printf "%.0f" .Servis}} din{{else}}—{{end}}
|
||||
{{if gt .Servis 0.0}}{{dinariCeli .Servis}} din{{else}}—{{end}}
|
||||
</td>
|
||||
<td style="padding:8px 12px;text-align:right;font-size:13px;font-weight:500;color:var(--tekst-glavni);">
|
||||
{{if gt .Ukupno 0.0}}{{printf "%.0f" .Ukupno}} din{{else}}—{{end}}
|
||||
{{if gt .Ukupno 0.0}}{{dinariCeli .Ukupno}} din{{else}}—{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
@@ -145,7 +145,7 @@
|
||||
<div style="font-size:11px;color:var(--tekst-sporedni);margin-top:1px;">{{.Kategorija}}</div>
|
||||
</td>
|
||||
<td style="padding:7px 8px;text-align:right;font-size:13px;color:var(--tekst-glavni);">{{.UkupnoKolicina}}</td>
|
||||
<td style="padding:7px 8px;text-align:right;font-size:13px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.0f" .UkupnoPrihod}} din</td>
|
||||
<td style="padding:7px 8px;text-align:right;font-size:13px;font-weight:500;color:var(--tekst-glavni);">{{dinariCeli .UkupnoPrihod}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
@@ -176,7 +176,7 @@
|
||||
</td>
|
||||
<td style="padding:7px 8px;font-size:13px;color:var(--tekst-glavni);">{{.Naziv}}</td>
|
||||
<td style="padding:7px 8px;text-align:right;font-size:13px;color:var(--tekst-sporedni);">{{.BrojNaloga}}</td>
|
||||
<td style="padding:7px 8px;text-align:right;font-size:13px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.0f" .UkupnoVrednost}} din</td>
|
||||
<td style="padding:7px 8px;text-align:right;font-size:13px;font-weight:500;color:var(--tekst-glavni);">{{dinariCeli .UkupnoVrednost}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
<input type="text" name="kod" value="{{.Kod}}" maxlength="10"
|
||||
placeholder="npr. MEM"
|
||||
style="width:100%;padding:8px 12px;border:0.5px solid var(--ivica);border-radius:8px;font-size:14px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;text-transform:uppercase;">
|
||||
{{if not .Kod}}<div class="pomocni-tekst" style="margin-top:4px;color:#f59e0b;">Kôd nije postavljen — artikli će koristiti prefiks ART-.</div>{{end}}
|
||||
</div>
|
||||
<div>
|
||||
<label class="polje-labela">Opis</label>
|
||||
|
||||
@@ -21,15 +21,31 @@
|
||||
placeholder="Pretraži po imenu ili nazivu firme..."
|
||||
style="flex:1;"
|
||||
hx-trigger="keyup changed delay:300ms, search"
|
||||
hx-get="/klijenti" hx-target="#klijenti-rezultati" hx-select="#klijenti-rezultati" hx-swap="innerHTML" hx-push-url="true">
|
||||
<button type="submit" class="btn-primarno">
|
||||
Traži
|
||||
</button>
|
||||
hx-get="/klijenti" hx-include="[name='tip']:checked" hx-target="#klijenti-rezultati" hx-select="#klijenti-rezultati" hx-swap="innerHTML" hx-push-url="true">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="klijenti-rezultati">
|
||||
|
||||
<!-- filter po tipu klijenta -->
|
||||
<div style="display:flex;gap:16px;margin-bottom:12px;flex-wrap:wrap;align-items:center;">
|
||||
<label style="display:inline-flex;align-items:center;gap:6px;cursor:pointer;font-size:14px;">
|
||||
<input type="radio" name="tip" value="" {{if eq .TipFilter ""}}checked{{end}}
|
||||
hx-trigger="change" hx-get="/klijenti" hx-include="[name='pretraga']"
|
||||
hx-target="#klijenti-rezultati" hx-select="#klijenti-rezultati" hx-swap="innerHTML" hx-push-url="true"> Svi
|
||||
</label>
|
||||
<label style="display:inline-flex;align-items:center;gap:6px;cursor:pointer;font-size:14px;">
|
||||
<input type="radio" name="tip" value="pravno" {{if eq .TipFilter "pravno"}}checked{{end}}
|
||||
hx-trigger="change" hx-get="/klijenti" hx-include="[name='pretraga']"
|
||||
hx-target="#klijenti-rezultati" hx-select="#klijenti-rezultati" hx-swap="innerHTML" hx-push-url="true"> Firme
|
||||
</label>
|
||||
<label style="display:inline-flex;align-items:center;gap:6px;cursor:pointer;font-size:14px;">
|
||||
<input type="radio" name="tip" value="fizicko" {{if eq .TipFilter "fizicko"}}checked{{end}}
|
||||
hx-trigger="change" hx-get="/klijenti" hx-include="[name='pretraga']"
|
||||
hx-target="#klijenti-rezultati" hx-select="#klijenti-rezultati" hx-swap="innerHTML" hx-push-url="true"> Fizička lica
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- desktop tabela -->
|
||||
<div class="kartica klijenti-tabela kartica-tabela animiraj">
|
||||
<div class="tabela-skrol">
|
||||
@@ -57,7 +73,7 @@
|
||||
{{end}}
|
||||
</td>
|
||||
<td class="pomocni-tekst">
|
||||
{{if .Telefon}}{{.Telefon}}{{else}}—{{end}}
|
||||
{{if .Telefon}}{{telefon .Telefon}}{{else}}—{{end}}
|
||||
</td>
|
||||
<td class="pomocni-tekst">
|
||||
{{if .Email}}{{.Email}}{{else}}—{{end}}
|
||||
@@ -84,7 +100,7 @@
|
||||
{{else}}
|
||||
<tr>
|
||||
<td colspan="5" class="prazno-stanje">
|
||||
Nema klijenata. <a href="/klijenti/novi" style="color:var(--sb-akcent);">Dodaj prvog klijenta.</a>
|
||||
{{if $.Pretraga}}Za datu pretragu nema klijenata u bazi.{{else}}Nema klijenata. <a href="/klijenti/novi" style="color:var(--sb-akcent);">Dodaj prvog klijenta.</a>{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
@@ -125,7 +141,7 @@
|
||||
<div class="kolona" style="gap:6px;">
|
||||
{{if .Telefon}}
|
||||
<div class="pomocni-tekst">
|
||||
<span style="color:var(--tekst-glavni);font-weight:500;">Telefon:</span> {{.Telefon}}
|
||||
<span style="color:var(--tekst-glavni);font-weight:500;">Telefon:</span> {{telefon .Telefon}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Email}}
|
||||
@@ -140,7 +156,7 @@
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="prazno-stanje">
|
||||
Nema klijenata. <a href="/klijenti/novi" style="color:var(--sb-akcent);">Dodaj prvog klijenta.</a>
|
||||
{{if $.Pretraga}}Za datu pretragu nema klijenata u bazi.{{else}}Nema klijenata. <a href="/klijenti/novi" style="color:var(--sb-akcent);">Dodaj prvog klijenta.</a>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -163,4 +179,15 @@
|
||||
</div><!-- kraj #klijenti-rezultati -->
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Gasi animaciju redova pri pretrazi i promeni tipa (ali NE pri paginaciji).
|
||||
document.body.addEventListener('htmx:beforeRequest', function (e) {
|
||||
var elt = e.detail && e.detail.elt;
|
||||
if (elt && (elt.name === 'pretraga' || elt.name === 'tip')) {
|
||||
var rez = document.getElementById('klijenti-rezultati');
|
||||
if (rez) rez.classList.add('bez-anim');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
<input type="text" name="pretraga" value="{{.Filter.Pretraga}}"
|
||||
placeholder="Pretraži po nazivu, šifri, barkodu, lokaciji..."
|
||||
style="width:100%;"
|
||||
hx-trigger="keyup changed delay:300ms, search"
|
||||
hx-get="/magacin" hx-target="#magacin-rezultati" hx-select="#magacin-rezultati" hx-swap="innerHTML" hx-push-url="true">
|
||||
hx-trigger="keyup[target.value.length==0||target.value.length>=3] changed delay:150ms, search"
|
||||
hx-get="/magacin" hx-include="closest form" hx-target="#magacin-rezultati" hx-select="#magacin-rezultati" hx-swap="innerHTML" hx-push-url="true">
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
|
||||
<select name="kategorija" style="flex:1;min-width:140px;"
|
||||
hx-trigger="change"
|
||||
@@ -63,9 +63,6 @@
|
||||
hx-get="/magacin" hx-target="#magacin-rezultati" hx-select="#magacin-rezultati" hx-swap="innerHTML" hx-push-url="true">
|
||||
Arhivirani
|
||||
</label>
|
||||
<button type="submit" class="btn-primarno">
|
||||
Traži
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -104,7 +101,7 @@
|
||||
<span style="font-size:12px;color:var(--tekst-sporedni);">{{if eq .Tip "usluga"}}usluga{{else}}trošak{{end}}</span>
|
||||
{{end}}
|
||||
</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-size:14px;color:var(--tekst-glavni);">{{printf "%.0f" .ProdajnaCena}} din</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-size:14px;color:var(--tekst-glavni);">{{dinariCeli .ProdajnaCena}} din</td>
|
||||
<td style="padding:12px 16px;font-size:13px;color:var(--tekst-sporedni);">
|
||||
{{if .Lokacija}}{{.Lokacija}}{{else}}—{{end}}
|
||||
</td>
|
||||
@@ -138,7 +135,7 @@
|
||||
{{else}}
|
||||
<tr>
|
||||
<td colspan="7" style="padding:32px;text-align:center;font-size:14px;color:var(--tekst-sporedni);">
|
||||
Nema artikala. <a href="/magacin/novi" style="color:var(--sb-akcent);">Dodaj prvi artikal.</a>
|
||||
{{if or $.Filter.Pretraga $.KategorijaIDStr $.Filter.SamoKriticni}}Za datu pretragu nema artikala u bazi.{{else}}Nema artikala. <a href="/magacin/novi" style="color:var(--sb-akcent);">Dodaj prvi artikal.</a>{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
@@ -195,7 +192,7 @@
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="pomocni-tekst">
|
||||
<span style="color:var(--tekst-glavni);font-weight:500;">Cena:</span> {{printf "%.0f" .ProdajnaCena}} din
|
||||
<span style="color:var(--tekst-glavni);font-weight:500;">Cena:</span> {{dinariCeli .ProdajnaCena}} din
|
||||
</div>
|
||||
{{if .Lokacija}}
|
||||
<div class="pomocni-tekst">
|
||||
@@ -206,7 +203,7 @@
|
||||
</div>
|
||||
{{else}}
|
||||
<div style="padding:32px;text-align:center;font-size:14px;color:var(--tekst-sporedni);">
|
||||
Nema artikala. <a href="/magacin/novi" style="color:var(--sb-akcent);">Dodaj prvi artikal.</a>
|
||||
{{if or $.Filter.Pretraga $.KategorijaIDStr $.Filter.SamoKriticni}}Za datu pretragu nema artikala u bazi.{{else}}Nema artikala. <a href="/magacin/novi" style="color:var(--sb-akcent);">Dodaj prvi artikal.</a>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -242,6 +239,20 @@ document.body.addEventListener('htmx:beforeRequest', function (e) {
|
||||
if (rez) rez.classList.add('bez-anim');
|
||||
}
|
||||
});
|
||||
|
||||
// Pri sidebar navigaciji (boost menja ceo #glavni-sadrzaj) browser nakratko
|
||||
// prikaže redove pre nego što animacija krene. Forsiramo reflow: prvo ugasimo
|
||||
// animaciju, pa je odmah pustimo iz from-stanja → nema bljeska. Pretragu/
|
||||
// paginaciju (target = #magacin-rezultati) NE diramo.
|
||||
document.body.addEventListener('htmx:afterSwap', function (e) {
|
||||
var t = e.detail && e.detail.target;
|
||||
if (t && t.id === 'magacin-rezultati') return;
|
||||
var rez = document.getElementById('magacin-rezultati');
|
||||
if (!rez) return;
|
||||
rez.classList.add('bez-anim');
|
||||
void rez.offsetHeight;
|
||||
rez.classList.remove('bez-anim');
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
@@ -276,7 +287,7 @@ document.body.addEventListener('htmx:beforeRequest', function (e) {
|
||||
<button type="submit" class="premesti-zatvori" aria-label="Zatvori">×</button>
|
||||
</form>
|
||||
<form method="POST" action="/magacin/promeni-cenu/{{.ID}}" style="display:flex;flex-direction:column;gap:12px;padding:16px;">
|
||||
<div style="font-size:13px;color:var(--tekst-sporedni);">Trenutna cena: <strong>{{printf "%.0f" .Cena}} din</strong></div>
|
||||
<div style="font-size:13px;color:var(--tekst-sporedni);">Trenutna cena: <strong>{{dinariCeli .Cena}} din</strong></div>
|
||||
<div>
|
||||
<label class="polje-labela">Nova cena (din)</label>
|
||||
<input type="number" name="nova_cena" min="0" step="0.01" value="{{printf "%.2f" .Cena}}" required
|
||||
|
||||
@@ -128,6 +128,22 @@
|
||||
placeholder="prazno = po kategoriji / globalna">
|
||||
</div>
|
||||
|
||||
<!-- dobavljači koji isporučuju artikal -->
|
||||
{{if .Dobavljaci}}
|
||||
<div>
|
||||
<label class="polje-labela">Dobavljači</label>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:6px;padding:10px 12px;border:0.5px solid var(--ivica);border-radius:8px;">
|
||||
{{range .Dobavljaci}}
|
||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;padding:4px 2px;">
|
||||
<input type="checkbox" name="dobavljaci" value="{{.ID}}" {{if index $.IzabraniDobavljaci .ID}}checked{{end}} style="flex-shrink:0;">
|
||||
<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{.Naziv}}</span>
|
||||
</label>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="pomocni-tekst" style="margin-top:4px;">Određuje koje artikle dobavljač nudi pri nabavci.</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- lokacija (samo za proizvode) -->
|
||||
<div data-lager>
|
||||
<label class="polje-labela">Lokacija u magacinu</label>
|
||||
@@ -184,6 +200,28 @@
|
||||
.then(function (t) { if (t) sifra.value = t; });
|
||||
});
|
||||
}
|
||||
|
||||
// dvosmerno povezivanje: nabavna + marža → prodajna, i prodajna → marža
|
||||
var nabavna = document.querySelector('[name="nabavna_cena"]');
|
||||
var prodajna = document.querySelector('[name="prodajna_cena"]');
|
||||
var marza = document.querySelector('[name="marza"]');
|
||||
function broj(el) { return parseFloat(el.value) || 0; }
|
||||
// postavljanje .value programski ne okida 'input', pa nema beskonačne petlje
|
||||
function izProdajne() {
|
||||
var n = broj(nabavna);
|
||||
if (n <= 0) return;
|
||||
prodajna.value = (n * (1 + broj(marza) / 100)).toFixed(2);
|
||||
}
|
||||
function izMarze() {
|
||||
var n = broj(nabavna), p = broj(prodajna);
|
||||
if (n <= 0) { marza.value = ''; return; }
|
||||
marza.value = ((p / n - 1) * 100).toFixed(2);
|
||||
}
|
||||
if (nabavna && prodajna && marza) {
|
||||
marza.addEventListener('input', izProdajne);
|
||||
nabavna.addEventListener('input', izProdajne);
|
||||
prodajna.addEventListener('input', izMarze);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
@@ -39,6 +39,55 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- dobavljači artikla -->
|
||||
<div class="kartica animiraj">
|
||||
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);margin-bottom:14px;padding-bottom:10px;border-bottom:0.5px solid var(--ivica);">
|
||||
Dobavljači
|
||||
<span style="font-size:12px;font-weight:400;color:var(--tekst-slabi);margin-left:8px;">{{len .Dobavljaci}}</span>
|
||||
</div>
|
||||
|
||||
{{if .Dobavljaci}}
|
||||
<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:16px;">
|
||||
{{range .Dobavljaci}}
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border:0.5px solid var(--ivica);border-radius:8px;">
|
||||
<div>
|
||||
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{.Naziv}}</div>
|
||||
{{if or .Telefon .KontaktOsoba}}
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:2px;">
|
||||
{{if .KontaktOsoba}}{{.KontaktOsoba}}{{end}}{{if and .KontaktOsoba .Telefon}} · {{end}}{{if .Telefon}}{{telefon .Telefon}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if index $.Dozvole "artikal.izmeni"}}
|
||||
<form method="POST" action="/magacin/kartica/{{$.Artikal.ID}}/dobavljac/obrisi" style="margin:0;">
|
||||
<input type="hidden" name="dobavljac_id" value="{{.ID}}">
|
||||
<button type="submit" class="btn-obrisi-malo" data-potvrda="Ukloniti dobavljača {{.Naziv}} sa ovog artikla?">Ukloni</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div style="font-size:13px;color:var(--tekst-sporedni);margin-bottom:16px;">Nijedan dobavljač nije vezan za ovaj artikal.</div>
|
||||
{{end}}
|
||||
|
||||
{{if index .Dozvole "artikal.izmeni"}}
|
||||
{{if .DostupniDobavljaci}}
|
||||
<form method="POST" action="/magacin/kartica/{{.Artikal.ID}}/dobavljac/dodaj" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
||||
<select name="dobavljac_id" required style="flex:1;min-width:200px;">
|
||||
<option value="">— izaberi dobavljača —</option>
|
||||
{{range .DostupniDobavljaci}}
|
||||
<option value="{{.ID}}">{{.Naziv}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
<button type="submit" class="btn-primarno">+ Dodaj dobavljača</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<div style="font-size:12px;color:var(--tekst-slabi);">Svi dobavljači su već vezani za ovaj artikal.</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- tabela promena -->
|
||||
<div class="kartica animiraj">
|
||||
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);margin-bottom:14px;padding-bottom:10px;border-bottom:0.5px solid var(--ivica);">
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Ukupan iznos</div>
|
||||
<div style="font-size:20px;font-weight:600;color:var(--sb-akcent);">
|
||||
{{printf "%.2f" .Nabavka.Ukupno}} din
|
||||
{{dinari .Nabavka.Ukupno}} din
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,8 +71,8 @@
|
||||
<tr class="animiraj red-tabele">
|
||||
<td style="padding:10px 16px;font-size:14px;color:var(--tekst-glavni);">{{.ArtikalNaziv}}</td>
|
||||
<td style="padding:10px 16px;text-align:center;font-size:14px;color:var(--tekst-glavni);">{{.Kolicina}}</td>
|
||||
<td style="padding:10px 16px;text-align:right;font-size:14px;color:var(--tekst-sporedni);">{{printf "%.2f" .CenaPoKomadu}} din</td>
|
||||
<td style="padding:10px 16px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.2f" .Ukupno}} din</td>
|
||||
<td style="padding:10px 16px;text-align:right;font-size:14px;color:var(--tekst-sporedni);">{{dinari .CenaPoKomadu}} din</td>
|
||||
<td style="padding:10px 16px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{dinari .Ukupno}} din</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr>
|
||||
@@ -86,7 +86,7 @@
|
||||
<tfoot>
|
||||
<tr style="border-top:0.5px solid var(--ivica);">
|
||||
<td colspan="3" style="padding:10px 16px;text-align:right;font-size:13px;font-weight:500;color:var(--tekst-sporedni);">Ukupno:</td>
|
||||
<td style="padding:10px 16px;text-align:right;font-size:15px;font-weight:600;color:var(--tekst-glavni);">{{printf "%.2f" .Nabavka.Ukupno}} din</td>
|
||||
<td style="padding:10px 16px;text-align:right;font-size:15px;font-weight:600;color:var(--tekst-glavni);">{{dinari .Nabavka.Ukupno}} din</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
{{end}}
|
||||
@@ -105,18 +105,18 @@
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:var(--tekst-sporedni);">Cena/kom</div>
|
||||
<div style="font-size:14px;color:var(--tekst-sporedni);">{{printf "%.2f" .CenaPoKomadu}} din</div>
|
||||
<div style="font-size:14px;color:var(--tekst-sporedni);">{{dinari .CenaPoKomadu}} din</div>
|
||||
</div>
|
||||
<div style="text-align:right;">
|
||||
<div style="font-size:11px;color:var(--tekst-sporedni);">Ukupno</div>
|
||||
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.2f" .Ukupno}} din</div>
|
||||
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{dinari .Ukupno}} din</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Stavke}}
|
||||
<div style="text-align:right;font-size:15px;font-weight:600;color:var(--tekst-glavni);padding:8px 4px;">
|
||||
Ukupno: {{printf "%.2f" .Nabavka.Ukupno}} din
|
||||
Ukupno: {{dinari .Nabavka.Ukupno}} din
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -135,12 +135,12 @@
|
||||
{{range .Troskovi}}
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:0.5px solid var(--ivica);">
|
||||
<span style="font-size:14px;color:var(--tekst-glavni);">{{.Naziv}}</span>
|
||||
<span style="font-size:14px;color:var(--tekst-sporedni);">{{printf "%.2f" .Iznos}} din</span>
|
||||
<span style="font-size:14px;color:var(--tekst-sporedni);">{{dinari .Iznos}} din</span>
|
||||
</div>
|
||||
{{end}}
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;">
|
||||
<span style="font-size:13px;font-weight:500;color:var(--tekst-sporedni);">Ukupno troškovi:</span>
|
||||
<span style="font-size:15px;font-weight:600;color:var(--tekst-glavni);">{{printf "%.2f" .UkupanTrosak}} din</span>
|
||||
<span style="font-size:15px;font-weight:600;color:var(--tekst-glavni);">{{dinari .UkupanTrosak}} din</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -47,12 +47,19 @@
|
||||
+ Novi dobavljač
|
||||
</button>
|
||||
</div>
|
||||
<select name="dobavljac_id" x-ref="selDobavljac" style="width:100%;">
|
||||
<select name="dobavljac_id" x-ref="selDobavljac" x-model="dobavljacId" style="width:100%;">
|
||||
<option value="">— bez dobavljača —</option>
|
||||
{{range .Dobavljaci}}
|
||||
<option value="{{.ID}}">{{.Naziv}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
<label x-show="dobavljacId" style="display:inline-flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;margin-top:8px;color:var(--tekst-sporedni);">
|
||||
<input type="checkbox" x-model="prikaziSveArtikle">
|
||||
Prikaži sve artikle (ne samo dobavljačeve)
|
||||
</label>
|
||||
<div x-show="dobavljacId && prikaziSveArtikle" class="pomocni-tekst" style="margin-top:4px;">
|
||||
Napomena: izabrani artikal koji nije od ovog dobavljača biće mu dodat pri čuvanju nabavke.
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="polje-labela">Napomena</label>
|
||||
@@ -73,9 +80,6 @@
|
||||
onmouseover="this.style.background='var(--pozadina)'" onmouseout="this.style.background='var(--kartica)'">
|
||||
+ Novi artikal
|
||||
</button>
|
||||
<button type="button" @click="dodajStavku()" class="btn-primarno" style="font-size:13px;padding:6px 14px;">
|
||||
+ Dodaj stavku
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -100,7 +104,7 @@
|
||||
<select :name="'artikal_id[]'" x-model="stavka.artikal_id"
|
||||
@change="izaberiArtikal(stavka)" :disabled="isMobile" style="width:100%;">
|
||||
<option value="">— odaberi artikal —</option>
|
||||
<template x-for="a in artikliOpcije" :key="a.id">
|
||||
<template x-for="a in artikliZaDobavljaca()" :key="a.id">
|
||||
<option :value="a.id" x-text="a.naziv"></option>
|
||||
</template>
|
||||
</select>
|
||||
@@ -126,11 +130,10 @@
|
||||
min="0" step="0.01" :disabled="isMobile" style="width:100%;text-align:right;">
|
||||
</td>
|
||||
<td style="padding:8px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||
<span x-text="ukupnoStavke(stavka) + ' din'"></span>
|
||||
<span x-text="fmtDin(ukupnoStavke(stavka)) + ' din'"></span>
|
||||
</td>
|
||||
<td style="padding:8px 10px;text-align:center;">
|
||||
<button type="button" @click="ukloniStavku(i)"
|
||||
x-show="stavke.length > 1"
|
||||
style="background:none;border:none;cursor:pointer;color:#dc2626;font-size:18px;line-height:1;padding:2px 6px;border-radius:4px;transition:background 0.15s;"
|
||||
onmouseover="this.style.background='rgba(220,38,38,0.08)'"
|
||||
onmouseout="this.style.background='none'"
|
||||
@@ -143,13 +146,17 @@
|
||||
<tr style="border-top:0.5px solid var(--ivica);">
|
||||
<td colspan="5" style="padding:10px 10px;text-align:right;font-size:13px;color:var(--tekst-sporedni);font-weight:500;">Ukupno:</td>
|
||||
<td style="padding:10px 10px;text-align:right;font-size:15px;font-weight:600;color:var(--tekst-glavni);">
|
||||
<span x-text="ukupnoSvega() + ' din'"></span>
|
||||
<span x-text="fmtDin(ukupnoSvega()) + ' din'"></span>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<button type="button" x-show="!isMobile" @click="dodajStavku()" class="btn-primarno"
|
||||
style="width:100%;font-size:14px;padding:10px;margin-top:10px;">
|
||||
+ Dodaj stavku
|
||||
</button>
|
||||
|
||||
<!-- mobilne kartice stavki (display kontroliše .stavke-kartice: none na desktopu,
|
||||
flex na mobilnom @media — inline display:none bi pobedio @media, zato ga NEMA) -->
|
||||
@@ -160,7 +167,6 @@
|
||||
<span style="font-size:13px;font-weight:500;color:var(--tekst-sporedni);"
|
||||
x-text="'Stavka ' + (i + 1)"></span>
|
||||
<button type="button" @click="ukloniStavku(i)"
|
||||
x-show="stavke.length > 1"
|
||||
style="background:none;border:0.5px solid #dc2626;color:#dc2626;cursor:pointer;font-size:13px;padding:2px 8px;border-radius:4px;">
|
||||
Ukloni
|
||||
</button>
|
||||
@@ -171,7 +177,7 @@
|
||||
<select :name="'artikal_id[]'" x-model="stavka.artikal_id"
|
||||
@change="izaberiArtikal(stavka)" :disabled="!isMobile" style="width:100%;">
|
||||
<option value="">— odaberi artikal —</option>
|
||||
<template x-for="a in artikliOpcije" :key="a.id">
|
||||
<template x-for="a in artikliZaDobavljaca()" :key="a.id">
|
||||
<option :value="a.id" x-text="a.naziv"></option>
|
||||
</template>
|
||||
</select>
|
||||
@@ -197,13 +203,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||
Ukupno: <span x-text="ukupnoStavke(stavka) + ' din'"></span>
|
||||
Ukupno: <span x-text="fmtDin(ukupnoStavke(stavka)) + ' din'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<button type="button" x-show="isMobile" @click="dodajStavku()" class="btn-primarno"
|
||||
style="width:100%;font-size:14px;padding:10px;margin-top:4px;">
|
||||
+ Dodaj stavku
|
||||
</button>
|
||||
<div style="text-align:right;font-size:15px;font-weight:600;color:var(--tekst-glavni);padding:8px 4px;">
|
||||
Ukupno: <span x-text="ukupnoSvega() + ' din'"></span>
|
||||
Ukupno: <span x-text="fmtDin(ukupnoSvega()) + ' din'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -241,7 +251,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<div style="text-align:right;font-size:13px;color:var(--tekst-sporedni);">
|
||||
Ukupno troškovi: <strong x-text="ukupanTrosak().toFixed(2) + ' din'"></strong>
|
||||
Ukupno troškovi: <strong x-text="fmtDin(ukupanTrosak()) + ' din'"></strong>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
{{if .Napomena}}{{.Napomena}}{{else}}—{{end}}
|
||||
</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||
{{printf "%.2f" .Ukupno}} din
|
||||
{{dinari .Ukupno}} din
|
||||
</td>
|
||||
<td style="padding:12px 16px;text-align:center;">
|
||||
<div style="display:flex;align-items:center;justify-content:center;gap:8px;">
|
||||
@@ -87,7 +87,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);white-space:nowrap;">
|
||||
{{printf "%.2f" .Ukupno}} din
|
||||
{{dinari .Ukupno}} din
|
||||
</div>
|
||||
</div>
|
||||
{{if .Napomena}}
|
||||
|
||||
@@ -42,10 +42,10 @@
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:10px 12px;white-space:nowrap;">{{.Datum.Format "02.01.2006."}}</td>
|
||||
<td style="padding:10px 12px;">{{.ArtikalNaziv}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;color:var(--tekst-sporedni);">{{printf "%.2f" .StaraCena}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;font-weight:500;">{{printf "%.2f" .NovaCena}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;color:var(--tekst-sporedni);">{{dinari .StaraCena}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;font-weight:500;">{{dinari .NovaCena}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;white-space:nowrap;color:{{if .Poskupljenje}}var(--greska){{else}}var(--uspeh){{end}};">
|
||||
{{if .Poskupljenje}}+{{end}}{{printf "%.2f" .Razlika}}
|
||||
{{if .Poskupljenje}}+{{end}}{{dinari .Razlika}}
|
||||
<div style="font-size:11px;">{{if .Poskupljenje}}+{{end}}{{printf "%.1f" .Procenat}}%</div>
|
||||
</td>
|
||||
<td style="padding:10px 12px;">
|
||||
|
||||
@@ -48,12 +48,12 @@
|
||||
<td style="padding:10px 12px;white-space:nowrap;">{{.DatumPrometa.Format "02.01.2006."}}</td>
|
||||
<td style="padding:10px 12px;">{{.BrojDokumenta}}</td>
|
||||
<td style="padding:10px 12px;">{{.KupacNaziv}}{{if .KupacPib}}<div style="font-size:11px;color:var(--tekst-sporedni);">{{.OznakaPoreskogBroja}}: {{.KupacPib}}</div>{{end}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .PdvOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .PdvPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .OslobodenUkupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;font-weight:500;">{{printf "%.2f" .Ukupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .PdvOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .PdvPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .OslobodenUkupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;font-weight:500;">{{dinari .Ukupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;white-space:nowrap;">
|
||||
{{if eq .Izvor "rucno"}}
|
||||
<form method="POST" action="/pdv/kir/obrisi/{{.ID}}" style="display:inline;">
|
||||
@@ -72,12 +72,12 @@
|
||||
<tfoot>
|
||||
<tr style="border-top:0.5px solid var(--ivica);font-weight:500;background:var(--pozadina);">
|
||||
<td style="padding:10px 12px;" colspan="3">UKUPNO ({{len .Zapisi}})</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.PdvOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.PdvPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.OslobodenUkupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.Ukupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.PdvOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.PdvPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.OslobodenUkupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.Ukupno}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
||||
@@ -49,13 +49,13 @@
|
||||
<td style="padding:10px 12px;white-space:nowrap;">{{.DatumPrometa.Format "02.01.2006."}}</td>
|
||||
<td style="padding:10px 12px;">{{.BrojDokumenta}}{{if .Uvoz}}<div style="display:inline-block;margin-top:2px;font-size:10px;font-weight:600;color:var(--sb-akcent);border:0.5px solid var(--sb-akcent);border-radius:4px;padding:0 5px;">UVOZ</div>{{end}}</td>
|
||||
<td style="padding:10px 12px;">{{.DobavljacNaziv}}{{if .DobavljacPib}}<div style="font-size:11px;color:var(--tekst-sporedni);">{{.OznakaPoreskogBroja}}: {{.DobavljacPib}}</div>{{end}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .PdvOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .PdvPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .PdvBezOdbitka}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .OslobodenNabavka}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;font-weight:500;">{{printf "%.2f" .Ukupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .PdvOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .PdvPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .PdvBezOdbitka}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .OslobodenNabavka}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;font-weight:500;">{{dinari .Ukupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;white-space:nowrap;">
|
||||
{{if eq .Izvor "rucno"}}
|
||||
<form method="POST" action="/pdv/kpr/obrisi/{{.ID}}" style="display:inline;">
|
||||
@@ -74,13 +74,13 @@
|
||||
<tfoot>
|
||||
<tr style="border-top:0.5px solid var(--ivica);font-weight:500;background:var(--pozadina);">
|
||||
<td style="padding:10px 12px;" colspan="3">UKUPNO ({{len .Zapisi}})</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.PdvOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.PdvPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.PdvBezOdbitka}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.OslobodenNabavka}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Sume.Ukupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.PdvOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.PdvPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.PdvBezOdbitka}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.OslobodenNabavka}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Sume.Ukupno}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
||||
@@ -38,23 +38,23 @@
|
||||
</tr>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:10px 12px;padding-left:24px;">Opšta stopa (20%)</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .KirSume.OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Obracun.IzlazniPdvOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .KirSume.OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Obracun.IzlazniPdvOpsta}}</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:10px 12px;padding-left:24px;">Posebna stopa (10%)</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .KirSume.OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Obracun.IzlazniPdvPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .KirSume.OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Obracun.IzlazniPdvPosebna}}</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:10px 12px;padding-left:24px;color:var(--tekst-sporedni);">Oslobođen promet</td>
|
||||
<td style="padding:10px 12px;text-align:right;color:var(--tekst-sporedni);">{{printf "%.2f" .KirSume.OslobodenUkupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;color:var(--tekst-sporedni);">{{dinari .KirSume.OslobodenUkupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;color:var(--tekst-sporedni);">—</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);font-weight:500;background:var(--pozadina);">
|
||||
<td style="padding:10px 12px;">Ukupno izlazni PDV</td>
|
||||
<td style="padding:10px 12px;"></td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Obracun.IzlazniPdvUkupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Obracun.IzlazniPdvUkupno}}</td>
|
||||
</tr>
|
||||
|
||||
<!-- odbitni (prethodni) PDV — iz KPR -->
|
||||
@@ -63,23 +63,23 @@
|
||||
</tr>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:10px 12px;padding-left:24px;">Opšta stopa (20%)</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .KprSume.OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Obracun.OdbitniPdvOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .KprSume.OsnovicaOpsta}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Obracun.OdbitniPdvOpsta}}</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:10px 12px;padding-left:24px;">Posebna stopa (10%)</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .KprSume.OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Obracun.OdbitniPdvPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .KprSume.OsnovicaPosebna}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Obracun.OdbitniPdvPosebna}}</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:10px 12px;padding-left:24px;color:var(--tekst-sporedni);">PDV bez prava na odbitak</td>
|
||||
<td style="padding:10px 12px;text-align:right;color:var(--tekst-sporedni);">—</td>
|
||||
<td style="padding:10px 12px;text-align:right;color:var(--tekst-sporedni);">{{printf "%.2f" .KprSume.PdvBezOdbitka}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;color:var(--tekst-sporedni);">{{dinari .KprSume.PdvBezOdbitka}}</td>
|
||||
</tr>
|
||||
<tr style="font-weight:500;background:var(--pozadina);">
|
||||
<td style="padding:10px 12px;">Ukupno odbitni PDV</td>
|
||||
<td style="padding:10px 12px;"></td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .Obracun.OdbitniPdvUkupno}}</td>
|
||||
<td style="padding:10px 12px;text-align:right;">{{dinari .Obracun.OdbitniPdvUkupno}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -96,7 +96,7 @@
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:2px;">izlazni − odbitni PDV</div>
|
||||
</div>
|
||||
<div style="font-size:24px;font-weight:600;color:{{if .Obracun.ZaUplatu}}var(--greska){{else}}var(--uspeh){{end}};white-space:nowrap;">
|
||||
{{printf "%.2f" .Obracun.ObavezaApsolutna}} RSD
|
||||
{{dinari .Obracun.ObavezaApsolutna}} RSD
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<td style="padding: 12px 16px; font-size: 13px; font-family: monospace; color: var(--tekst-glavni)">{{.BrojNaloga}}</td>
|
||||
<td style="padding: 12px 16px; font-size: 13px; color: var(--tekst-sporedni); white-space: nowrap">{{.Datum.Format "02.01.2006."}}</td>
|
||||
<td style="padding: 12px 16px; font-size: 14px; font-weight: 500; color: var(--tekst-glavni)">{{if .KlijentNaziv}}{{.KlijentNaziv}}{{else}}—{{end}}</td>
|
||||
<td style="padding: 12px 16px; text-align: right; font-size: 14px; font-weight: 500; color: var(--tekst-glavni)">{{printf "%.2f" .Ukupno}} din</td>
|
||||
<td style="padding: 12px 16px; text-align: right; font-size: 14px; font-weight: 500; color: var(--tekst-glavni)">{{dinari .Ukupno}} din</td>
|
||||
<td style="padding: 12px 16px; text-align: center">
|
||||
<div style="display: flex; align-items: center; justify-content: center; gap: 8px">
|
||||
<a href="/prodaja/{{.ID}}" class="btn-primarno-malo">Detalji</a>
|
||||
@@ -74,7 +74,7 @@
|
||||
<div style="font-size: 14px; font-weight: 500; color: var(--tekst-glavni); margin-top: 2px">{{if .KlijentNaziv}}{{.KlijentNaziv}}{{else}}Bez klijenta{{end}}</div>
|
||||
<div style="font-size: 12px; color: var(--tekst-sporedni); margin-top: 2px">{{.Datum.Format "02.01.2006."}}</div>
|
||||
</div>
|
||||
<div style="font-size: 15px; font-weight: 500; color: var(--tekst-glavni); white-space: nowrap">{{printf "%.2f" .Ukupno}} din</div>
|
||||
<div style="font-size: 15px; font-weight: 500; color: var(--tekst-glavni); white-space: nowrap">{{dinari .Ukupno}} din</div>
|
||||
</div>
|
||||
<a href="/prodaja/{{.ID}}" class="btn-primarno-malo" style="justify-content: center; width: 100%; box-sizing: border-box">Detalji</a>
|
||||
</div>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
<div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Ukupno</div>
|
||||
<div style="font-size:20px;font-weight:600;color:var(--sb-akcent);">
|
||||
{{printf "%.2f" .Nalog.Ukupno}} din
|
||||
{{dinari .Nalog.Ukupno}} din
|
||||
</div>
|
||||
</div>
|
||||
{{if .Nalog.RazlogStorniranja}}
|
||||
@@ -205,18 +205,18 @@
|
||||
<tr style="border-bottom: 0.5px solid var(--ivica)">
|
||||
<td style="padding:10px 20px;font-size:14px;color:var(--tekst-glavni);">{{.ArtikalNaziv}}</td>
|
||||
<td style="padding:10px 20px;text-align:center;font-size:14px;color:var(--tekst-glavni);">{{.Kolicina}}</td>
|
||||
<td style="padding:10px 20px;text-align:right;font-size:14px;color:var(--tekst-sporedni);">{{printf "%.2f" .CenaPoKomadu}} din</td>
|
||||
<td style="padding:10px 20px;text-align:right;font-size:14px;color:var(--tekst-sporedni);">{{dinari .CenaPoKomadu}} din</td>
|
||||
<td style="padding:10px 20px;text-align:center;font-size:13px;color:var(--tekst-sporedni);">
|
||||
{{if .PdvStopa}}{{printf "%.0f" .PdvStopa}}%{{else}}—{{end}}
|
||||
</td>
|
||||
<td style="padding:10px 20px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.2f" .Ukupno}} din</td>
|
||||
<td style="padding:10px 20px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{dinari .Ukupno}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr style="border-top: 0.5px solid var(--ivica)">
|
||||
<td colspan="4" style="padding:12px 20px;text-align:right;font-size:13px;font-weight:500;color:var(--tekst-sporedni);">Ukupno:</td>
|
||||
<td style="padding:12px 20px;text-align:right;font-size:16px;font-weight:600;color:var(--sb-akcent);">{{printf "%.2f" .Nalog.Ukupno}} din</td>
|
||||
<td style="padding:12px 20px;text-align:right;font-size:16px;font-weight:600;color:var(--sb-akcent);">{{dinari .Nalog.Ukupno}} din</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
font-weight: 500;
|
||||
color: var(--tekst-glavni);
|
||||
">
|
||||
<span x-text="ukupnoStavke(stavka) + ' din'"></span>
|
||||
<span x-text="fmtDin(ukupnoStavke(stavka)) + ' din'"></span>
|
||||
</td>
|
||||
<td style="padding: 8px 10px; text-align: center">
|
||||
<button
|
||||
@@ -265,7 +265,7 @@
|
||||
font-weight: 600;
|
||||
color: var(--tekst-glavni);
|
||||
">
|
||||
<span x-text="ukupnoSvega() + ' din'"></span>
|
||||
<span x-text="fmtDin(ukupnoSvega()) + ' din'"></span>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
@@ -390,7 +390,7 @@
|
||||
font-weight: 500;
|
||||
color: var(--tekst-glavni);
|
||||
">
|
||||
Ukupno: <span x-text="ukupnoStavke(stavka) + ' din'"></span>
|
||||
Ukupno: <span x-text="fmtDin(ukupnoStavke(stavka)) + ' din'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -403,7 +403,7 @@
|
||||
color: var(--tekst-glavni);
|
||||
padding: 8px 4px;
|
||||
">
|
||||
Ukupno: <span x-text="ukupnoSvega() + ' din'"></span>
|
||||
Ukupno: <span x-text="fmtDin(ukupnoSvega()) + ' din'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
{{end}} {{if .Adresa}}
|
||||
<div class="firma-kontakt">{{.Adresa}}</div>
|
||||
{{end}} {{if .Telefon}}
|
||||
<div class="firma-kontakt">{{.Telefon}}</div>
|
||||
<div class="firma-kontakt">{{telefon .Telefon}}</div>
|
||||
{{end}} {{if .PIB}}
|
||||
<div class="firma-kontakt">PIB: {{.PIB}}</div>
|
||||
{{end}}
|
||||
@@ -82,15 +82,15 @@
|
||||
<tr>
|
||||
<td>{{.ArtikalNaziv}}</td>
|
||||
<td>{{.Kolicina}} {{.JedinicaMere}}</td>
|
||||
<td>{{printf "%.2f" .CenaPoKomadu}} din</td>
|
||||
<td>{{printf "%.2f" .Ukupno}} din</td>
|
||||
<td>{{dinari .CenaPoKomadu}} din</td>
|
||||
<td>{{dinari .Ukupno}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3" class="ukupno-label">Ukupno za naplatu:</td>
|
||||
<td class="ukupno-iznos">{{printf "%.2f" .Nalog.Ukupno}} din</td>
|
||||
<td class="ukupno-iznos">{{dinari .Nalog.Ukupno}} din</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
{{else}}
|
||||
<tr>
|
||||
<td colspan="6" style="padding:32px;text-align:center;font-size:14px;color:var(--tekst-sporedni);">
|
||||
Nema servisnih naloga. <a href="/servis/novi" style="color:var(--sb-akcent);">Dodaj prvi nalog.</a>
|
||||
{{if or $.Pretraga $.FilterStatus}}Za datu pretragu nema servisnih naloga u bazi.{{else}}Nema servisnih naloga. <a href="/servis/novi" style="color:var(--sb-akcent);">Dodaj prvi nalog.</a>{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
@@ -145,7 +145,7 @@
|
||||
</div>
|
||||
{{else}}
|
||||
<div style="padding:32px;text-align:center;font-size:14px;color:var(--tekst-sporedni);">
|
||||
Nema servisnih naloga. <a href="/servis/novi" style="color:var(--sb-akcent);">Dodaj prvi nalog.</a>
|
||||
{{if or $.Pretraga $.FilterStatus}}Za datu pretragu nema servisnih naloga u bazi.{{else}}Nema servisnih naloga. <a href="/servis/novi" style="color:var(--sb-akcent);">Dodaj prvi nalog.</a>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
@@ -188,7 +188,7 @@
|
||||
<div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Ugrađeni delovi</div>
|
||||
<div style="font-size:16px;font-weight:500;color:var(--tekst-glavni);">
|
||||
{{printf "%.2f" .UkupnoDelovi}} din
|
||||
{{dinari .UkupnoDelovi}} din
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -196,7 +196,7 @@
|
||||
<div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Ukupno</div>
|
||||
<div style="font-size:20px;font-weight:600;color:var(--sb-akcent);">
|
||||
{{printf "%.2f" .UkupnoSve}} din
|
||||
{{dinari .UkupnoSve}} din
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -210,7 +210,7 @@
|
||||
<div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Za naplatu</div>
|
||||
<div style="font-size:20px;font-weight:600;color:#16a34a;">
|
||||
{{printf "%.2f" .PreostaloSve}} din
|
||||
{{dinari .PreostaloSve}} din
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -269,8 +269,8 @@
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:9px 10px;font-size:14px;color:var(--tekst-glavni);">{{.ArtikalNaziv}}</td>
|
||||
<td style="padding:9px 10px;text-align:center;font-size:14px;color:var(--tekst-glavni);">{{.Kolicina}}</td>
|
||||
<td style="padding:9px 10px;text-align:right;font-size:14px;color:var(--tekst-glavni);">{{printf "%.2f" .CenaKomada}} din</td>
|
||||
<td style="padding:9px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.2f" .Ukupno}} din</td>
|
||||
<td style="padding:9px 10px;text-align:right;font-size:14px;color:var(--tekst-glavni);">{{dinari .CenaKomada}} din</td>
|
||||
<td style="padding:9px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{dinari .Ukupno}} din</td>
|
||||
<td style="padding:9px 10px;text-align:center;">
|
||||
{{if index $.Dozvole "servis.izmeni"}}
|
||||
<form method="POST" action="/servis/{{$.Nalog.ID}}/delovi/{{.ID}}/obrisi" style="display:inline;">
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
<div class="firma-info">
|
||||
{{if .Podnazlov}}{{.Podnazlov}}<br>{{end}}
|
||||
{{if .Adresa}}{{.Adresa}}<br>{{end}}
|
||||
{{if .Telefon}}Tel: {{.Telefon}}<br>{{end}}
|
||||
{{if .Telefon}}Tel: {{telefon .Telefon}}<br>{{end}}
|
||||
{{if .PIB}}PIB: {{.PIB}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -112,7 +112,7 @@
|
||||
<div class="strana-naziv">{{if .NazivFirme}}{{.NazivFirme}}{{else}}—{{end}}</div>
|
||||
<div class="strana-info">
|
||||
{{if .Adresa}}{{.Adresa}}<br>{{end}}
|
||||
{{if .Telefon}}{{.Telefon}}{{end}}
|
||||
{{if .Telefon}}{{telefon .Telefon}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="strana-kartica">
|
||||
@@ -121,7 +121,7 @@
|
||||
<div class="strana-naziv">{{.KlijentNaziv}}</div>
|
||||
<div class="strana-info">
|
||||
{{if .Klijent}}
|
||||
{{if .Klijent.Telefon}}Tel: {{.Klijent.Telefon}}<br>{{end}}
|
||||
{{if .Klijent.Telefon}}Tel: {{telefon .Klijent.Telefon}}<br>{{end}}
|
||||
{{if .Klijent.Email}}{{.Klijent.Email}}<br>{{end}}
|
||||
{{if .Klijent.Mesto}}{{.Klijent.Mesto}}{{end}}
|
||||
{{end}}
|
||||
@@ -183,13 +183,13 @@
|
||||
<tr>
|
||||
<td>{{.ArtikalNaziv}}</td>
|
||||
<td class="desno">{{.Kolicina}}</td>
|
||||
<td class="desno">{{printf "%.2f" .CenaKomada}} din</td>
|
||||
<td class="desno">{{printf "%.2f" .Ukupno}} din</td>
|
||||
<td class="desno">{{dinari .CenaKomada}} din</td>
|
||||
<td class="desno">{{dinari .Ukupno}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
<tr class="ukupno-red">
|
||||
<td colspan="3">Ukupno delovi</td>
|
||||
<td class="desno">{{printf "%.2f" .UkupnoDelovi}} din</td>
|
||||
<td class="desno">{{dinari .UkupnoDelovi}} din</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -209,7 +209,7 @@
|
||||
{{if gt .UkupnoDelovi 0.0}}
|
||||
<tr>
|
||||
<td>Ugrađeni delovi i materijal</td>
|
||||
<td class="desno">{{printf "%.2f" .UkupnoDelovi}} din</td>
|
||||
<td class="desno">{{dinari .UkupnoDelovi}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{if .ImaAvans}}
|
||||
@@ -222,7 +222,7 @@
|
||||
</table>
|
||||
<div class="naplata-blok">
|
||||
<div class="naplata-labela">Za naplatu pri preuzimanju:</div>
|
||||
<div class="naplata-iznos">{{printf "%.2f" .PreostaloSve}} din</div>
|
||||
<div class="naplata-iznos">{{dinari .PreostaloSve}} din</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<div class="firma-info">
|
||||
{{if .Podnazlov}}{{.Podnazlov}}<br>{{end}}
|
||||
{{if .Adresa}}{{.Adresa}}<br>{{end}}
|
||||
{{if .Telefon}}Tel: {{.Telefon}}<br>{{end}}
|
||||
{{if .Telefon}}Tel: {{telefon .Telefon}}<br>{{end}}
|
||||
{{if .PIB}}PIB: {{.PIB}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,7 +109,7 @@
|
||||
<div class="strana-naziv">{{if .NazivFirme}}{{.NazivFirme}}{{else}}—{{end}}</div>
|
||||
<div class="strana-info">
|
||||
{{if .Adresa}}{{.Adresa}}<br>{{end}}
|
||||
{{if .Telefon}}{{.Telefon}}{{end}}
|
||||
{{if .Telefon}}{{telefon .Telefon}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="strana-kartica">
|
||||
@@ -118,7 +118,7 @@
|
||||
<div class="strana-naziv">{{.KlijentNaziv}}</div>
|
||||
<div class="strana-info">
|
||||
{{if .Klijent}}
|
||||
{{if .Klijent.Telefon}}Tel: {{.Klijent.Telefon}}<br>{{end}}
|
||||
{{if .Klijent.Telefon}}Tel: {{telefon .Klijent.Telefon}}<br>{{end}}
|
||||
{{if .Klijent.Email}}{{.Klijent.Email}}<br>{{end}}
|
||||
{{if .Klijent.Mesto}}{{.Klijent.Mesto}}{{end}}
|
||||
{{end}}
|
||||
@@ -174,13 +174,13 @@
|
||||
<tr>
|
||||
<td>{{.ArtikalNaziv}}</td>
|
||||
<td class="desno">{{.Kolicina}}</td>
|
||||
<td class="desno">{{printf "%.2f" .CenaKomada}} din</td>
|
||||
<td class="desno">{{printf "%.2f" .Ukupno}} din</td>
|
||||
<td class="desno">{{dinari .CenaKomada}} din</td>
|
||||
<td class="desno">{{dinari .Ukupno}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
<tr class="ukupno-red">
|
||||
<td colspan="3">Ukupno delovi</td>
|
||||
<td class="desno">{{printf "%.2f" .UkupnoDelovi}} din</td>
|
||||
<td class="desno">{{dinari .UkupnoDelovi}} din</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -196,18 +196,18 @@
|
||||
{{if .CenaRaspon}}
|
||||
<tr>
|
||||
<td>Cena rada (procena)</td>
|
||||
<td class="desno" style="width:200px;">{{printf "%.2f" .CenaRadaOd}} – {{printf "%.2f" .CenaRadaDo}} din</td>
|
||||
<td class="desno" style="width:200px;">{{dinari .CenaRadaOd}} – {{dinari .CenaRadaDo}} din</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr>
|
||||
<td>Cena rada</td>
|
||||
<td class="desno" style="width:200px;">{{printf "%.2f" .CenaRada}} din</td>
|
||||
<td class="desno" style="width:200px;">{{dinari .CenaRada}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{if gt .UkupnoDelovi 0.0}}
|
||||
<tr>
|
||||
<td>Predloženi delovi i materijal</td>
|
||||
<td class="desno">{{printf "%.2f" .UkupnoDelovi}} din</td>
|
||||
<td class="desno">{{dinari .UkupnoDelovi}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
@@ -215,9 +215,9 @@
|
||||
<div class="naplata-blok">
|
||||
<div class="naplata-labela">Procena ukupnog troška:</div>
|
||||
{{if .CenaRaspon}}
|
||||
<div class="naplata-iznos">{{printf "%.2f" .UkupnoOd}} – {{printf "%.2f" .UkupnoDo}} din</div>
|
||||
<div class="naplata-iznos">{{dinari .UkupnoOd}} – {{dinari .UkupnoDo}} din</div>
|
||||
{{else}}
|
||||
<div class="naplata-iznos">{{printf "%.2f" .Ukupno}} din</div>
|
||||
<div class="naplata-iznos">{{dinari .Ukupno}} din</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<div class="firma-info">
|
||||
{{if .Podnazlov}}{{.Podnazlov}}<br>{{end}}
|
||||
{{if .Adresa}}{{.Adresa}}<br>{{end}}
|
||||
{{if .Telefon}}Tel: {{.Telefon}}<br>{{end}}
|
||||
{{if .Telefon}}Tel: {{telefon .Telefon}}<br>{{end}}
|
||||
{{if .PIB}}PIB: {{.PIB}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,13 +188,13 @@
|
||||
<tr>
|
||||
<td>{{.ArtikalNaziv}}</td>
|
||||
<td class="desno">{{.Kolicina}}</td>
|
||||
<td class="desno">{{printf "%.2f" .CenaKomada}} din</td>
|
||||
<td class="desno">{{printf "%.2f" .Ukupno}} din</td>
|
||||
<td class="desno">{{dinari .CenaKomada}} din</td>
|
||||
<td class="desno">{{dinari .Ukupno}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
<tr class="ukupno-red">
|
||||
<td colspan="3">Ukupno delovi</td>
|
||||
<td class="desno">{{printf "%.2f" .UkupnoDelovi}} din</td>
|
||||
<td class="desno">{{dinari .UkupnoDelovi}} din</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
{{if .Telefon}}
|
||||
<div class="kontakt">
|
||||
<div class="kontakt-naslov">Kontakt</div>
|
||||
<a href="tel:{{.Telefon}}" class="kontakt-tel">{{.Telefon}}</a>
|
||||
<a href="tel:{{.Telefon}}" class="kontakt-tel">{{telefon .Telefon}}</a>
|
||||
{{if .Adresa}}<div class="kontakt-adresa">{{.Adresa}}</div>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
<div style="text-align:right;">
|
||||
<div style="font-size:12px;color:var(--tekst-slabi);">Ukupna vrednost zalihe</div>
|
||||
<div style="font-size:18px;font-weight:600;color:var(--tekst-glavni);">{{printf "%.2f" .UkupnaVrednost}} din</div>
|
||||
<div style="font-size:18px;font-weight:600;color:var(--tekst-glavni);">{{dinari .UkupnaVrednost}} din</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,16 +58,16 @@
|
||||
<td style="font-size:12px;color:var(--tekst-slabi);">{{if .Kategorija}}{{.Kategorija}}{{else}}—{{end}}</td>
|
||||
<td style="text-align:right;font-weight:600;{{if le .Kolicina .KolicinMin}}color:#dc2626;{{end}}">{{.Kolicina}}</td>
|
||||
<td style="text-align:right;font-size:12px;color:var(--tekst-slabi);">{{.KolicinMin}}</td>
|
||||
<td style="text-align:right;font-family:monospace;font-size:12px;">{{printf "%.2f" .NabavnaCena}}</td>
|
||||
<td style="text-align:right;font-family:monospace;font-size:12px;">{{printf "%.2f" .ProdajnaCena}}</td>
|
||||
<td style="text-align:right;font-family:monospace;font-weight:500;">{{printf "%.2f" .VrednostZalihe}}</td>
|
||||
<td style="text-align:right;font-family:monospace;font-size:12px;">{{dinari .NabavnaCena}}</td>
|
||||
<td style="text-align:right;font-family:monospace;font-size:12px;">{{dinari .ProdajnaCena}}</td>
|
||||
<td style="text-align:right;font-family:monospace;font-weight:500;">{{dinari .VrednostZalihe}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr style="border-top:1.5px solid var(--ivica);font-weight:600;">
|
||||
<td colspan="7" style="padding:10px 12px;font-size:13px;">Ukupna vrednost zalihe</td>
|
||||
<td style="text-align:right;padding:10px 12px;font-family:monospace;">{{printf "%.2f" .UkupnaVrednost}} din</td>
|
||||
<td style="text-align:right;padding:10px 12px;font-family:monospace;">{{dinari .UkupnaVrednost}} din</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user