Bugfix: PDV obračun u prodaji, magacinski trag nabavke, fiskalizacija servisa, admin/podesavanja greške
This commit is contained in:
@@ -151,8 +151,8 @@ type NabavkaRepository interface {
|
|||||||
DohvatiID(ctx context.Context, id int64) (*model.Nabavka, error)
|
DohvatiID(ctx context.Context, id int64) (*model.Nabavka, error)
|
||||||
DohvatiStavke(ctx context.Context, nabavkaID int64) ([]model.StavkaSaArtiklom, error)
|
DohvatiStavke(ctx context.Context, nabavkaID int64) ([]model.StavkaSaArtiklom, error)
|
||||||
DohvatiTroskove(ctx context.Context, nabavkaID int64) ([]model.NabavkaTrosak, error)
|
DohvatiTroskove(ctx context.Context, nabavkaID int64) ([]model.NabavkaTrosak, error)
|
||||||
Kreiraj(ctx context.Context, n *model.Nabavka, stavke []model.StavkaNabavke, troskovi []model.NabavkaTrosak) (int64, error)
|
Kreiraj(ctx context.Context, n *model.Nabavka, stavke []model.StavkaNabavke, troskovi []model.NabavkaTrosak, korisnikID *int64) (int64, error)
|
||||||
Obrisi(ctx context.Context, id int64) error
|
Obrisi(ctx context.Context, id int64, korisnikID *int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// DobavljacRepository definiše operacije nad dobavljačima
|
// DobavljacRepository definiše operacije nad dobavljačima
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ func (r *NabavkaRepo) DohvatiTroskove(ctx context.Context, nabavkaID int64) ([]m
|
|||||||
|
|
||||||
// Kreiraj upisuje novu nabavku sa svim stavkama i zavisnim troškovima u jednoj
|
// Kreiraj upisuje novu nabavku sa svim stavkama i zavisnim troškovima u jednoj
|
||||||
// transakciji i ažurira stanje magacina
|
// transakciji i ažurira stanje magacina
|
||||||
func (r *NabavkaRepo) Kreiraj(ctx context.Context, n *model.Nabavka, stavke []model.StavkaNabavke, troskovi []model.NabavkaTrosak) (int64, error) {
|
func (r *NabavkaRepo) Kreiraj(ctx context.Context, n *model.Nabavka, stavke []model.StavkaNabavke, troskovi []model.NabavkaTrosak, korisnikID *int64) (int64, error) {
|
||||||
tx, err := r.db.BeginTx(ctx, nil)
|
tx, err := r.db.BeginTx(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: begin: %w", err)
|
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: begin: %w", err)
|
||||||
@@ -193,13 +193,27 @@ func (r *NabavkaRepo) Kreiraj(ctx context.Context, n *model.Nabavka, stavke []mo
|
|||||||
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: insert stavka: %w", err)
|
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: insert stavka: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stanjePre int
|
||||||
|
err = tx.QueryRowContext(ctx,
|
||||||
|
"SELECT kolicina FROM artikli WHERE id = ?", s.ArtikalID,
|
||||||
|
).Scan(&stanjePre)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: dohvati stanje: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stanjePosle := stanjePre + s.Kolicina
|
||||||
_, err = tx.ExecContext(ctx,
|
_, err = tx.ExecContext(ctx,
|
||||||
"UPDATE artikli SET kolicina = kolicina + ? WHERE id = ?",
|
"UPDATE artikli SET kolicina = ? WHERE id = ?",
|
||||||
s.Kolicina, s.ArtikalID,
|
stanjePosle, s.ArtikalID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: update kolicina: %w", err)
|
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: update kolicina: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = zabeleziMagacinPromenu(ctx, tx, s.ArtikalID, model.PromenaUlazNabavka,
|
||||||
|
s.Kolicina, stanjePre, stanjePosle, nabavkaID, korisnikID, ""); err != nil {
|
||||||
|
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: magacin: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
@@ -209,12 +223,64 @@ func (r *NabavkaRepo) Kreiraj(ctx context.Context, n *model.Nabavka, stavke []mo
|
|||||||
return nabavkaID, nil
|
return nabavkaID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obrisi briše nabavku po ID-u — stavke se brišu automatski (ON DELETE CASCADE)
|
// Obrisi briše nabavku po ID-u, vraća količine artikala na stanje i beleži korekciju u magacinski trag.
|
||||||
// Napomena: brisanje ne vraća količine artikala u magacin
|
func (r *NabavkaRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) error {
|
||||||
func (r *NabavkaRepo) Obrisi(ctx context.Context, id int64) error {
|
tx, err := r.db.BeginTx(ctx, nil)
|
||||||
_, err := r.db.ExecContext(ctx, "DELETE FROM nabavke WHERE id = ?", id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: %w", err)
|
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: begin: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// učitaj stavke pre brisanja (CASCADE ih briše zajedno sa nabavkom)
|
||||||
|
redovi, err := tx.QueryContext(ctx,
|
||||||
|
"SELECT artikal_id, kolicina FROM stavke_nabavke WHERE nabavka_id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: dohvati stavke: %w", err)
|
||||||
|
}
|
||||||
|
type stavka struct {
|
||||||
|
artikalID int64
|
||||||
|
kolicina int
|
||||||
|
}
|
||||||
|
var stavke []stavka
|
||||||
|
for redovi.Next() {
|
||||||
|
var s stavka
|
||||||
|
if err := redovi.Scan(&s.artikalID, &s.kolicina); err != nil {
|
||||||
|
redovi.Close()
|
||||||
|
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: scan stavka: %w", err)
|
||||||
|
}
|
||||||
|
stavke = append(stavke, s)
|
||||||
|
}
|
||||||
|
redovi.Close()
|
||||||
|
|
||||||
|
// vrati količine na stanje i zabeleži korekciju
|
||||||
|
for _, s := range stavke {
|
||||||
|
var stanjePre int
|
||||||
|
if err := tx.QueryRowContext(ctx,
|
||||||
|
"SELECT kolicina FROM artikli WHERE id = ?", s.artikalID,
|
||||||
|
).Scan(&stanjePre); err != nil {
|
||||||
|
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: dohvati stanje: %w", err)
|
||||||
|
}
|
||||||
|
stanjePosle := stanjePre - s.kolicina
|
||||||
|
if stanjePosle < 0 {
|
||||||
|
stanjePosle = 0
|
||||||
|
}
|
||||||
|
if _, err := tx.ExecContext(ctx,
|
||||||
|
"UPDATE artikli SET kolicina = ? WHERE id = ?", stanjePosle, s.artikalID,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: update stanje: %w", err)
|
||||||
|
}
|
||||||
|
if err := zabeleziMagacinPromenu(ctx, tx, s.artikalID, model.PromenaKorekcija,
|
||||||
|
-s.kolicina, stanjePre, stanjePosle, id, korisnikID, "brisanje nabavke"); err != nil {
|
||||||
|
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: magacin: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tx.ExecContext(ctx, "DELETE FROM nabavke WHERE id = ?", id); err != nil {
|
||||||
|
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: delete: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: commit: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,12 +207,12 @@ func (r *ProdajaRepo) Kreiraj(ctx context.Context, n *model.ProdajniNalog, stavk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PDV računamo iz cene ako nije eksplicitno postavljeno
|
// CenaPoKomadu je neto (bez PDV); PDV se dodaje naviše
|
||||||
cenaBezPdv := s.CenaBezPdv
|
cenaBezPdv := s.CenaBezPdv
|
||||||
pdvIznos := s.PdvIznos
|
pdvIznos := s.PdvIznos
|
||||||
if cenaBezPdv == 0 && s.PdvStopa > 0 {
|
if cenaBezPdv == 0 {
|
||||||
cenaBezPdv = s.CenaPoKomadu / (1 + s.PdvStopa/100)
|
cenaBezPdv = s.CenaPoKomadu
|
||||||
pdvIznos = s.CenaPoKomadu - cenaBezPdv
|
pdvIznos = cenaBezPdv * s.PdvStopa / 100
|
||||||
}
|
}
|
||||||
|
|
||||||
ukupnoStavke := float64(s.Kolicina) * s.CenaPoKomadu
|
ukupnoStavke := float64(s.Kolicina) * s.CenaPoKomadu
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"ntech/internal/auth"
|
"ntech/internal/auth"
|
||||||
@@ -549,7 +550,9 @@ func (h *Handler) AdminTotpDeaktivacija(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// isključenjem 2FA brišemo i rezervne kodove
|
// isključenjem 2FA brišemo i rezervne kodove
|
||||||
_ = h.RezervniKodoviRepo.Obrisi(r.Context(), k.ID)
|
if err := h.RezervniKodoviRepo.Obrisi(r.Context(), k.ID); err != nil {
|
||||||
|
slog.Warn("brisanje rezervnih kodova nije uspelo", "korisnik_id", k.ID, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/admin/profil?sacuvano=1", http.StatusSeeOther)
|
http.Redirect(w, r, "/admin/profil?sacuvano=1", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ func (h *Handler) SacuvajNabavku(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := h.NabavkeRepo.Kreiraj(r.Context(), &nabavka, stavke, troskovi)
|
id, err := h.NabavkeRepo.Kreiraj(r.Context(), &nabavka, stavke, troskovi, &k.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri čuvanju nabavke", http.StatusInternalServerError)
|
http.Error(w, "Greška pri čuvanju nabavke", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -340,7 +340,8 @@ func (h *Handler) DetaljiNabavke(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// ObrisiNabavku prima POST zahtev i briše nabavku po ID-u
|
// ObrisiNabavku prima POST zahtev i briše nabavku po ID-u
|
||||||
func (h *Handler) ObrisiNabavku(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ObrisiNabavku(w http.ResponseWriter, r *http.Request) {
|
||||||
if _, ok := h.zahtevajDozvolu(w, r, "nabavka.obrisi"); !ok {
|
k, ok := h.zahtevajDozvolu(w, r, "nabavka.obrisi")
|
||||||
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
@@ -349,7 +350,7 @@ func (h *Handler) ObrisiNabavku(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.NabavkeRepo.Obrisi(r.Context(), id); err != nil {
|
if err := h.NabavkeRepo.Obrisi(r.Context(), id, &k.ID); err != nil {
|
||||||
http.Error(w, "Greška pri brisanju nabavke", http.StatusInternalServerError)
|
http.Error(w, "Greška pri brisanju nabavke", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -944,20 +944,26 @@ func (h *Handler) TestFiskalizacije(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SSRF zaštita: dozvoljeni su samo localhost i privatne mreže (192.168.x.x,
|
// SSRF zaštita: dozvoljeni su samo localhost i privatne mreže (192.168.x.x,
|
||||||
// 10.x.x.x, 172.16-31.x.x, 127.x.x.x). Port izvlačimo kao integer da presečemo
|
// 10.x.x.x, 172.16-31.x.x, 127.x.x.x). URL rekonstruišemo kroz url.URL{}
|
||||||
// CodeQL taint lanac — broj ne može da sadrži URL manipulaciju.
|
// iz validiranih komponenti da CodeQL prepozna sanitizaciju.
|
||||||
parsedURL, err := url.Parse(pfrURL)
|
parsedURL, err := url.Parse(pfrURL)
|
||||||
if err != nil || !jePrivatnaAdresa(parsedURL.Hostname()) {
|
if err != nil || !jePrivatnaAdresa(parsedURL.Hostname()) {
|
||||||
http.Error(w, "Nevažeći PFR URL — dozvoljeni su samo lokalni/privatni hostovi (127.x, 192.168.x, 10.x, 172.16-31.x)", http.StatusBadRequest)
|
http.Error(w, "Nevažeći PFR URL — dozvoljeni su samo lokalni/privatni hostovi (127.x, 192.168.x, 10.x, 172.16-31.x)", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
host := parsedURL.Hostname()
|
||||||
port := 4566
|
port := 4566
|
||||||
if p := parsedURL.Port(); p != "" {
|
if p := parsedURL.Port(); p != "" {
|
||||||
if n, e := strconv.Atoi(p); e == nil && n > 0 && n <= 65535 {
|
if n, e := strconv.Atoi(p); e == nil && n > 0 && n <= 65535 {
|
||||||
port = n
|
port = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statusURL := fmt.Sprintf("http://%s:%d/api/status", parsedURL.Hostname(), port)
|
safeURL := url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: net.JoinHostPort(host, strconv.Itoa(port)),
|
||||||
|
Path: "/api/status",
|
||||||
|
}
|
||||||
|
statusURL := safeURL.String()
|
||||||
|
|
||||||
klijent := &http.Client{Timeout: 5 * time.Second}
|
klijent := &http.Client{Timeout: 5 * time.Second}
|
||||||
resp, err := klijent.Get(statusURL)
|
resp, err := klijent.Get(statusURL)
|
||||||
@@ -969,7 +975,11 @@ func (h *Handler) TestFiskalizacije(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
var status map[string]any
|
var status map[string]any
|
||||||
_ = json.NewDecoder(resp.Body).Decode(&status)
|
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
fmt.Fprintf(w, `<div class="fisk-status greska">✗ Neispravan odgovor servera — %s</div>`, html.EscapeString(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sdcDateTime, _ := status["sdcDateTime"].(string)
|
sdcDateTime, _ := status["sdcDateTime"].(string)
|
||||||
lastInvoice, _ := status["lastInvoiceNumber"].(string)
|
lastInvoice, _ := status["lastInvoiceNumber"].(string)
|
||||||
|
|||||||
+24
-11
@@ -2076,7 +2076,10 @@ func (h *Handler) PromeniStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = h.ServisRepo.SacuvajNaplatu(r.Context(), id, nacin, iznos)
|
if err := h.ServisRepo.SacuvajNaplatu(r.Context(), id, nacin, iznos); err != nil {
|
||||||
|
slog.Error("greška pri čuvanju naplate servisa", "id", id, "error", err)
|
||||||
|
middleware.SetFlash(w, r, h.DB, "greska", "Naplata nije sačuvana. Pokušajte ponovo.")
|
||||||
|
}
|
||||||
|
|
||||||
// fiskalizacija servisa — ako je modul uključen
|
// fiskalizacija servisa — ako je modul uključen
|
||||||
if h.modulUkljucen(r.Context(), config.ModulFiskalizacija) {
|
if h.modulUkljucen(r.Context(), config.ModulFiskalizacija) {
|
||||||
@@ -2145,8 +2148,16 @@ func (h *Handler) fiskalizujServis(ctx context.Context, servisID int64, klijent
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
radovi, _ := h.ServisniRadoviRepo.DohvatiZaNalog(ctx, servisID)
|
radovi, err := h.ServisniRadoviRepo.DohvatiZaNalog(ctx, servisID)
|
||||||
delovi, _ := h.deloviSaPotrazivanima(ctx, servisID)
|
if err != nil {
|
||||||
|
slog.Error("fiskalizujServis: greška pri dohvatanju radova", "id", servisID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delovi, err := h.deloviSaPotrazivanima(ctx, servisID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("fiskalizujServis: greška pri dohvatanju delova", "id", servisID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// napravi stavke za fiskalni račun
|
// napravi stavke za fiskalni račun
|
||||||
items := make([]fiskal.InvoiceItem, 0)
|
items := make([]fiskal.InvoiceItem, 0)
|
||||||
@@ -2154,12 +2165,11 @@ func (h *Handler) fiskalizujServis(ctx context.Context, servisID int64, klijent
|
|||||||
if r.Predlozeno {
|
if r.Predlozeno {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stopa := 20.0 // podrazumevano — servisne usluge su 20% PDV
|
|
||||||
items = append(items, fiskal.InvoiceItem{
|
items = append(items, fiskal.InvoiceItem{
|
||||||
Name: r.Naziv,
|
Name: r.Naziv,
|
||||||
Labels: []string{fiskal.OznakaPDV(stopa)},
|
Labels: []string{fiskal.OznakaPDV(r.PdvStopa)},
|
||||||
TotalAmount: fiskal.BrutoCena(r.Ukupno(), stopa),
|
TotalAmount: fiskal.BrutoCena(r.Ukupno(), r.PdvStopa),
|
||||||
UnitPrice: fiskal.BrutoCena(r.CenaKomada, stopa),
|
UnitPrice: fiskal.BrutoCena(r.CenaKomada, r.PdvStopa),
|
||||||
Quantity: r.Kolicina,
|
Quantity: r.Kolicina,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2167,15 +2177,18 @@ func (h *Handler) fiskalizujServis(ctx context.Context, servisID int64, klijent
|
|||||||
if d.Predlozeno {
|
if d.Predlozeno {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stopa := 20.0
|
|
||||||
items = append(items, fiskal.InvoiceItem{
|
items = append(items, fiskal.InvoiceItem{
|
||||||
Name: d.ArtikalNaziv,
|
Name: d.ArtikalNaziv,
|
||||||
Labels: []string{fiskal.OznakaPDV(stopa)},
|
Labels: []string{fiskal.OznakaPDV(d.PdvStopa)},
|
||||||
TotalAmount: fiskal.BrutoCena(d.Ukupno(), stopa),
|
TotalAmount: fiskal.BrutoCena(d.Ukupno(), d.PdvStopa),
|
||||||
UnitPrice: fiskal.BrutoCena(d.CenaKomada, stopa),
|
UnitPrice: fiskal.BrutoCena(d.CenaKomada, d.PdvStopa),
|
||||||
Quantity: float64(d.Kolicina),
|
Quantity: float64(d.Kolicina),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if len(items) == 0 {
|
||||||
|
slog.Warn("fiskalizujServis: nema stavki za fiskalni račun, zahtev odbačen", "id", servisID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
pib := ""
|
pib := ""
|
||||||
if nalog.KlijentID != nil {
|
if nalog.KlijentID != nil {
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ func (k *PdvKir) DodajNeto(osnovica, stopa float64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// KirIzProdaje gradi KIR zapis iz prodaje: stavke se grupišu po PDV stopi
|
// KirIzProdaje gradi KIR zapis iz prodaje: stavke se grupišu po PDV stopi
|
||||||
// (20→opšta, 10→posebna, ostalo→oslobođeno). CenaPoKomadu je prodajna cena SA PDV,
|
// (20→opšta, 10→posebna, ostalo→oslobođeno). CenaPoKomadu je prodajna cena BEZ PDV
|
||||||
// pa se osnovica izvodi deljenjem sa (1 + stopa/100).
|
// (neto), PDV se dodaje naviše — isti obrazac kao kod servisnih radova/delova.
|
||||||
func KirIzProdaje(nalog ProdajniNalog, stavke []StavkaProdaje, kupacNaziv, kupacPib, kupacMesto string) PdvKir {
|
func KirIzProdaje(nalog ProdajniNalog, stavke []StavkaProdaje, kupacNaziv, kupacPib, kupacMesto string) PdvKir {
|
||||||
id := nalog.ID
|
id := nalog.ID
|
||||||
k := PdvKir{
|
k := PdvKir{
|
||||||
@@ -120,12 +120,7 @@ func KirIzProdaje(nalog ProdajniNalog, stavke []StavkaProdaje, kupacNaziv, kupac
|
|||||||
IzvorID: &id,
|
IzvorID: &id,
|
||||||
}
|
}
|
||||||
for _, s := range stavke {
|
for _, s := range stavke {
|
||||||
ukupnoLinija := float64(s.Kolicina) * s.CenaPoKomadu
|
k.DodajNeto(float64(s.Kolicina)*s.CenaPoKomadu, s.PdvStopa)
|
||||||
osnovica := ukupnoLinija
|
|
||||||
if s.PdvStopa > 0 {
|
|
||||||
osnovica = ukupnoLinija / (1 + s.PdvStopa/100)
|
|
||||||
}
|
|
||||||
k.dodajStavku(osnovica, ukupnoLinija-osnovica, s.PdvStopa)
|
|
||||||
}
|
}
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,9 +79,13 @@
|
|||||||
<th style="padding:12px 16px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-jak);">Naziv</th>
|
<th style="padding:12px 16px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-jak);">Naziv</th>
|
||||||
<th style="padding:12px 16px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-jak);">Kategorija</th>
|
<th style="padding:12px 16px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-jak);">Kategorija</th>
|
||||||
<th style="padding:12px 16px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-jak);">Količina</th>
|
<th style="padding:12px 16px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-jak);">Količina</th>
|
||||||
|
{{if index .Moduli "pdv"}}
|
||||||
<th style="padding:12px 16px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-jak);cursor:pointer;white-space:nowrap;" id="cena-zaglavlje" onclick="toggleCenaPdv()">
|
<th style="padding:12px 16px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-jak);cursor:pointer;white-space:nowrap;" id="cena-zaglavlje" onclick="toggleCenaPdv()">
|
||||||
Cena <span id="cena-toggle" style="font-size:10px;color:var(--sb-akcent);">▾ bez PDV</span>
|
Cena <span id="cena-toggle" style="font-size:10px;color:var(--sb-akcent);">▾ bez PDV</span>
|
||||||
</th>
|
</th>
|
||||||
|
{{else}}
|
||||||
|
<th style="padding:12px 16px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-jak);">Cena</th>
|
||||||
|
{{end}}
|
||||||
<th style="padding:12px 16px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-jak);">Lokacija</th>
|
<th style="padding:12px 16px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-jak);">Lokacija</th>
|
||||||
<th style="padding:12px 16px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-jak);">Akcije</th>
|
<th style="padding:12px 16px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-jak);">Akcije</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -104,7 +108,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</td>
|
</td>
|
||||||
<td style="padding:12px 16px;text-align:right;font-size:14px;color:var(--tekst-glavni);">
|
<td style="padding:12px 16px;text-align:right;font-size:14px;color:var(--tekst-glavni);">
|
||||||
<span class="cena-bez-pdv">{{dinari .ProdajnaCena}}</span><span class="cena-sa-pdv" style="display:none">{{dinari .CenaSaPdv}}</span> din
|
<span class="cena-bez-pdv">{{dinari .ProdajnaCena}}</span>{{if index $.Moduli "pdv"}}<span class="cena-sa-pdv" style="display:none">{{dinari .CenaSaPdv}}</span>{{end}} din
|
||||||
</td>
|
</td>
|
||||||
<td style="padding:12px 16px;font-size:13px;color:var(--tekst-sporedni);">
|
<td style="padding:12px 16px;font-size:13px;color:var(--tekst-sporedni);">
|
||||||
{{if .Lokacija}}{{.Lokacija}}{{else}}—{{end}}
|
{{if .Lokacija}}{{.Lokacija}}{{else}}—{{end}}
|
||||||
@@ -259,6 +263,7 @@ document.body.addEventListener('htmx:afterSwap', function (e) {
|
|||||||
rez.classList.remove('bez-anim');
|
rez.classList.remove('bez-anim');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
{{if index .Moduli "pdv"}}
|
||||||
// toggle cena: bez PDV ↔ sa PDV
|
// toggle cena: bez PDV ↔ sa PDV
|
||||||
var cenaPdvRezim = false; // false = bez PDV
|
var cenaPdvRezim = false; // false = bez PDV
|
||||||
function toggleCenaPdv() {
|
function toggleCenaPdv() {
|
||||||
@@ -270,6 +275,7 @@ function toggleCenaPdv() {
|
|||||||
for (var j = 0; j < sa.length; j++) { sa[j].style.display = cenaPdvRezim ? '' : 'none'; }
|
for (var j = 0; j < sa.length; j++) { sa[j].style.display = cenaPdvRezim ? '' : 'none'; }
|
||||||
if (tgl) tgl.textContent = cenaPdvRezim ? '▾ sa PDV' : '▾ bez PDV';
|
if (tgl) tgl.textContent = cenaPdvRezim ? '▾ sa PDV' : '▾ bez PDV';
|
||||||
}
|
}
|
||||||
|
{{end}}
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user