- internal/be: emulator kartice (TCP listener za L-PFR mock)
- /v/: javna stranica za verifikaciju fiskalnog računa (QR kod)
- /servis/{id}/fiskalni-racun: štampa fiskalnog računa
- Podešavanja fiskalizacije: BE status/reset, proširen UI
- Korisnik: polja ime i prezime (model, DB, handler, admin profil)
- Fisk server: ažuriran receipt.py i server.py
- Koncept.md, Magacin.md: dokumentacija
11 KiB
11 KiB
Magacin — Plan izmena: Cena sa PDV-om
Dodajemo polje Cena sa PDV-om i PDV dropdown u forme za Novi artikal i Izmeni artikal.
Trenutno stanje
| Kolona u DB | Šta čuva | Default |
|---|---|---|
prodajna_cena |
Prodajna cena (neto, bez PDV-a) | 0 |
pdv_stopa |
PDV stopa u % | 20.0 |
cena_sa_pdv |
ne postoji | — |
Model metode (internal/model/artikal.go, linije 41–47):
// PAŽNJA: ove metode tretiraju ProdajnaCenu kao BRUTO — ne odgovaraju
// korisnikovom modelu gde je prodajna_cena NETO.
func (a Artikal) CenaBezPdv() float64 { return a.ProdajnaCena / (1 + a.PdvStopa/100) }
func (a Artikal) PdvIznos() float64 { return a.ProdajnaCena - a.CenaBezPdv() }
Ove metode treba ispraviti posle ove izmene (videti §6).
Šta dodajemo
| Novo | Opis |
|---|---|
Kolona cena_sa_pdv |
Prodajna cena sa PDV-om (bruto). Čuva se u bazi radi brzog prikaza. |
Dropdown pdv_stopa |
Izbor PDV stope u formi. Default: Opšta stopa 20%. |
| JS dvosmerna kalkulacija | Prodajna ↔ sa PDV (automatsko popunjavanje u zavisnosti od toga šta je uneto) |
Formula:
cena_sa_pdv = prodajna_cena * (1 + pdv_stopa / 100)
prodajna_cena = cena_sa_pdv / (1 + pdv_stopa / 100)
Izmene po fajlovima
1. migrations/087_artikal_cena_sa_pdv.sql — NOVI FAJL
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);
Drugi red popunjava sve postojeće artikle — svaka prodajna cena se konvertuje u bruto.
2. internal/model/artikal.go
Linija 26 — posle ProdajnaCena float64 dodati novo polje:
// DODATI:
CenaSaPdv float64 // prodajna cena sa PDV-om (bruto); = ProdajnaCena * (1 + PdvStopa/100)
Linije 40–47 — ispraviti metode:
// STARO:
func (a Artikal) CenaBezPdv() float64 {
return a.ProdajnaCena / (1 + a.PdvStopa/100)
}
func (a Artikal) PdvIznos() float64 {
return a.ProdajnaCena - a.CenaBezPdv()
}
// NOVO:
func (a Artikal) CenaBezPdv() float64 {
return a.ProdajnaCena // prodajna_cena je već neto
}
func (a Artikal) PdvIznos() float64 {
return a.CenaSaPdv - a.ProdajnaCena
}
3. internal/db/sqlite/artikal.go
3a. SELECT u Lista (linija ~33) — dodati cena_sa_pdv na kraj SELECT-a
-- STARO (kraj SELECT-a):
a.nabavna_cena, a.prodajna_cena, a.pdv_stopa, a.marza, a.napomena,
-- NOVO:
a.nabavna_cena, a.prodajna_cena, a.pdv_stopa, a.cena_sa_pdv, a.marza, a.napomena,
3b. Scan u Lista (linija ~96) — dodati &a.CenaSaPdv iza &a.PdvStopa:
// STARO:
&a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &marza, &a.Napomena, &a.DatumUnosa, &arhiviran,
// NOVO:
&a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &a.CenaSaPdv, &marza, &a.Napomena, &a.DatumUnosa, &arhiviran,
3c. SELECT u DohvatiID (linija ~139–141) — isti princip:
-- STARO:
lokacija, nabavna_cena, prodajna_cena, pdv_stopa, marza, napomena, datum_unosa, arhiviran
-- NOVO:
lokacija, nabavna_cena, prodajna_cena, pdv_stopa, cena_sa_pdv, marza, napomena, datum_unosa, arhiviran
3d. Scan u DohvatiID (linija ~143):
// STARO:
&a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &marza, &a.Napomena, &a.DatumUnosa, &arhiviran,
// NOVO:
&a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &a.CenaSaPdv, &marza, &a.Napomena, &a.DatumUnosa, &arhiviran,
3e. INSERT u Kreiraj (linije ~179–182) — dodati cena_sa_pdv:
// STARO:
INSERT INTO artikli
(..., nabavna_cena, prodajna_cena, pdv_stopa, marza, napomena)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
...
a.Lokacija, a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.Marza, a.Napomena,
// NOVO:
INSERT INTO artikli
(..., nabavna_cena, prodajna_cena, pdv_stopa, cena_sa_pdv, marza, napomena)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
...
a.Lokacija, a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.CenaSaPdv, a.Marza, a.Napomena,
3f. UPDATE u Izmeni (linije ~210–214) — dodati cena_sa_pdv = ?:
// STARO:
UPDATE artikli SET
..., nabavna_cena = ?, prodajna_cena = ?, pdv_stopa = ?, marza = ?, napomena = ?
WHERE id = ?
...
a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.Marza, a.Napomena, a.ID,
// NOVO:
UPDATE artikli SET
..., nabavna_cena = ?, prodajna_cena = ?, pdv_stopa = ?, cena_sa_pdv = ?, marza = ?, napomena = ?
WHERE id = ?
...
a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.CenaSaPdv, a.Marza, a.Napomena, a.ID,
4. internal/handler/magacin_forma.go
4a. parseFormuArtikla — čitanje pdv_stopa iz forme (dodati iza linije 389, pre prodajna_cena):
// DODATI — čitanje PDV stope iz forme:
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
4b. parseFormuArtikla — kalkulacija CenaSaPdv (dodati na kraju, pre return artikal, ""):
// DODATI — izračunaj 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)
}
4c. Nivelacioni trag — linija ~278
Ovaj deo poredi artikal.ProdajnaCena - staraCena. Ništa ne menjamo — i dalje poredimo neto cene. Ostaje kako jeste.
5. web/templates/stranice/magacin_forma.html
5a. Blok cena (linije 106–122) — trenutno izgleda ovako:
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label class="polje-labela">Nabavna cena (din)</label>
<input type="number" name="nabavna_cena" ...>
</div>
<div>
<label class="polje-labela">Prodajna cena (din)</label>
<input type="number" name="prodajna_cena" ...>
</div>
</div>
<div>
<label class="polje-labela">Marža (%)</label>
<input type="number" name="marza" ...>
</div>
5b. ZAMENA — novi blok cena sa PDV dropdown-om i trećim poljem:
<!-- PDV stopa -->
<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>
<!-- cene: nabavna / prodajna / sa PDV -->
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;">
<div>
<label class="polje-labela">Nabavna cena (din)</label>
<input type="number" id="nabavna-cena" name="nabavna_cena"
value="{{.Artikal.NabavnaCena}}" min="0" step="0.01" style="width:100%;">
</div>
<div>
<label class="polje-labela">Prodajna cena (din)</label>
<input type="number" id="prodajna-cena" name="prodajna_cena"
value="{{.Artikal.ProdajnaCena}}" min="0" step="0.01" style="width:100%;">
</div>
<div>
<label class="polje-labela">Cena sa PDV-om (din)</label>
<input type="number" id="cena-sa-pdv" name="cena_sa_pdv"
value="{{.Artikal.CenaSaPdv}}" min="0" step="0.01" style="width:100%;">
</div>
</div>
<!-- marža ostaje ispod -->
<div>
<label class="polje-labela">Marža (%)</label>
<input type="number" name="marza"
value="{{if .Artikal.Marza}}{{.Artikal.Marza}}{{end}}"
min="0" step="0.01" style="width:100%;"
placeholder="prazno = po kategoriji / globalna">
</div>
5c. JavaScript blok (linije ~187–207) — dodati logiku za cena_sa_pdv i PDV stopa
// NOVI JS — zamenjuje stari blok kalkulacije cena:
(function() {
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 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 = br(nabavna), p = br(prodajna);
if (n > 0) marza.value = ((p / n - 1) * 100).toFixed(2);
}
// pri promeni PDV stope — preračunaj cena sa PDV iz prodajne
function izPdvStope() {
if (prodajna.value !== '') izProdajne();
}
prodajna.addEventListener('input', izProdajne);
saPdv.addEventListener('input', izSaPdv);
nabavna.addEventListener('input', izMarzeKalc);
marza.addEventListener('input', izMarzeKalc);
pdvSel.addEventListener('change', izPdvStope);
})();
6. Napomene — šta NE menjamo u ovoj fazi
| Fajl | Zašto ne diramo |
|---|---|
internal/handler/prodaja.go linija 74 |
POS i dalje koristi a.ProdajnaCena kao neto cenu; korisnik vidi neto pri prodaji. Ako treba prikazati bruto → posebna izmena. |
internal/fiskal/mapiranje.go linija 66 |
Koristi s.CenaBezPdv iz model.StavkaProdaje (ne iz model.Artikal) — to je poseban struct, nije zahvaćen. |
internal/db/sqlite/izvestaj.go |
Koristi ProdajnaCena za vrednost zaliha — neto ostaje logična osnova za to. |
magacin.html (lista) |
Prikazuje ProdajnaCena — može ostati neto, ili se doda kolona CenaSaPdv. Nije hitan. |
Redosled implementacije
- Migracija
087_artikal_cena_sa_pdv.sql— nova kolona + popunjavanje postojećih - Model
internal/model/artikal.go— novo polje + ispravka metoda - SQLite repo
internal/db/sqlite/artikal.go— SELECT, Scan, INSERT, UPDATE (4 mesta) - Handler parser
internal/handler/magacin_forma.go— čitanjepdv_stopa+ kalkulacijacena_sa_pdv - Šablon
web/templates/stranice/magacin_forma.html— PDV dropdown + treće polje + novi JS