feat(pdv): obračun PDV za period (KIR − KPR)

Interni obračun: izlazni (dugovani) PDV iz KIR i odbitni (prethodni)
PDV iz KPR po stopama, konačna obaveza za uplatu ili povraćaj/prenos.
PdvBezOdbitka se ne računa u odbitni PDV. Stranica /pdv/obracun
(podrazumevano tekući mesec), link u sidebaru. Brojčana podloga za
budući zvanični PPPDV/POPDV obrazac.
This commit is contained in:
2026-06-14 08:34:33 +02:00
parent dfad0ff1f4
commit e1ee5c3765
7 changed files with 253 additions and 0 deletions
+45
View File
@@ -219,3 +219,48 @@ func KprIzNabavke(nabavka Nabavka, dobavljacNaziv, dobavljacPib, dobavljacMesto
}
return k
}
// PdvObracun je rezultat obračuna PDV za period: izlazni (dugovani) PDV iz KIR,
// prethodni (odbitni) PDV iz KPR i konačna obaveza. Pozitivna obaveza znači iznos
// za uplatu, negativna iznos za povraćaj / prenos poreskog kredita u naredni period.
type PdvObracun struct {
// izlazni (dugovani) PDV — iz KIR, po stopama
IzlazniPdvOpsta float64
IzlazniPdvPosebna float64
IzlazniPdvUkupno float64
// prethodni (odbitni) PDV — iz KPR, po stopama (bez PDV bez prava na odbitak)
OdbitniPdvOpsta float64
OdbitniPdvPosebna float64
OdbitniPdvUkupno float64
// obaveza = izlazni odbitni (>0 za uplatu, <0 za povraćaj/prenos)
Obaveza float64
}
// ZaUplatu vraća true kada postoji obaveza za uplatu (izlazni PDV veći od odbitnog).
func (o PdvObracun) ZaUplatu() bool {
return o.Obaveza > 0
}
// ObavezaApsolutna vraća iznos obaveze bez predznaka (za prikaz povraćaja kao pozitivan broj).
func (o PdvObracun) ObavezaApsolutna() float64 {
if o.Obaveza < 0 {
return -o.Obaveza
}
return o.Obaveza
}
// ObracunajPdv računa obavezu PDV iz zbirova KIR i KPR za isti period.
// PdvBezOdbitka iz KPR se namerno NE računa u odbitni PDV — to je PDV za koji
// ne postoji pravo na odbitak prethodnog poreza.
func ObracunajPdv(kir PdvKirSume, kpr PdvKprSume) PdvObracun {
o := PdvObracun{
IzlazniPdvOpsta: kir.PdvOpsta,
IzlazniPdvPosebna: kir.PdvPosebna,
OdbitniPdvOpsta: kpr.PdvOpsta,
OdbitniPdvPosebna: kpr.PdvPosebna,
}
o.IzlazniPdvUkupno = o.IzlazniPdvOpsta + o.IzlazniPdvPosebna
o.OdbitniPdvUkupno = o.OdbitniPdvOpsta + o.OdbitniPdvPosebna
o.Obaveza = o.IzlazniPdvUkupno - o.OdbitniPdvUkupno
return o
}
+25
View File
@@ -70,3 +70,28 @@ func TestKprIzNabavke(t *testing.T) {
t.Errorf("ukupno=%v, očekivano 400 (240+110+50)", k.Ukupno)
}
}
func TestObracunajPdv(t *testing.T) {
// izlazni: 200 (opšta) + 50 (posebna) = 250; odbitni: 80 + 20 = 100; obaveza = 150
kir := PdvKirSume{PdvOpsta: 200, PdvPosebna: 50}
kpr := PdvKprSume{PdvOpsta: 80, PdvPosebna: 20, PdvBezOdbitka: 30}
o := ObracunajPdv(kir, kpr)
if !blizu(o.IzlazniPdvUkupno, 250) {
t.Errorf("izlazni=%v, očekivano 250", o.IzlazniPdvUkupno)
}
// PdvBezOdbitka (30) ne sme da uđe u odbitni PDV
if !blizu(o.OdbitniPdvUkupno, 100) {
t.Errorf("odbitni=%v, očekivano 100 (bez PdvBezOdbitka)", o.OdbitniPdvUkupno)
}
if !blizu(o.Obaveza, 150) || !o.ZaUplatu() {
t.Errorf("obaveza=%v ZaUplatu=%v, očekivano 150/true", o.Obaveza, o.ZaUplatu())
}
// kada je odbitni veći od izlaznog → negativna obaveza (povraćaj/prenos), ZaUplatu=false
o2 := ObracunajPdv(PdvKirSume{PdvOpsta: 40}, PdvKprSume{PdvOpsta: 100})
if !blizu(o2.Obaveza, -60) || o2.ZaUplatu() {
t.Errorf("obaveza=%v ZaUplatu=%v, očekivano -60/false", o2.Obaveza, o2.ZaUplatu())
}
}