Merge grane feature/greske-pravni-status: PDV profil i šifarnik stopa
This commit is contained in:
@@ -58,6 +58,9 @@ type PdvStopaRepository interface {
|
|||||||
Kreiraj(ctx context.Context, s *model.PdvStopa) (int64, error)
|
Kreiraj(ctx context.Context, s *model.PdvStopa) (int64, error)
|
||||||
Izmeni(ctx context.Context, s *model.PdvStopa) error
|
Izmeni(ctx context.Context, s *model.PdvStopa) error
|
||||||
PostaviAktivnu(ctx context.Context, id int64, aktivna bool) error
|
PostaviAktivnu(ctx context.Context, id int64, aktivna bool) error
|
||||||
|
// PodrazumevanaOpsta vraća aktivnu opštu stopu iz šifarnika (prva po redosledu),
|
||||||
|
// ili nil ako u šifarniku nema nijedne aktivne opšte stope.
|
||||||
|
PodrazumevanaOpsta(ctx context.Context) (*model.PdvStopa, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PdvKirRepository definiše operacije nad knjigom izdatih računa (KIR)
|
// PdvKirRepository definiše operacije nad knjigom izdatih računa (KIR)
|
||||||
|
|||||||
@@ -78,6 +78,28 @@ func (r *PdvStopaRepo) Kreiraj(ctx context.Context, s *model.PdvStopa) (int64, e
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PodrazumevanaOpsta vraća aktivnu opštu stopu iz šifarnika (prva po redosledu),
|
||||||
|
// ili nil ako u šifarniku nema nijedne aktivne opšte stope — poziocalac tada mora
|
||||||
|
// sam odlučiti o fallback vrednosti (šifarnik nikad ne sme biti prazan u praksi,
|
||||||
|
// ali ne oslanjamo se na to).
|
||||||
|
func (r *PdvStopaRepo) PodrazumevanaOpsta(ctx context.Context) (*model.PdvStopa, error) {
|
||||||
|
var s model.PdvStopa
|
||||||
|
err := r.db.QueryRowContext(ctx, `
|
||||||
|
SELECT id, naziv, stopa, oznaka, aktivna, redosled, datum_unosa
|
||||||
|
FROM pdv_stope
|
||||||
|
WHERE oznaka = 'opsta' AND aktivna = 1
|
||||||
|
ORDER BY redosled ASC, stopa DESC
|
||||||
|
LIMIT 1`).
|
||||||
|
Scan(&s.ID, &s.Naziv, &s.Stopa, &s.Oznaka, &s.Aktivna, &s.Redosled, &s.DatumUnosa)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ntech: PdvStopaRepo.PodrazumevanaOpsta: %w", err)
|
||||||
|
}
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Izmeni menja podatke postojeće stope (osim datuma unosa)
|
// Izmeni menja podatke postojeće stope (osim datuma unosa)
|
||||||
func (r *PdvStopaRepo) Izmeni(ctx context.Context, s *model.PdvStopa) error {
|
func (r *PdvStopaRepo) Izmeni(ctx context.Context, s *model.PdvStopa) error {
|
||||||
_, err := r.db.ExecContext(ctx, `
|
_, err := r.db.ExecContext(ctx, `
|
||||||
|
|||||||
@@ -20,9 +20,14 @@ func NoviServisniRadoviRepo(baza *sql.DB) *ServisniRadoviRepo {
|
|||||||
|
|
||||||
// DohvatiZaNalog vraća sve radove (usluge) jednog naloga, redom unosa
|
// DohvatiZaNalog vraća sve radove (usluge) jednog naloga, redom unosa
|
||||||
func (r *ServisniRadoviRepo) DohvatiZaNalog(ctx context.Context, nalogID int64) ([]model.ServisniRad, error) {
|
func (r *ServisniRadoviRepo) DohvatiZaNalog(ctx context.Context, nalogID int64) ([]model.ServisniRad, error) {
|
||||||
|
// COALESCE fallback je za „ad-hoc" rad bez veze na cenovnik usluga
|
||||||
|
// (sr.usluga_id NULL) — tad se PDV stopa uzima iz šifarnika (Podešavanja →
|
||||||
|
// Kalkulacija i PDV), ne hardkodovana vrednost. 20.0 ostaje samo kao
|
||||||
|
// apsolutno poslednje utočište ako šifarnik nema nijednu aktivnu opštu stopu.
|
||||||
redovi, err := r.db.QueryContext(ctx, `
|
redovi, err := r.db.QueryContext(ctx, `
|
||||||
SELECT sr.id, sr.nalog_id, COALESCE(sr.usluga_id, 0), sr.naziv, sr.kolicina, sr.cena_komada, sr.datum, sr.predlozeno,
|
SELECT sr.id, sr.nalog_id, COALESCE(sr.usluga_id, 0), sr.naziv, sr.kolicina, sr.cena_komada, sr.datum, sr.predlozeno,
|
||||||
COALESCE(u.sifra, ''), COALESCE(u.pdv_stopa, 20.0)
|
COALESCE(u.sifra, ''),
|
||||||
|
COALESCE(u.pdv_stopa, (SELECT stopa FROM pdv_stope WHERE oznaka = 'opsta' AND aktivna = 1 ORDER BY redosled ASC, stopa DESC LIMIT 1), 20.0)
|
||||||
FROM servis_radovi sr
|
FROM servis_radovi sr
|
||||||
LEFT JOIN usluge u ON u.id = sr.usluga_id
|
LEFT JOIN usluge u ON u.id = sr.usluga_id
|
||||||
WHERE sr.nalog_id = ? ORDER BY sr.id`, nalogID)
|
WHERE sr.nalog_id = ? ORDER BY sr.id`, nalogID)
|
||||||
|
|||||||
@@ -64,10 +64,11 @@ func NapraviZahtev(
|
|||||||
ukupanIznos := 0.0
|
ukupanIznos := 0.0
|
||||||
|
|
||||||
for _, s := range stavke {
|
for _, s := range stavke {
|
||||||
stopa := 20.0 // podrazumevano 20% ako stopa nije eksplicitno postavljena
|
// stopa je uvek ono što je snimljeno na stavci — 0 je legitimna vrednost
|
||||||
if s.PdvStopa > 0 {
|
// (van sistema PDV/neobveznik), ne „nije postavljeno". Nikad je ne prepisivati
|
||||||
stopa = s.PdvStopa
|
// nazad na opštu stopu, jer bi to poništilo nuliranje PDV-a za ne-PDV obveznike
|
||||||
}
|
// (v. docs/Greške.md).
|
||||||
|
stopa := s.PdvStopa
|
||||||
// CenaBezPdv iz baze je već diskontovana (popust primenjen pri kreiranju naloga);
|
// CenaBezPdv iz baze je već diskontovana (popust primenjen pri kreiranju naloga);
|
||||||
// popust se ponovo primenjuje samo u fallback grani kad CenaBezPdv nije popunjena.
|
// popust se ponovo primenjuje samo u fallback grani kad CenaBezPdv nije popunjena.
|
||||||
netoCena := s.CenaBezPdv
|
netoCena := s.CenaBezPdv
|
||||||
@@ -147,16 +148,12 @@ func NapraviRefundZahtev(
|
|||||||
// mora biti dosledan, jer ga PFR ne povezuje po ID-u već po sadržaju stavke).
|
// mora biti dosledan, jer ga PFR ne povezuje po ID-u već po sadržaju stavke).
|
||||||
const NazivAvansneStavke = "Аванс"
|
const NazivAvansneStavke = "Аванс"
|
||||||
|
|
||||||
// StopaAvansa je pretpostavljena PDV stopa za avans — u trenutku uplate avansa
|
|
||||||
// stavke (radovi/delovi) obično još nisu poznate, pa se ne može tačno odrediti
|
|
||||||
// stvarna stopa. Koristi se opšta stopa (20%) kao razumna pretpostavka za
|
|
||||||
// srpsko tržište; ako firma posluje isključivo po posebnoj/nultoj stopi, ovo
|
|
||||||
// treba prilagoditi ručno (nema opšteg rešenja bez poznavanja stavki unapred).
|
|
||||||
const StopaAvansa = 20.0
|
|
||||||
|
|
||||||
// NapraviAvansZahtev gradi Advance/Sale zahtev za primljeni avans — jedna stavka
|
// NapraviAvansZahtev gradi Advance/Sale zahtev za primljeni avans — jedna stavka
|
||||||
// "Аванс" u punom (bruto) iznosu.
|
// "Аванс" u punom (bruto) iznosu. stopa je PDV stopa avansa — u trenutku uplate
|
||||||
func NapraviAvansZahtev(iznos float64, nacinPlacanja, kasir string) InvoiceRequest {
|
// stavke (radovi/delovi) obično još nisu poznate, pa pozivalac prosleđuje opštu
|
||||||
|
// stopu iz šifarnika (Podešavanja → Kalkulacija i PDV) kad je firma PDV obveznik,
|
||||||
|
// ili 0 kad nije — nikad hardkodovanu vrednost (v. docs/Greške.md §4.1).
|
||||||
|
func NapraviAvansZahtev(iznos, stopa float64, nacinPlacanja, kasir string) InvoiceRequest {
|
||||||
return InvoiceRequest{
|
return InvoiceRequest{
|
||||||
InvoiceRequest: InvoiceRequestBody{
|
InvoiceRequest: InvoiceRequestBody{
|
||||||
InvoiceType: "Advance",
|
InvoiceType: "Advance",
|
||||||
@@ -165,7 +162,7 @@ func NapraviAvansZahtev(iznos float64, nacinPlacanja, kasir string) InvoiceReque
|
|||||||
{Amount: iznos, PaymentType: TipPlacanja(nacinPlacanja)},
|
{Amount: iznos, PaymentType: TipPlacanja(nacinPlacanja)},
|
||||||
},
|
},
|
||||||
Items: []InvoiceItem{
|
Items: []InvoiceItem{
|
||||||
{Name: NazivAvansneStavke, Labels: []string{OznakaPDV(StopaAvansa)}, TotalAmount: iznos, UnitPrice: iznos, Quantity: 1},
|
{Name: NazivAvansneStavke, Labels: []string{OznakaPDV(stopa)}, TotalAmount: iznos, UnitPrice: iznos, Quantity: 1},
|
||||||
},
|
},
|
||||||
Cashier: kasir,
|
Cashier: kasir,
|
||||||
},
|
},
|
||||||
@@ -175,15 +172,18 @@ func NapraviAvansZahtev(iznos float64, nacinPlacanja, kasir string) InvoiceReque
|
|||||||
// NapraviAvansRefundZahtev gradi Advance/Refund zahtev za povraćaj dela ili celog
|
// NapraviAvansRefundZahtev gradi Advance/Refund zahtev za povraćaj dela ili celog
|
||||||
// avansa (npr. kad avans premaši konačnu cenu popravke). referentBroj je PfrBroj
|
// avansa (npr. kad avans premaši konačnu cenu popravke). referentBroj je PfrBroj
|
||||||
// originalnog avansnog računa.
|
// originalnog avansnog računa.
|
||||||
func NapraviAvansRefundZahtev(iznos float64, nacinPlacanja, kasir, referentBroj string) InvoiceRequest {
|
func NapraviAvansRefundZahtev(iznos, stopa float64, nacinPlacanja, kasir, referentBroj string) InvoiceRequest {
|
||||||
zahtev := NapraviAvansZahtev(iznos, nacinPlacanja, kasir)
|
zahtev := NapraviAvansZahtev(iznos, stopa, nacinPlacanja, kasir)
|
||||||
zahtev.InvoiceRequest.TransactionType = "Refund"
|
zahtev.InvoiceRequest.TransactionType = "Refund"
|
||||||
zahtev.InvoiceRequest.ReferentDocumentNumber = referentBroj
|
zahtev.InvoiceRequest.ReferentDocumentNumber = referentBroj
|
||||||
return zahtev
|
return zahtev
|
||||||
}
|
}
|
||||||
|
|
||||||
// PorezIzBrutoAvansa izvlači poreski deo iz bruto iznosa avansa po StopaAvansa
|
// PorezIzBrutoAvansa izvlači poreski deo iz bruto iznosa avansa po datoj stopi
|
||||||
// (npr. 500 din avansa po 20% → porez ≈ 83.33 din).
|
// (npr. 500 din avansa po 20% → porez ≈ 83.33 din). stopa=0 (ne-PDV obveznik) → 0.
|
||||||
func PorezIzBrutoAvansa(bruto float64) float64 {
|
func PorezIzBrutoAvansa(bruto, stopa float64) float64 {
|
||||||
return math.Round(bruto*StopaAvansa/(100+StopaAvansa)*100) / 100
|
if stopa <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return math.Round(bruto*stopa/(100+stopa)*100) / 100
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,35 @@ func (h *Handler) modulUkljucen(ctx context.Context, modul string) bool {
|
|||||||
return config.ModulUkljucen(podesavanja, modul)
|
return config.ModulUkljucen(podesavanja, modul)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// podrazumevanaPdvStopa vraća opštu PDV stopu iz šifarnika (Podešavanja → Kalkulacija
|
||||||
|
// i PDV → Šifarnik PDV stopa) — jedini izvor „koja je opšta stopa" u programu.
|
||||||
|
// 20.0 se koristi samo kao krajnji fallback ako šifarnik nema nijednu aktivnu
|
||||||
|
// opštu stopu (npr. korisnik ih sve arhivirao) — ne kao podrazumevana poslovna
|
||||||
|
// pretpostavka, već da se izbegne prekid rada.
|
||||||
|
func (h *Handler) podrazumevanaPdvStopa(ctx context.Context) float64 {
|
||||||
|
stopa, err := h.PdvStopeRepo.PodrazumevanaOpsta(ctx)
|
||||||
|
if err != nil || stopa == nil {
|
||||||
|
return 20.0
|
||||||
|
}
|
||||||
|
return stopa.Stopa
|
||||||
|
}
|
||||||
|
|
||||||
|
// validnePdvStope vraća skup PDV stopa koje korisnik sme da izabere na stavci —
|
||||||
|
// aktivne stope iz šifarnika (Podešavanja → Kalkulacija i PDV), plus 0 koje je
|
||||||
|
// uvek dozvoljeno (van sistema PDV/oslobođeno). Zamenjuje hardkodovanu proveru
|
||||||
|
// „0, 10 ili 20" — šifarnik je jedini izvor važećih stopa.
|
||||||
|
func (h *Handler) validnePdvStope(ctx context.Context) map[float64]bool {
|
||||||
|
validne := map[float64]bool{0: true}
|
||||||
|
stope, err := h.PdvStopeRepo.Lista(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return validne
|
||||||
|
}
|
||||||
|
for _, s := range stope {
|
||||||
|
validne[s.Stopa] = true
|
||||||
|
}
|
||||||
|
return validne
|
||||||
|
}
|
||||||
|
|
||||||
// zahtevajDozvolu vraća prijavljenog korisnika ako njegova uloga sme da izvrši akciju.
|
// zahtevajDozvolu vraća prijavljenog korisnika ako njegova uloga sme da izvrši akciju.
|
||||||
// U suprotnom šalje 403 sa srpskom porukom i vraća ok=false (handler tada return-uje).
|
// U suprotnom šalje 403 sa srpskom porukom i vraća ok=false (handler tada return-uje).
|
||||||
func (h *Handler) zahtevajDozvolu(w http.ResponseWriter, r *http.Request, akcija string) (*model.Korisnik, bool) {
|
func (h *Handler) zahtevajDozvolu(w http.ResponseWriter, r *http.Request, akcija string) (*model.Korisnik, bool) {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func (h *Handler) NoviArtikal(w http.ResponseWriter, r *http.Request) {
|
|||||||
PodaciStranice: ps,
|
PodaciStranice: ps,
|
||||||
Kategorije: kategorije,
|
Kategorije: kategorije,
|
||||||
Dobavljaci: dobavljaci,
|
Dobavljaci: dobavljaci,
|
||||||
Artikal: model.Artikal{Sifra: predlogSifre, Tip: tip, JedinicaMere: "kom", PdvStopa: 20.0},
|
Artikal: model.Artikal{Sifra: predlogSifre, Tip: tip, JedinicaMere: "kom", PdvStopa: h.podrazumevanaPdvStopa(r.Context())},
|
||||||
Izmena: false,
|
Izmena: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ func (h *Handler) SacuvajArtikal(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
artikal, greska := parseFormuArtikla(r)
|
artikal, greska := parseFormuArtikla(r, h.podrazumevanaPdvStopa(r.Context()))
|
||||||
if greska != "" {
|
if greska != "" {
|
||||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
kategorije, _ := h.KategorijeRepo.Lista(r.Context())
|
kategorije, _ := h.KategorijeRepo.Lista(r.Context())
|
||||||
@@ -210,7 +210,7 @@ func (h *Handler) SacuvajIzmenuArtikla(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
artikal, greska := parseFormuArtikla(r)
|
artikal, greska := parseFormuArtikla(r, h.podrazumevanaPdvStopa(r.Context()))
|
||||||
if greska != "" {
|
if greska != "" {
|
||||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
kategorije, _ := h.KategorijeRepo.Lista(r.Context())
|
kategorije, _ := h.KategorijeRepo.Lista(r.Context())
|
||||||
@@ -325,7 +325,9 @@ func citajDobavljaceForme(r *http.Request) []int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseFormuArtikla čita polja iz forme i vraća artikal i eventualnu grešku
|
// parseFormuArtikla čita polja iz forme i vraća artikal i eventualnu grešku
|
||||||
func parseFormuArtikla(r *http.Request) (model.Artikal, string) {
|
// podrazumevanaStopa je opšta PDV stopa iz šifarnika (v. Handler.podrazumevanaPdvStopa) —
|
||||||
|
// koristi se samo kad korisnik nije uneo stopu, nikad hardkodovana vrednost.
|
||||||
|
func parseFormuArtikla(r *http.Request, podrazumevanaStopa float64) (model.Artikal, string) {
|
||||||
naziv := r.FormValue("naziv")
|
naziv := r.FormValue("naziv")
|
||||||
if naziv == "" {
|
if naziv == "" {
|
||||||
return model.Artikal{}, "Naziv artikla je obavezan."
|
return model.Artikal{}, "Naziv artikla je obavezan."
|
||||||
@@ -389,8 +391,8 @@ func parseFormuArtikla(r *http.Request) (model.Artikal, string) {
|
|||||||
artikal.NabavnaCena = v
|
artikal.NabavnaCena = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// PDV stopa — podrazumevano 20%
|
// PDV stopa — podrazumevano opšta stopa iz šifarnika
|
||||||
pdvStopa := 20.0
|
pdvStopa := podrazumevanaStopa
|
||||||
if p := r.FormValue("pdv_stopa"); p != "" {
|
if p := r.FormValue("pdv_stopa"); p != "" {
|
||||||
if v, err := strconv.ParseFloat(p, 64); err == nil && v >= 0 {
|
if v, err := strconv.ParseFloat(p, 64); err == nil && v >= 0 {
|
||||||
pdvStopa = v
|
pdvStopa = v
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func TestParseFormuArtikla(t *testing.T) {
|
|||||||
t.Run("validan", func(t *testing.T) {
|
t.Run("validan", func(t *testing.T) {
|
||||||
a, greska := parseFormuArtikla(formZahtev(url.Values{
|
a, greska := parseFormuArtikla(formZahtev(url.Values{
|
||||||
"naziv": {"Pumpa"}, "kolicina": {"10"}, "prodajna_cena": {"1500.50"},
|
"naziv": {"Pumpa"}, "kolicina": {"10"}, "prodajna_cena": {"1500.50"},
|
||||||
}))
|
}), 20.0)
|
||||||
if greska != "" {
|
if greska != "" {
|
||||||
t.Fatalf("neočekivana greška: %q", greska)
|
t.Fatalf("neočekivana greška: %q", greska)
|
||||||
}
|
}
|
||||||
@@ -31,18 +31,18 @@ func TestParseFormuArtikla(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("naziv obavezan", func(t *testing.T) {
|
t.Run("naziv obavezan", func(t *testing.T) {
|
||||||
if _, greska := parseFormuArtikla(formZahtev(url.Values{"naziv": {""}})); greska == "" {
|
if _, greska := parseFormuArtikla(formZahtev(url.Values{"naziv": {""}}), 20.0); greska == "" {
|
||||||
t.Fatal("prazan naziv mora dati grešku")
|
t.Fatal("prazan naziv mora dati grešku")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("negativna količina", func(t *testing.T) {
|
t.Run("negativna količina", func(t *testing.T) {
|
||||||
_, greska := parseFormuArtikla(formZahtev(url.Values{"naziv": {"X"}, "kolicina": {"-5"}}))
|
_, greska := parseFormuArtikla(formZahtev(url.Values{"naziv": {"X"}, "kolicina": {"-5"}}), 20.0)
|
||||||
if greska == "" {
|
if greska == "" {
|
||||||
t.Fatal("negativna količina mora dati grešku")
|
t.Fatal("negativna količina mora dati grešku")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("neispravna cena", func(t *testing.T) {
|
t.Run("neispravna cena", func(t *testing.T) {
|
||||||
_, greska := parseFormuArtikla(formZahtev(url.Values{"naziv": {"X"}, "prodajna_cena": {"abc"}}))
|
_, greska := parseFormuArtikla(formZahtev(url.Values{"naziv": {"X"}, "prodajna_cena": {"abc"}}), 20.0)
|
||||||
if greska == "" {
|
if greska == "" {
|
||||||
t.Fatal("neispravna cena mora dati grešku")
|
t.Fatal("neispravna cena mora dati grešku")
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ func TestParseFormuKlijenta(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseFormuProdaje(t *testing.T) {
|
func TestParseFormuProdaje(t *testing.T) {
|
||||||
t.Run("bez stavki", func(t *testing.T) {
|
t.Run("bez stavki", func(t *testing.T) {
|
||||||
_, _, greska := parseFormuProdaje(formZahtev(url.Values{}))
|
_, _, greska := parseFormuProdaje(formZahtev(url.Values{}), map[float64]bool{0: true, 10: true, 20: true})
|
||||||
if greska == "" {
|
if greska == "" {
|
||||||
t.Fatal("prodaja bez stavki mora dati grešku")
|
t.Fatal("prodaja bez stavki mora dati grešku")
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ func TestParseFormuProdaje(t *testing.T) {
|
|||||||
t.Run("količina nula", func(t *testing.T) {
|
t.Run("količina nula", func(t *testing.T) {
|
||||||
_, _, greska := parseFormuProdaje(formZahtev(url.Values{
|
_, _, greska := parseFormuProdaje(formZahtev(url.Values{
|
||||||
"artikal_id[]": {"1"}, "kolicina[]": {"0"}, "cena_po_komadu[]": {"100"},
|
"artikal_id[]": {"1"}, "kolicina[]": {"0"}, "cena_po_komadu[]": {"100"},
|
||||||
}))
|
}), map[float64]bool{0: true, 10: true, 20: true})
|
||||||
if greska == "" {
|
if greska == "" {
|
||||||
t.Fatal("količina 0 mora dati grešku")
|
t.Fatal("količina 0 mora dati grešku")
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ func TestParseFormuProdaje(t *testing.T) {
|
|||||||
t.Run("neispravan artikal", func(t *testing.T) {
|
t.Run("neispravan artikal", func(t *testing.T) {
|
||||||
_, _, greska := parseFormuProdaje(formZahtev(url.Values{
|
_, _, greska := parseFormuProdaje(formZahtev(url.Values{
|
||||||
"artikal_id[]": {"0"}, "kolicina[]": {"1"}, "cena_po_komadu[]": {"100"},
|
"artikal_id[]": {"0"}, "kolicina[]": {"1"}, "cena_po_komadu[]": {"100"},
|
||||||
}))
|
}), map[float64]bool{0: true, 10: true, 20: true})
|
||||||
if greska == "" {
|
if greska == "" {
|
||||||
t.Fatal("artikal_id 0 mora dati grešku")
|
t.Fatal("artikal_id 0 mora dati grešku")
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ func TestParseFormuProdaje(t *testing.T) {
|
|||||||
t.Run("validna stavka", func(t *testing.T) {
|
t.Run("validna stavka", func(t *testing.T) {
|
||||||
nalog, stavke, greska := parseFormuProdaje(formZahtev(url.Values{
|
nalog, stavke, greska := parseFormuProdaje(formZahtev(url.Values{
|
||||||
"artikal_id[]": {"3"}, "kolicina[]": {"2"}, "cena_po_komadu[]": {"250"}, "pdv_stopa[]": {"20"},
|
"artikal_id[]": {"3"}, "kolicina[]": {"2"}, "cena_po_komadu[]": {"250"}, "pdv_stopa[]": {"20"},
|
||||||
}))
|
}), map[float64]bool{0: true, 10: true, 20: true})
|
||||||
if greska != "" {
|
if greska != "" {
|
||||||
t.Fatalf("neočekivana greška: %q", greska)
|
t.Fatalf("neočekivana greška: %q", greska)
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ func TestParseFormuProdaje(t *testing.T) {
|
|||||||
t.Run("nesklad broja stavki", func(t *testing.T) {
|
t.Run("nesklad broja stavki", func(t *testing.T) {
|
||||||
_, _, greska := parseFormuProdaje(formZahtev(url.Values{
|
_, _, greska := parseFormuProdaje(formZahtev(url.Values{
|
||||||
"artikal_id[]": {"1", "2"}, "kolicina[]": {"1"}, "cena_po_komadu[]": {"100"},
|
"artikal_id[]": {"1", "2"}, "kolicina[]": {"1"}, "cena_po_komadu[]": {"100"},
|
||||||
}))
|
}), map[float64]bool{0: true, 10: true, 20: true})
|
||||||
if greska == "" {
|
if greska == "" {
|
||||||
t.Fatal("nesklad broja stavki mora dati grešku")
|
t.Fatal("nesklad broja stavki mora dati grešku")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,6 +285,20 @@ func validirajMaticniBroj(broj string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validirajProfilFirme proverava da li je kombinacija pravnog/poreskog statusa
|
||||||
|
// pravno moguća — vidi docs/Greške.md §1. „Samo evidencija” gasi ceo zakonski
|
||||||
|
// sloj, pa dok je firma u tom režimu ne postoji osnov da bude PDV obveznik
|
||||||
|
// niti da izdaje fiskalne račune.
|
||||||
|
func validirajProfilFirme(rezim, pdvObveznik, fiskalizacija string) string {
|
||||||
|
if rezim != "samo_evidencija" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if pdvObveznik == "da" || fiskalizacija == "da" {
|
||||||
|
return "Režim „Samo evidencija” ne dozvoljava PDV obveznika ni izdavanje fiskalnih računa — prebacite firmu na režim „Pun” ako stvarno posluje."
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// SacuvajPodesavanja prima POST i čuva podešavanja u bazu
|
// SacuvajPodesavanja prima POST i čuva podešavanja u bazu
|
||||||
func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
||||||
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.izmeni"); !ok {
|
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.izmeni"); !ok {
|
||||||
@@ -320,6 +334,19 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, jeFirmaSekcija := r.Form["_sekcija_firma"]; jeFirmaSekcija {
|
||||||
|
greska := validirajProfilFirme(r.FormValue("firma_rezim"), r.FormValue("firma_pdv_obveznik"), r.FormValue("firma_fiskalizacija"))
|
||||||
|
if greska != "" {
|
||||||
|
middleware.SetFlash(w, r, h.DB, "greska", greska)
|
||||||
|
sledeci := "/podesavanja"
|
||||||
|
if r.FormValue("_next") == "/admin/podesavanja/opste" {
|
||||||
|
sledeci = "/admin/podesavanja/opste"
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, sledeci, http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
polja := map[string]string{
|
polja := map[string]string{
|
||||||
"naziv_firme": r.FormValue("naziv_firme"),
|
"naziv_firme": r.FormValue("naziv_firme"),
|
||||||
"podnazlov": r.FormValue("podnazlov"),
|
"podnazlov": r.FormValue("podnazlov"),
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ func (h *Handler) SacuvajProdaju(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nalog, stavke, greska := parseFormuProdaje(r)
|
nalog, stavke, greska := parseFormuProdaje(r, h.validnePdvStope(r.Context()))
|
||||||
|
|
||||||
renderujGresku := func(poruka string) {
|
renderujGresku := func(poruka string) {
|
||||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
@@ -536,7 +536,9 @@ pre{white-space:pre;margin:0;padding:0;font-family:inherit;font-size:inherit;dis
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseFormuProdaje čita zaglavlje i stavke iz HTTP forme i vraća model i eventualnu grešku
|
// parseFormuProdaje čita zaglavlje i stavke iz HTTP forme i vraća model i eventualnu grešku
|
||||||
func parseFormuProdaje(r *http.Request) (model.ProdajniNalog, []model.StavkaProdaje, string) {
|
// validneStope su dozvoljene PDV stope na stavci (v. Handler.validnePdvStope) —
|
||||||
|
// izvedene iz šifarnika, ne hardkodovane.
|
||||||
|
func parseFormuProdaje(r *http.Request, validneStope map[float64]bool) (model.ProdajniNalog, []model.StavkaProdaje, string) {
|
||||||
var nalog model.ProdajniNalog
|
var nalog model.ProdajniNalog
|
||||||
|
|
||||||
if klijentIDStr := r.FormValue("klijent_id"); klijentIDStr != "" {
|
if klijentIDStr := r.FormValue("klijent_id"); klijentIDStr != "" {
|
||||||
@@ -589,8 +591,8 @@ func parseFormuProdaje(r *http.Request) (model.ProdajniNalog, []model.StavkaProd
|
|||||||
return nalog, nil, "Neispravna PDV stopa u stavci."
|
return nalog, nil, "Neispravna PDV stopa u stavci."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if pdvStopa != 0 && pdvStopa != 10 && pdvStopa != 20 {
|
if !validneStope[pdvStopa] {
|
||||||
return nalog, nil, "PDV stopa mora biti 0, 10 ili 20."
|
return nalog, nil, "PDV stopa u stavci nije u šifarniku PDV stopa."
|
||||||
}
|
}
|
||||||
|
|
||||||
var popust float64
|
var popust float64
|
||||||
|
|||||||
+37
-12
@@ -2531,8 +2531,15 @@ func (h *Handler) fiskalizujAvansServisa(ctx context.Context, servisID int64, no
|
|||||||
if nacinPlacanja == "" {
|
if nacinPlacanja == "" {
|
||||||
nacinPlacanja = "Gotovina"
|
nacinPlacanja = "Gotovina"
|
||||||
}
|
}
|
||||||
|
// stopa avansa: opšta stopa iz šifarnika za PDV obveznika, 0 (van sistema PDV)
|
||||||
|
// za ne-obveznika — stavke (radovi/delovi) još nisu poznate u trenutku avansa,
|
||||||
|
// pa se ne može koristiti njihova stvarna stopa (v. docs/Greške.md §4.2).
|
||||||
|
stopaAvansa := 0.0
|
||||||
|
if h.modulUkljucen(ctx, config.ModulPdv) {
|
||||||
|
stopaAvansa = h.podrazumevanaPdvStopa(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
zahtev := fiskal.NapraviAvansZahtev(delta, nacinPlacanja, h.imeKasira(ctx))
|
zahtev := fiskal.NapraviAvansZahtev(delta, stopaAvansa, nacinPlacanja, h.imeKasira(ctx))
|
||||||
odgovor, err := klijent.IzdajRacun(ctx, zahtev)
|
odgovor, err := klijent.IzdajRacun(ctx, zahtev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("fiskalizacija avansa servisa nije uspela", "servis_id", servisID, "error", err)
|
slog.Error("fiskalizacija avansa servisa nije uspela", "servis_id", servisID, "error", err)
|
||||||
@@ -2587,7 +2594,7 @@ func (h *Handler) fiskalizujServis(ctx context.Context, servisID int64, klijent
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
items := stavkeFiskalnogServisa(radovi, delovi)
|
items := stavkeFiskalnogServisa(radovi, delovi, h.modulUkljucen(ctx, config.ModulPdv))
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
slog.Warn("fiskalizujServis: nema stavki za fiskalni račun, zahtev odbačen", "id", servisID)
|
slog.Warn("fiskalizujServis: nema stavki za fiskalni račun, zahtev odbačen", "id", servisID)
|
||||||
return
|
return
|
||||||
@@ -2626,6 +2633,13 @@ func (h *Handler) fiskalizujServis(ctx context.Context, servisID int64, klijent
|
|||||||
// zatvara — PFR sam raspoređuje avans na ovaj račun preko Advance* polja.
|
// zatvara — PFR sam raspoređuje avans na ovaj račun preko Advance* polja.
|
||||||
// Ako avans premašuje punu cenu (visak), zatvara se samo do pune cene, a
|
// Ako avans premašuje punu cenu (visak), zatvara se samo do pune cene, a
|
||||||
// razlika se posle uspešnog Sale-a vraća posebnim Advance/Refund zahtevom.
|
// razlika se posle uspešnog Sale-a vraća posebnim Advance/Refund zahtevom.
|
||||||
|
// stopa avansa — ista logika kao pri fiskalizaciji avansa (v. fiskalizujAvansServisa);
|
||||||
|
// pretpostavlja da se profil firme nije promenio između avansa i konačne naplate.
|
||||||
|
stopaAvansa := 0.0
|
||||||
|
if h.modulUkljucen(ctx, config.ModulPdv) {
|
||||||
|
stopaAvansa = h.podrazumevanaPdvStopa(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
var visakAvansa float64
|
var visakAvansa float64
|
||||||
var avansRacun *model.FiskalniRacun
|
var avansRacun *model.FiskalniRacun
|
||||||
if avansNeto, e := h.FiskalRepo.SumaAvansaPoServisu(ctx, servisID); e == nil && avansNeto > 0 {
|
if avansNeto, e := h.FiskalRepo.SumaAvansaPoServisu(ctx, servisID); e == nil && avansNeto > 0 {
|
||||||
@@ -2641,7 +2655,7 @@ func (h *Handler) fiskalizujServis(ctx context.Context, servisID int64, klijent
|
|||||||
visakAvansa = math.Round((iskoriscenAvans-punaCena)*100) / 100
|
visakAvansa = math.Round((iskoriscenAvans-punaCena)*100) / 100
|
||||||
iskoriscenAvans = punaCena
|
iskoriscenAvans = punaCena
|
||||||
}
|
}
|
||||||
porez := fiskal.PorezIzBrutoAvansa(iskoriscenAvans)
|
porez := fiskal.PorezIzBrutoAvansa(iskoriscenAvans, stopaAvansa)
|
||||||
zahtev.AdvancePaid = &iskoriscenAvans
|
zahtev.AdvancePaid = &iskoriscenAvans
|
||||||
zahtev.AdvanceTax = &porez
|
zahtev.AdvanceTax = &porez
|
||||||
zahtev.AdvanceLastInvoiceNumber = avansRacun.PfrBroj
|
zahtev.AdvanceLastInvoiceNumber = avansRacun.PfrBroj
|
||||||
@@ -2685,7 +2699,7 @@ func (h *Handler) fiskalizujServis(ctx context.Context, servisID int64, klijent
|
|||||||
|
|
||||||
// avans je premašio punu cenu — vrati razliku posebnim Advance/Refund zahtevom
|
// avans je premašio punu cenu — vrati razliku posebnim Advance/Refund zahtevom
|
||||||
if visakAvansa > 0 && avansRacun != nil {
|
if visakAvansa > 0 && avansRacun != nil {
|
||||||
refundZahtev := fiskal.NapraviAvansRefundZahtev(visakAvansa, nacinPlacanja, kasir, avansRacun.PfrBroj)
|
refundZahtev := fiskal.NapraviAvansRefundZahtev(visakAvansa, stopaAvansa, nacinPlacanja, kasir, avansRacun.PfrBroj)
|
||||||
refundOdgovor, errRefund := klijent.IzdajRacun(ctx, refundZahtev)
|
refundOdgovor, errRefund := klijent.IzdajRacun(ctx, refundZahtev)
|
||||||
if errRefund != nil {
|
if errRefund != nil {
|
||||||
slog.Error("povraćaj viška avansa nije uspeo", "servis_id", servisID, "error", errRefund)
|
slog.Error("povraćaj viška avansa nije uspeo", "servis_id", servisID, "error", errRefund)
|
||||||
@@ -2720,17 +2734,27 @@ func (h *Handler) fiskalizujServis(ctx context.Context, servisID int64, klijent
|
|||||||
// stavkeFiskalnogServisa gradi stavke fiskalnog računa od radova i ugrađenih
|
// stavkeFiskalnogServisa gradi stavke fiskalnog računa od radova i ugrađenih
|
||||||
// delova servisnog naloga — koristi se i za Sale (fiskalizujServis) i za
|
// delova servisnog naloga — koristi se i za Sale (fiskalizujServis) i za
|
||||||
// Refund (refundujServis), predloženi (neprihvaćeni) redovi se ne naplaćuju.
|
// Refund (refundujServis), predloženi (neprihvaćeni) redovi se ne naplaćuju.
|
||||||
func stavkeFiskalnogServisa(radovi []model.ServisniRad, delovi []model.ServisniDeoSaArtiklom) []fiskal.InvoiceItem {
|
// pdvUkljucen odražava trenutni profil firme (modul „pdv") — kad je false,
|
||||||
|
// stope se prisilno nuliraju bez obzira šta piše u tabelama usluga/artikala
|
||||||
|
// (ista odbrana kao u Prodaji, v. prodaja.go i docs/Greške.md §2.1).
|
||||||
|
func stavkeFiskalnogServisa(radovi []model.ServisniRad, delovi []model.ServisniDeoSaArtiklom, pdvUkljucen bool) []fiskal.InvoiceItem {
|
||||||
|
stopaZa := func(stopa float64) float64 {
|
||||||
|
if !pdvUkljucen {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return stopa
|
||||||
|
}
|
||||||
items := make([]fiskal.InvoiceItem, 0)
|
items := make([]fiskal.InvoiceItem, 0)
|
||||||
for _, r := range radovi {
|
for _, r := range radovi {
|
||||||
if r.Predlozeno {
|
if r.Predlozeno {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
stopa := stopaZa(r.PdvStopa)
|
||||||
items = append(items, fiskal.InvoiceItem{
|
items = append(items, fiskal.InvoiceItem{
|
||||||
Name: r.Naziv,
|
Name: r.Naziv,
|
||||||
Labels: []string{fiskal.OznakaPDV(r.PdvStopa)},
|
Labels: []string{fiskal.OznakaPDV(stopa)},
|
||||||
TotalAmount: fiskal.BrutoCena(r.Ukupno(), r.PdvStopa),
|
TotalAmount: fiskal.BrutoCena(r.Ukupno(), stopa),
|
||||||
UnitPrice: fiskal.BrutoCena(r.CenaKomada, r.PdvStopa),
|
UnitPrice: fiskal.BrutoCena(r.CenaKomada, stopa),
|
||||||
Quantity: r.Kolicina,
|
Quantity: r.Kolicina,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2738,11 +2762,12 @@ func stavkeFiskalnogServisa(radovi []model.ServisniRad, delovi []model.ServisniD
|
|||||||
if d.Predlozeno {
|
if d.Predlozeno {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
stopa := stopaZa(d.PdvStopa)
|
||||||
items = append(items, fiskal.InvoiceItem{
|
items = append(items, fiskal.InvoiceItem{
|
||||||
Name: d.ArtikalNaziv,
|
Name: d.ArtikalNaziv,
|
||||||
Labels: []string{fiskal.OznakaPDV(d.PdvStopa)},
|
Labels: []string{fiskal.OznakaPDV(stopa)},
|
||||||
TotalAmount: fiskal.BrutoCena(d.Ukupno(), d.PdvStopa),
|
TotalAmount: fiskal.BrutoCena(d.Ukupno(), stopa),
|
||||||
UnitPrice: fiskal.BrutoCena(d.CenaKomada, d.PdvStopa),
|
UnitPrice: fiskal.BrutoCena(d.CenaKomada, stopa),
|
||||||
Quantity: float64(d.Kolicina),
|
Quantity: float64(d.Kolicina),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2769,7 +2794,7 @@ func (h *Handler) refundujServis(ctx context.Context, servisID int64, klijent *f
|
|||||||
slog.Error("refundujServis: greška pri dohvatanju delova", "id", servisID, "error", err)
|
slog.Error("refundujServis: greška pri dohvatanju delova", "id", servisID, "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
items := stavkeFiskalnogServisa(radovi, delovi)
|
items := stavkeFiskalnogServisa(radovi, delovi, h.modulUkljucen(ctx, config.ModulPdv))
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func (h *Handler) NovaUsluga(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
sifra = "USL-001"
|
sifra = "USL-001"
|
||||||
}
|
}
|
||||||
h.renderujFormuUsluge(w, r, model.Usluga{Sifra: sifra, PdvStopa: 20, JedinicaMere: "usluga"}, false, "")
|
h.renderujFormuUsluge(w, r, model.Usluga{Sifra: sifra, PdvStopa: h.podrazumevanaPdvStopa(r.Context()), JedinicaMere: "usluga"}, false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// IzmeniUslugu prikazuje formu sa postojećom uslugom
|
// IzmeniUslugu prikazuje formu sa postojećom uslugom
|
||||||
@@ -86,7 +86,7 @@ func (h *Handler) SacuvajUslugu(w http.ResponseWriter, r *http.Request) {
|
|||||||
if _, ok := h.zahtevajDozvolu(w, r, "artikal.dodaj"); !ok {
|
if _, ok := h.zahtevajDozvolu(w, r, "artikal.dodaj"); !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
usluga, greska := parseFormuUsluge(r)
|
usluga, greska := parseFormuUsluge(r, h.podrazumevanaPdvStopa(r.Context()))
|
||||||
if greska != "" {
|
if greska != "" {
|
||||||
h.renderujFormuUsluge(w, r, usluga, false, greska)
|
h.renderujFormuUsluge(w, r, usluga, false, greska)
|
||||||
return
|
return
|
||||||
@@ -108,7 +108,7 @@ func (h *Handler) SacuvajIzmenuUsluge(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "Neispravan ID usluge", http.StatusBadRequest)
|
http.Error(w, "Neispravan ID usluge", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
usluga, greska := parseFormuUsluge(r)
|
usluga, greska := parseFormuUsluge(r, h.podrazumevanaPdvStopa(r.Context()))
|
||||||
usluga.ID = id
|
usluga.ID = id
|
||||||
if greska != "" {
|
if greska != "" {
|
||||||
h.renderujFormuUsluge(w, r, usluga, true, greska)
|
h.renderujFormuUsluge(w, r, usluga, true, greska)
|
||||||
@@ -139,7 +139,9 @@ func (h *Handler) ObrisiUslugu(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseFormuUsluge čita i validira polja forme usluge. Vraća model i poruku o grešci.
|
// parseFormuUsluge čita i validira polja forme usluge. Vraća model i poruku o grešci.
|
||||||
func parseFormuUsluge(r *http.Request) (model.Usluga, string) {
|
// podrazumevanaStopa je opšta PDV stopa iz šifarnika (v. Handler.podrazumevanaPdvStopa) —
|
||||||
|
// koristi se samo kad korisnik nije uneo stopu, nikad hardkodovana vrednost.
|
||||||
|
func parseFormuUsluge(r *http.Request, podrazumevanaStopa float64) (model.Usluga, string) {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
return model.Usluga{}, "Greška pri čitanju forme."
|
return model.Usluga{}, "Greška pri čitanju forme."
|
||||||
}
|
}
|
||||||
@@ -154,7 +156,7 @@ func parseFormuUsluge(r *http.Request) (model.Usluga, string) {
|
|||||||
}
|
}
|
||||||
pdv, err := strconv.ParseFloat(strings.TrimSpace(r.FormValue("pdv_stopa")), 64)
|
pdv, err := strconv.ParseFloat(strings.TrimSpace(r.FormValue("pdv_stopa")), 64)
|
||||||
if err != nil || pdv < 0 {
|
if err != nil || pdv < 0 {
|
||||||
pdv = 20
|
pdv = podrazumevanaStopa
|
||||||
}
|
}
|
||||||
|
|
||||||
jm := strings.TrimSpace(r.FormValue("jedinica_mere"))
|
jm := strings.TrimSpace(r.FormValue("jedinica_mere"))
|
||||||
|
|||||||
Reference in New Issue
Block a user