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:
2026-06-26 23:47:52 +02:00
parent 97fd4490ea
commit 84bb8aa158
8 changed files with 176 additions and 78 deletions
+10 -3
View File
@@ -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}}
+115 -54
View File
@@ -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}}></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}}></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>