From a562d4bb6f0941e5d6e00e561b4b17c01c0ec239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Sun, 14 Jun 2026 08:53:18 +0200 Subject: [PATCH] =?UTF-8?q?feat(pdv):=20PPPDV=20mapiranje=20na=20stranici?= =?UTF-8?q?=20obra=C4=8Duna?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit model.MapirajPPPDV preslikava zbirove KIR/KPR na polja zvaničnog obrasca PPPDV (001-005/103-105, 006-009/106-109, 110, povraćaj) u celim dinarima; zbirovi se računaju iz zaokruženih polja. Uvoz (006/106) i nadoknada poljoprivredniku (007/107) se ne prate → 0. Sekcija PPPDV dodata na /pdv/obracun. Prikaz za popunjavanje, ne elektronska predaja. --- internal/handler/pdv_obracun.go | 2 + internal/model/pdv_evidencija.go | 62 ++++++++++++++++ internal/model/pdv_evidencija_test.go | 47 ++++++++++++ web/templates/stranice/pdv_obracun.html | 96 +++++++++++++++++++++++++ 4 files changed, 207 insertions(+) diff --git a/internal/handler/pdv_obracun.go b/internal/handler/pdv_obracun.go index 269291b..f7882ab 100644 --- a/internal/handler/pdv_obracun.go +++ b/internal/handler/pdv_obracun.go @@ -16,6 +16,7 @@ type PodaciPdvObracun struct { KirSume model.PdvKirSume KprSume model.PdvKprSume Obracun model.PdvObracun + PPPDV model.PPPDV } // PdvObracunStranica računa obavezu PDV za izabrani period. @@ -68,5 +69,6 @@ func (h *Handler) PdvObracunStranica(w http.ResponseWriter, r *http.Request) { KirSume: kirSume, KprSume: kprSume, Obracun: model.ObracunajPdv(kirSume, kprSume), + PPPDV: model.MapirajPPPDV(kirSume, kprSume), }) } diff --git a/internal/model/pdv_evidencija.go b/internal/model/pdv_evidencija.go index 989e777..08252e3 100644 --- a/internal/model/pdv_evidencija.go +++ b/internal/model/pdv_evidencija.go @@ -2,6 +2,7 @@ package model import ( "fmt" + "math" "time" ) @@ -249,6 +250,67 @@ func (o PdvObracun) ObavezaApsolutna() float64 { return o.Obaveza } +// PPPDV su vrednosti polja zvaničnog obrasca poreske prijave PDV (PPPDV). +// Iznosi su u celim dinarima (obrazac ne dozvoljava decimale). Brojevi polja +// odgovaraju obrascu: 001–005/103–105 (promet i izlazni PDV), 006–009/106–109 +// (prethodni/odbitni porez), 110 (obaveza = 105 − 109), Povracaj (polje 11). +type PPPDV struct { + // I. promet dobara i usluga + Polje001 int64 // oslobođen sa pravom na odbitak (naknada) + Polje002 int64 // oslobođen bez prava na odbitak (naknada) + Polje003 int64 // opšta stopa — naknada bez PDV + Polje103 int64 // opšta stopa — obračunati PDV + Polje004 int64 // posebna stopa — naknada bez PDV + Polje104 int64 // posebna stopa — obračunati PDV + Polje005 int64 // zbir naknada (1+2+3+4) + Polje105 int64 // zbir izlaznog PDV (103+104) + // II. prethodni porez + Polje006 int64 // prethodni porez pri uvozu — naknada (ne prati se → 0) + Polje106 int64 // prethodni porez pri uvozu — PDV (ne prati se → 0) + Polje007 int64 // PDV nadoknada poljoprivredniku — naknada (ne prati se → 0) + Polje107 int64 // PDV nadoknada poljoprivredniku — PDV (ne prati se → 0) + Polje008 int64 // ostali prethodni porez — naknada + Polje108 int64 // ostali prethodni porez — PDV (odbitni) + Polje009 int64 // zbir naknada (6+7+8) + Polje109 int64 // zbir prethodnog poreza (106+107+108) + // III. poreska obaveza + Polje110 int64 // iznos PDV u periodu (105 − 109); >0 za uplatu, <0 za povraćaj + Povracaj bool // polje 11: true kada je 110 negativan (iznos za povraćaj) +} + +// Polje110Apsolutno vraća iznos polja 110 bez predznaka (za prikaz povraćaja). +func (p PPPDV) Polje110Apsolutno() int64 { + if p.Polje110 < 0 { + return -p.Polje110 + } + return p.Polje110 +} + +// MapirajPPPDV preslikava zbirove KIR i KPR na polja obrasca PPPDV (cele dinare). +// ⚠ Uvoz (006/106) i PDV nadoknada poljoprivredniku (007/107) se ne prate zasebno +// pa ostaju 0 — sav odbitni PDV iz KPR pada u polje 008/108. +func MapirajPPPDV(kir PdvKirSume, kpr PdvKprSume) PPPDV { + uDinare := func(v float64) int64 { return int64(math.Round(v)) } + p := PPPDV{ + Polje001: uDinare(kir.OslobodenSaPravom), + Polje002: uDinare(kir.OslobodenBezPrava), + Polje003: uDinare(kir.OsnovicaOpsta), + Polje103: uDinare(kir.PdvOpsta), + Polje004: uDinare(kir.OsnovicaPosebna), + Polje104: uDinare(kir.PdvPosebna), + Polje008: uDinare(kpr.OsnovicaOpsta + kpr.OsnovicaPosebna), + Polje108: uDinare(kpr.PdvOpsta + kpr.PdvPosebna), + } + // zbirovi se računaju iz zaokruženih polja da kolone na obrascu uvek zbiraju + p.Polje005 = p.Polje001 + p.Polje002 + p.Polje003 + p.Polje004 + p.Polje105 = p.Polje103 + p.Polje104 + p.Polje009 = p.Polje006 + p.Polje007 + p.Polje008 + p.Polje109 = p.Polje106 + p.Polje107 + p.Polje108 + p.Polje110 = p.Polje105 - p.Polje109 + p.Povracaj = p.Polje110 < 0 + return p +} + // 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. diff --git a/internal/model/pdv_evidencija_test.go b/internal/model/pdv_evidencija_test.go index 22a95d8..3e7feef 100644 --- a/internal/model/pdv_evidencija_test.go +++ b/internal/model/pdv_evidencija_test.go @@ -95,3 +95,50 @@ func TestObracunajPdv(t *testing.T) { t.Errorf("obaveza=%v ZaUplatu=%v, očekivano -60/false", o2.Obaveza, o2.ZaUplatu()) } } + +func TestMapirajPPPDV(t *testing.T) { + kir := PdvKirSume{ + OsnovicaOpsta: 1000.4, PdvOpsta: 200.08, // 003=1000, 103=200 + OsnovicaPosebna: 500.5, PdvPosebna: 50.05, // 004=501 (round), 104=50 + OslobodenSaPravom: 300.2, OslobodenBezPrava: 100.9, // 001=300, 002=101 + } + kpr := PdvKprSume{ + OsnovicaOpsta: 400, PdvOpsta: 80, + OsnovicaPosebna: 100, PdvPosebna: 10, + PdvBezOdbitka: 25, // ne sme da uđe nigde u PPPDV + } + + p := MapirajPPPDV(kir, kpr) + + if p.Polje001 != 300 || p.Polje002 != 101 || p.Polje003 != 1000 || p.Polje103 != 200 { + t.Errorf("I deo (001/002/003/103) = %d/%d/%d/%d", p.Polje001, p.Polje002, p.Polje003, p.Polje103) + } + if p.Polje004 != 501 || p.Polje104 != 50 { + t.Errorf("posebna 004/104 = %d/%d, očekivano 501/50", p.Polje004, p.Polje104) + } + // 005 = 300+101+1000+501 = 1902; 105 = 200+50 = 250 + if p.Polje005 != 1902 || p.Polje105 != 250 { + t.Errorf("zbir 005/105 = %d/%d, očekivano 1902/250", p.Polje005, p.Polje105) + } + // uvoz i poljoprivrednik se ne prate + if p.Polje006 != 0 || p.Polje106 != 0 || p.Polje007 != 0 || p.Polje107 != 0 { + t.Errorf("006/106/007/107 moraju biti 0") + } + // 008 = 400+100 = 500; 108 = 80+10 = 90 (PdvBezOdbitka 25 izostavljen) + if p.Polje008 != 500 || p.Polje108 != 90 { + t.Errorf("ostali prethodni 008/108 = %d/%d, očekivano 500/90", p.Polje008, p.Polje108) + } + if p.Polje009 != 500 || p.Polje109 != 90 { + t.Errorf("zbir prethodnog 009/109 = %d/%d, očekivano 500/90", p.Polje009, p.Polje109) + } + // 110 = 250 − 90 = 160 (za uplatu) + if p.Polje110 != 160 || p.Povracaj { + t.Errorf("110=%d Povracaj=%v, očekivano 160/false", p.Polje110, p.Povracaj) + } + + // scenario povraćaja: izlazni < odbitni → 110 negativan, Povracaj=true + p2 := MapirajPPPDV(PdvKirSume{PdvOpsta: 30}, PdvKprSume{PdvOpsta: 90}) + if p2.Polje110 != -60 || !p2.Povracaj || p2.Polje110Apsolutno() != 60 { + t.Errorf("povraćaj: 110=%d Povracaj=%v abs=%d, očekivano -60/true/60", p2.Polje110, p2.Povracaj, p2.Polje110Apsolutno()) + } +} diff --git a/web/templates/stranice/pdv_obracun.html b/web/templates/stranice/pdv_obracun.html index 9196a41..49da5de 100644 --- a/web/templates/stranice/pdv_obracun.html +++ b/web/templates/stranice/pdv_obracun.html @@ -100,5 +100,101 @@ + +
+
+
PPPDV — poreska prijava PDV
+
+ Prikaz polja obrasca za izabrani period, u celim dinarima. + ⚠ Uvoz (006/106) i PDV nadoknada poljoprivredniku (007/107) se ne vode zasebno u programu. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PoljeOpisNaknada bez PDVPDV
I. Promet dobara i usluga
001Oslobođen sa pravom na odbitak{{.PPPDV.Polje001}}
002Oslobođen bez prava na odbitak{{.PPPDV.Polje002}}
003
103
Promet po opštoj stopi (20%){{.PPPDV.Polje003}}{{.PPPDV.Polje103}}
004
104
Promet po posebnoj stopi (10%){{.PPPDV.Polje004}}{{.PPPDV.Polje104}}
005
105
ZBIR (1+2+3+4){{.PPPDV.Polje005}}{{.PPPDV.Polje105}}
II. Prethodni porez
006
106
Prethodni porez pri uvozu{{.PPPDV.Polje006}}{{.PPPDV.Polje106}}
007
107
PDV nadoknada poljoprivredniku{{.PPPDV.Polje007}}{{.PPPDV.Polje107}}
008
108
Ostali prethodni porez (domaće nabavke){{.PPPDV.Polje008}}{{.PPPDV.Polje108}}
009
109
ZBIR (6+7+8){{.PPPDV.Polje009}}{{.PPPDV.Polje109}}
110 + Iznos PDV u periodu (105 − 109) + {{if .PPPDV.Povracaj}} — za povraćaj{{else}} — za uplatu{{end}} + {{.PPPDV.Polje110Apsolutno}}
11Povraćaj: {{if .PPPDV.Povracaj}}DA{{else}}NE{{end}}
+
+
+ {{end}}