Cena sa PDV-om u magacinu + uslovno prikazivanje po modulu
- migrations/087: nova kolona cena_sa_pdv + popunjavanje postojećih - model.Artikal: polje CenaSaPdv, ispravljene CenaBezPdv/PdvIznos (neto) - sqlite/artikal.go: SELECT/Scan/INSERT/UPDATE za cena_sa_pdv (6 mesta) - magacin_forma.go: pdv_stopa iz forme, kalkulacija cena_sa_pdv, default PDV 20% - magacin.html: dinari format (25.999,00), kolona Sa PDV-om, uslovno po Moduli.pdv - magacin_forma.html: PDV dropdown + Cena sa PDV polje, uslovno po Moduli.pdv, optimizovan layout (Naziv/Jed. mere/Kategorija u 1 redu) - main.go: demo mod bez keširanja šablona (hot-reload) - model_test.go: ažurirani testovi za novo ponašanje CenaBezPdv/PdvIznos
This commit is contained in:
+1
-1
@@ -159,7 +159,7 @@ func main() {
|
||||
// čuva odabrani FS (disk ili embed) za hot-reload u razvoju i za keš u produkciji
|
||||
h.TemplatesFS = templFS
|
||||
|
||||
if ntechEnv := os.Getenv("NTECH_ENV"); ntechEnv == "production" || ntechEnv == "demo" {
|
||||
if ntechEnv := os.Getenv("NTECH_ENV"); ntechEnv == "production" {
|
||||
kes, err := handler.KreirajKes(templFS)
|
||||
if err != nil {
|
||||
slog.Error("Greška pri kreiranju keša šablona", "error", err)
|
||||
|
||||
@@ -30,7 +30,7 @@ func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]mod
|
||||
SELECT
|
||||
a.id, a.kategorija_id, a.sifra, a.barkod, a.naziv, a.opis, a.tip, a.jedinica_mere,
|
||||
a.kolicina, a.kolicina_min, a.lokacija,
|
||||
a.nabavna_cena, a.prodajna_cena, a.pdv_stopa, a.marza, a.napomena, a.datum_unosa, a.arhiviran,
|
||||
a.nabavna_cena, a.prodajna_cena, a.pdv_stopa, a.cena_sa_pdv, a.marza, a.napomena, a.datum_unosa, a.arhiviran,
|
||||
COALESCE(k.naziv, '') as kategorija_naziv, k.marza as kategorija_marza
|
||||
FROM artikli a
|
||||
LEFT JOIN kategorije k ON a.kategorija_id = k.id
|
||||
@@ -93,7 +93,7 @@ func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]mod
|
||||
err := redovi.Scan(
|
||||
&a.ID, &kategorijaID, &sifra, &barkod, &a.Naziv, &a.Opis, &a.Tip, &a.JedinicaMere,
|
||||
&a.Kolicina, &a.KolicinMin, &a.Lokacija,
|
||||
&a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &marza, &a.Napomena, &a.DatumUnosa, &arhiviran,
|
||||
&a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &a.CenaSaPdv, &marza, &a.Napomena, &a.DatumUnosa, &arhiviran,
|
||||
&a.KategorijaNaziv, &katMarza,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -136,11 +136,11 @@ func (r *ArtikalRepo) DohvatiID(ctx context.Context, id int64) (*model.Artikal,
|
||||
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT id, kategorija_id, sifra, barkod, naziv, opis, tip, jedinica_mere, kolicina, kolicina_min,
|
||||
lokacija, nabavna_cena, prodajna_cena, pdv_stopa, marza, napomena, datum_unosa, arhiviran
|
||||
lokacija, nabavna_cena, prodajna_cena, pdv_stopa, cena_sa_pdv, marza, napomena, datum_unosa, arhiviran
|
||||
FROM artikli WHERE id = ?`, id).Scan(
|
||||
&a.ID, &kategorijaID, &sifra, &barkod, &a.Naziv, &a.Opis, &a.Tip, &a.JedinicaMere,
|
||||
&a.Kolicina, &a.KolicinMin, &a.Lokacija,
|
||||
&a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &marza, &a.Napomena, &a.DatumUnosa, &arhiviran,
|
||||
&a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &a.CenaSaPdv, &marza, &a.Napomena, &a.DatumUnosa, &arhiviran,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: ArtikalRepo.DohvatiID: %w", err)
|
||||
@@ -176,10 +176,10 @@ func (r *ArtikalRepo) Kreiraj(ctx context.Context, a *model.Artikal) (int64, err
|
||||
rezultat, err := r.db.ExecContext(ctx, `
|
||||
INSERT INTO artikli
|
||||
(kategorija_id, sifra, barkod, naziv, opis, tip, jedinica_mere, kolicina, kolicina_min, lokacija,
|
||||
nabavna_cena, prodajna_cena, pdv_stopa, marza, napomena)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
nabavna_cena, prodajna_cena, pdv_stopa, cena_sa_pdv, marza, napomena)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
a.KategorijaID, sifra, barkod, a.Naziv, a.Opis, a.Tip, a.JedinicaMere, a.Kolicina, a.KolicinMin,
|
||||
a.Lokacija, a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.Marza, a.Napomena,
|
||||
a.Lokacija, a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.CenaSaPdv, a.Marza, a.Napomena,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ntech: ArtikalRepo.Kreiraj: %w", err)
|
||||
@@ -207,11 +207,11 @@ func (r *ArtikalRepo) Izmeni(ctx context.Context, a *model.Artikal) error {
|
||||
UPDATE artikli SET
|
||||
kategorija_id = ?, sifra = ?, barkod = ?, naziv = ?, opis = ?, tip = ?, jedinica_mere = ?,
|
||||
kolicina = ?, kolicina_min = ?, lokacija = ?,
|
||||
nabavna_cena = ?, prodajna_cena = ?, pdv_stopa = ?, marza = ?, napomena = ?
|
||||
WHERE id = ?`,
|
||||
nabavna_cena = ?, prodajna_cena = ?, pdv_stopa = ?, cena_sa_pdv = ?, marza = ?, napomena = ?
|
||||
WHERE id = ?`,
|
||||
a.KategorijaID, sifra, barkod, a.Naziv, a.Opis, a.Tip, a.JedinicaMere,
|
||||
a.Kolicina, a.KolicinMin, a.Lokacija,
|
||||
a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.Marza, a.Napomena, a.ID,
|
||||
a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.CenaSaPdv, a.Marza, a.Napomena, a.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ArtikalRepo.Izmeni: %w", err)
|
||||
|
||||
@@ -62,7 +62,7 @@ func (h *Handler) NoviArtikal(w http.ResponseWriter, r *http.Request) {
|
||||
PodaciStranice: ps,
|
||||
Kategorije: kategorije,
|
||||
Dobavljaci: dobavljaci,
|
||||
Artikal: model.Artikal{Sifra: predlogSifre, Tip: tip, JedinicaMere: "kom"},
|
||||
Artikal: model.Artikal{Sifra: predlogSifre, Tip: tip, JedinicaMere: "kom", PdvStopa: 20.0},
|
||||
Izmena: false,
|
||||
})
|
||||
}
|
||||
@@ -389,6 +389,15 @@ func parseFormuArtikla(r *http.Request) (model.Artikal, string) {
|
||||
artikal.NabavnaCena = v
|
||||
}
|
||||
|
||||
// PDV stopa — podrazumevano 20%
|
||||
pdvStopa := 20.0
|
||||
if p := r.FormValue("pdv_stopa"); p != "" {
|
||||
if v, err := strconv.ParseFloat(p, 64); err == nil && v >= 0 {
|
||||
pdvStopa = v
|
||||
}
|
||||
}
|
||||
artikal.PdvStopa = pdvStopa
|
||||
|
||||
if c := r.FormValue("prodajna_cena"); c != "" {
|
||||
v, err := strconv.ParseFloat(c, 64)
|
||||
if err != nil || v < 0 {
|
||||
@@ -413,6 +422,21 @@ func parseFormuArtikla(r *http.Request) (model.Artikal, string) {
|
||||
}
|
||||
}
|
||||
|
||||
// kalkulacija cena_sa_pdv na osnovu unetog polja
|
||||
if c := r.FormValue("cena_sa_pdv"); c != "" {
|
||||
// korisnik je uneo bruto cenu → izračunaj neto
|
||||
if v, err := strconv.ParseFloat(c, 64); err == nil && v >= 0 {
|
||||
artikal.CenaSaPdv = v
|
||||
// ako prodajna_cena nije uneta ručno, izračunaj je
|
||||
if r.FormValue("prodajna_cena") == "" {
|
||||
artikal.ProdajnaCena = v / (1 + artikal.PdvStopa/100)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// korisnik je uneo neto cenu → izračunaj bruto
|
||||
artikal.CenaSaPdv = artikal.ProdajnaCena * (1 + artikal.PdvStopa/100)
|
||||
}
|
||||
|
||||
return artikal, ""
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ type Artikal struct {
|
||||
NabavnaCena float64
|
||||
ProdajnaCena float64
|
||||
PdvStopa float64
|
||||
CenaSaPdv float64 // prodajna cena sa PDV-om (bruto); = ProdajnaCena * (1 + PdvStopa/100)
|
||||
Marza *float64 // podrazumevana marža (%) za kalkulaciju; NULL = nije postavljeno
|
||||
Napomena string
|
||||
DatumUnosa time.Time
|
||||
@@ -37,14 +38,14 @@ func (a Artikal) PratiLager() bool {
|
||||
return a.Tip == TipProizvod || a.Tip == ""
|
||||
}
|
||||
|
||||
// CenaBezPdv izračunava prodajnu cenu bez PDV-a
|
||||
// CenaBezPdv vraća prodajnu cenu bez PDV-a (neto)
|
||||
func (a Artikal) CenaBezPdv() float64 {
|
||||
return a.ProdajnaCena / (1 + a.PdvStopa/100)
|
||||
return a.ProdajnaCena
|
||||
}
|
||||
|
||||
// PdvIznos izračunava iznos PDV-a za jednu jedinicu
|
||||
func (a Artikal) PdvIznos() float64 {
|
||||
return a.ProdajnaCena - a.CenaBezPdv()
|
||||
return a.CenaSaPdv - a.ProdajnaCena
|
||||
}
|
||||
|
||||
// Kategorija predstavlja kategoriju artikala
|
||||
|
||||
@@ -15,19 +15,20 @@ func ptr(v float64) *float64 { return &v }
|
||||
func TestArtikalCenaBezPdvIPdvIznos(t *testing.T) {
|
||||
testovi := []struct {
|
||||
naziv string
|
||||
prodajna float64
|
||||
prodajna float64 // neto
|
||||
cenaSaPdv float64 // bruto
|
||||
stopa float64
|
||||
ocekBezPdv float64
|
||||
ocekPdvIznos float64
|
||||
}{
|
||||
{"stopa 20%", 120, 20, 100, 20},
|
||||
{"stopa 0%", 100, 0, 100, 0},
|
||||
{"stopa 10%", 110, 10, 100, 10},
|
||||
{"nula cena", 0, 20, 0, 0},
|
||||
{"stopa 20%", 100, 120, 20, 100, 20},
|
||||
{"stopa 0%", 100, 100, 0, 100, 0},
|
||||
{"stopa 10%", 100, 110, 10, 100, 10},
|
||||
{"nula cena", 0, 0, 20, 0, 0},
|
||||
}
|
||||
for _, tt := range testovi {
|
||||
t.Run(tt.naziv, func(t *testing.T) {
|
||||
a := Artikal{ProdajnaCena: tt.prodajna, PdvStopa: tt.stopa}
|
||||
a := Artikal{ProdajnaCena: tt.prodajna, CenaSaPdv: tt.cenaSaPdv, PdvStopa: tt.stopa}
|
||||
if got := a.CenaBezPdv(); !jednako(got, tt.ocekBezPdv) {
|
||||
t.Errorf("CenaBezPdv() = %v, očekivano %v", got, tt.ocekBezPdv)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE artikli ADD COLUMN cena_sa_pdv REAL NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE artikli
|
||||
SET cena_sa_pdv = ROUND(prodajna_cena * (1 + pdv_stopa / 100), 2);
|
||||
@@ -80,6 +80,7 @@
|
||||
<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:right;font-size:12px;font-weight:500;color:var(--tekst-jak);">Prodajna</th>
|
||||
{{if index .Moduli "pdv"}}<th style="padding:12px 16px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-jak);">Sa PDV-om</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:center;font-size:12px;font-weight:500;color:var(--tekst-jak);">Akcije</th>
|
||||
</tr>
|
||||
@@ -101,7 +102,8 @@
|
||||
<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);">{{dinariCeli .ProdajnaCena}} din</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-size:14px;color:var(--tekst-glavni);">{{dinari .ProdajnaCena}} din</td>
|
||||
{{if index $.Moduli "pdv"}}<td style="padding:12px 16px;text-align:right;font-size:14px;color:var(--tekst-glavni);">{{dinari .CenaSaPdv}} din</td>{{end}}
|
||||
<td style="padding:12px 16px;font-size:13px;color:var(--tekst-sporedni);">
|
||||
{{if .Lokacija}}{{.Lokacija}}{{else}}—{{end}}
|
||||
</td>
|
||||
@@ -134,7 +136,7 @@
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr>
|
||||
<td colspan="7" style="padding:32px;text-align:center;font-size:14px;color:var(--tekst-sporedni);">
|
||||
<td colspan="{{if index .Moduli "pdv"}}8{{else}}7{{end}}" style="padding:32px;text-align:center;font-size:14px;color:var(--tekst-sporedni);">
|
||||
{{if or $.Filter.Pretraga $.KategorijaIDStr $.Filter.SamoKriticni}}Za datu pretragu nema stavki u bazi.{{else}}Nema stavki. <a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/magacin/novi?tip={{$.Tip}}" style="color:var(--sb-akcent);">Dodaj prvu.</a>{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -192,8 +194,13 @@
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="pomocni-tekst">
|
||||
<span style="color:var(--tekst-glavni);font-weight:500;">Cena:</span> {{dinariCeli .ProdajnaCena}} din
|
||||
<span style="color:var(--tekst-glavni);font-weight:500;">Cena:</span> {{dinari .ProdajnaCena}} din
|
||||
</div>
|
||||
{{if index $.Moduli "pdv"}}
|
||||
<div class="pomocni-tekst">
|
||||
<span style="color:var(--tekst-glavni);font-weight:500;">Sa PDV:</span> {{dinari .CenaSaPdv}} din
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Lokacija}}
|
||||
<div class="pomocni-tekst">
|
||||
<span style="color:var(--tekst-glavni);font-weight:500;">Lokacija:</span> {{.Lokacija}}
|
||||
|
||||
@@ -43,44 +43,42 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- naziv -->
|
||||
<div>
|
||||
<label class="polje-labela">
|
||||
Naziv <span style="color:#dc2626;">*</span>
|
||||
</label>
|
||||
<input type="text" name="naziv" value="{{.Artikal.Naziv}}"
|
||||
placeholder="npr. RAM DDR4 8GB Kingston"
|
||||
style="width:100%;">
|
||||
</div>
|
||||
|
||||
<!-- naziv / jedinica mere / kategorija — u 1 redu -->
|
||||
<!-- Magacin je isključivo za proizvode (prate lager); usluge i troškovi
|
||||
imaju svoje module, zato ovde tip više nije izbor -->
|
||||
<input type="hidden" name="tip" value="proizvod">
|
||||
<!-- jedinica mere -->
|
||||
<div>
|
||||
<label class="polje-labela">Jedinica mere</label>
|
||||
{{$jm := .Artikal.JedinicaMere}}
|
||||
<select name="jedinica_mere" style="width:100%;">
|
||||
<option value="kom" {{if eq $jm "kom"}}selected{{end}}>kom</option>
|
||||
<option value="sat" {{if eq $jm "sat"}}selected{{end}}>sat</option>
|
||||
<option value="set" {{if eq $jm "set"}}selected{{end}}>set</option>
|
||||
<option value="m" {{if eq $jm "m"}}selected{{end}}>m</option>
|
||||
<option value="m2" {{if eq $jm "m2"}}selected{{end}}>m²</option>
|
||||
<option value="l" {{if eq $jm "l"}}selected{{end}}>l</option>
|
||||
<option value="kg" {{if eq $jm "kg"}}selected{{end}}>kg</option>
|
||||
<option value="pak" {{if eq $jm "pak"}}selected{{end}}>pak</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- kategorija -->
|
||||
<div>
|
||||
<label class="polje-labela">Kategorija</label>
|
||||
<select name="kategorija_id" style="width:100%;">
|
||||
<option value="">— bez kategorije —</option>
|
||||
{{range .Kategorije}}
|
||||
<option value="{{.ID}}" {{if eq (printf "%d" .ID) $.KategorijaIDStr}}selected{{end}}>{{.Naziv}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
<div style="display:grid;grid-template-columns:1fr 110px 1fr;gap:12px;">
|
||||
<div>
|
||||
<label class="polje-labela">
|
||||
Naziv <span style="color:#dc2626;">*</span>
|
||||
</label>
|
||||
<input type="text" name="naziv" value="{{.Artikal.Naziv}}"
|
||||
placeholder="npr. RAM DDR4 8GB Kingston"
|
||||
style="width:100%;">
|
||||
</div>
|
||||
<div>
|
||||
<label class="polje-labela">Jed. mere</label>
|
||||
{{$jm := .Artikal.JedinicaMere}}
|
||||
<select name="jedinica_mere" style="width:100%;">
|
||||
<option value="kom" {{if eq $jm "kom"}}selected{{end}}>kom</option>
|
||||
<option value="sat" {{if eq $jm "sat"}}selected{{end}}>sat</option>
|
||||
<option value="set" {{if eq $jm "set"}}selected{{end}}>set</option>
|
||||
<option value="m" {{if eq $jm "m"}}selected{{end}}>m</option>
|
||||
<option value="m2" {{if eq $jm "m2"}}selected{{end}}>m²</option>
|
||||
<option value="l" {{if eq $jm "l"}}selected{{end}}>l</option>
|
||||
<option value="kg" {{if eq $jm "kg"}}selected{{end}}>kg</option>
|
||||
<option value="pak" {{if eq $jm "pak"}}selected{{end}}>pak</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="polje-labela">Kategorija</label>
|
||||
<select name="kategorija_id" style="width:100%;">
|
||||
<option value="">— bez kategorije —</option>
|
||||
{{range .Kategorije}}
|
||||
<option value="{{.ID}}" {{if eq (printf "%d" .ID) $.KategorijaIDStr}}selected{{end}}>{{.Naziv}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- opis -->
|
||||
@@ -103,15 +101,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||||
<!-- PDV stopa -->
|
||||
{{if index .Moduli "pdv"}}
|
||||
<div>
|
||||
<label class="polje-labela">PDV stopa</label>
|
||||
<select name="pdv_stopa" id="pdv-stopa"
|
||||
style="width:100%;padding:8px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-glavni);font-size:14px;">
|
||||
<option value="20" {{if eq .Artikal.PdvStopa 20.0}}selected{{end}}>Opšta stopa — 20%</option>
|
||||
<option value="10" {{if eq .Artikal.PdvStopa 10.0}}selected{{end}}>Posebna stopa — 10%</option>
|
||||
<option value="0" {{if eq .Artikal.PdvStopa 0.0}}selected{{end}}>Oslobođeno — 0%</option>
|
||||
</select>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- cene: nabavna / prodajna / sa PDV -->
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr{{if index .Moduli "pdv"}} 1fr{{end}};gap:12px;">
|
||||
<div>
|
||||
<label class="polje-labela">Nabavna cena (din)</label>
|
||||
<input type="number" name="nabavna_cena" value="{{.Artikal.NabavnaCena}}" min="0" step="0.01" style="width:100%;">
|
||||
<input type="number" id="nabavna-cena" name="nabavna_cena"
|
||||
value="{{printf "%.2f" .Artikal.NabavnaCena}}" min="0" step="0.01" style="width:100%;">
|
||||
</div>
|
||||
<div>
|
||||
<label class="polje-labela">Prodajna cena (din)</label>
|
||||
<input type="number" name="prodajna_cena" value="{{.Artikal.ProdajnaCena}}" min="0" step="0.01" style="width:100%;">
|
||||
<input type="number" id="prodajna-cena" name="prodajna_cena"
|
||||
value="{{printf "%.2f" .Artikal.ProdajnaCena}}" min="0" step="0.01" style="width:100%;">
|
||||
</div>
|
||||
{{if index .Moduli "pdv"}}
|
||||
<div>
|
||||
<label class="polje-labela">Cena sa PDV-om (din)</label>
|
||||
<input type="number" id="cena-sa-pdv" name="cena_sa_pdv"
|
||||
value="{{printf "%.2f" .Artikal.CenaSaPdv}}" min="0" step="0.01" style="width:100%;">
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- marža za kalkulaciju; prazno = nasleđuje maržu kategorije ili globalnu -->
|
||||
@@ -184,26 +205,66 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 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
|
||||
// dvosmerno povezivanje: nabavna + marža → prodajna, prodajna ↔ cena sa PDV
|
||||
var nabavna = document.getElementById('nabavna-cena');
|
||||
var prodajna = document.getElementById('prodajna-cena');
|
||||
var saPdv = document.getElementById('cena-sa-pdv');
|
||||
var pdvSel = document.getElementById('pdv-stopa');
|
||||
var marza = document.querySelector('[name="marza"]');
|
||||
|
||||
function pdv() { return parseFloat(pdvSel.value) || 0; }
|
||||
function br(el) { return parseFloat(el.value) || 0; }
|
||||
|
||||
// prodajna → cena sa PDV
|
||||
function izProdajne() {
|
||||
var n = broj(nabavna);
|
||||
if (n <= 0) return;
|
||||
prodajna.value = (n * (1 + broj(marza) / 100)).toFixed(2);
|
||||
var p = br(prodajna);
|
||||
if (p > 0) saPdv.value = (p * (1 + pdv() / 100)).toFixed(2);
|
||||
izMarze();
|
||||
}
|
||||
// cena sa PDV → prodajna
|
||||
function izSaPdv() {
|
||||
var s = br(saPdv);
|
||||
if (s > 0) prodajna.value = (s / (1 + pdv() / 100)).toFixed(2);
|
||||
izMarze();
|
||||
}
|
||||
// nabavna + marža → prodajna (i onda prodajna → sa PDV)
|
||||
function izMarzeKalc() {
|
||||
var n = br(nabavna), m = br(marza);
|
||||
if (n > 0 && marza.value !== '') {
|
||||
prodajna.value = (n * (1 + m / 100)).toFixed(2);
|
||||
izProdajne();
|
||||
}
|
||||
}
|
||||
// prodajna + nabavna → marža
|
||||
function izMarze() {
|
||||
var n = broj(nabavna), p = broj(prodajna);
|
||||
if (n <= 0) { marza.value = ''; return; }
|
||||
marza.value = ((p / n - 1) * 100).toFixed(2);
|
||||
var n = br(nabavna), p = br(prodajna);
|
||||
if (n > 0) marza.value = ((p / n - 1) * 100).toFixed(2);
|
||||
}
|
||||
if (nabavna && prodajna && marza) {
|
||||
marza.addEventListener('input', izProdajne);
|
||||
nabavna.addEventListener('input', izProdajne);
|
||||
prodajna.addEventListener('input', izMarze);
|
||||
// pri promeni PDV stope — preračunaj cena sa PDV iz prodajne
|
||||
function izPdvStope() {
|
||||
if (prodajna.value !== '') izProdajne();
|
||||
}
|
||||
|
||||
if (nabavna && prodajna && saPdv && marza && pdvSel) {
|
||||
prodajna.addEventListener('input', izProdajne);
|
||||
saPdv.addEventListener('input', izSaPdv);
|
||||
nabavna.addEventListener('input', izMarzeKalc);
|
||||
marza.addEventListener('input', izMarzeKalc);
|
||||
pdvSel.addEventListener('change', izPdvStope);
|
||||
} else if (nabavna && prodajna && marza) {
|
||||
// bez PDV modula — samo osnovna kalkulacija nabavna+marža↔prodajna
|
||||
marza.addEventListener('input', function() {
|
||||
var n = br(nabavna);
|
||||
if (n > 0) prodajna.value = (n * (1 + br(marza) / 100)).toFixed(2);
|
||||
});
|
||||
nabavna.addEventListener('input', function() {
|
||||
var n = br(nabavna);
|
||||
if (n > 0 && marza.value !== '') prodajna.value = (n * (1 + br(marza) / 100)).toFixed(2);
|
||||
});
|
||||
prodajna.addEventListener('input', function() {
|
||||
var n = br(nabavna);
|
||||
if (n > 0) marza.value = ((br(prodajna) / n - 1) * 100).toFixed(2);
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user