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:
@@ -28,6 +28,7 @@ var saSidebar = []string{
|
||||
"pdv_stope",
|
||||
"pdv_kir", "pdv_kir_forma",
|
||||
"pdv_kpr", "pdv_kpr_forma",
|
||||
"pdv_obracun",
|
||||
"podsetnici", "podsetnik_forma",
|
||||
"profil_tema",
|
||||
"prodaja", "prodaja_detalji", "prodaja_forma",
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"ntech/internal/db/sqlite"
|
||||
"ntech/internal/model"
|
||||
)
|
||||
|
||||
// PodaciPdvObracun su podaci za stranicu obračuna PDV za period
|
||||
type PodaciPdvObracun struct {
|
||||
model.PodaciStranice
|
||||
Od string
|
||||
Do string
|
||||
KirSume model.PdvKirSume
|
||||
KprSume model.PdvKprSume
|
||||
Obracun model.PdvObracun
|
||||
}
|
||||
|
||||
// PdvObracunStranica računa obavezu PDV za izabrani period.
|
||||
// Kada period nije zadat, podrazumevano se uzima tekući mesec.
|
||||
func (h *Handler) PdvObracunStranica(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "pdv.pregled"); !ok {
|
||||
return
|
||||
}
|
||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
odStr := r.URL.Query().Get("od")
|
||||
doStr := r.URL.Query().Get("do")
|
||||
// podrazumevani period: tekući mesec (od prvog do poslednjeg dana)
|
||||
if odStr == "" && doStr == "" {
|
||||
sada := time.Now()
|
||||
prvi := time.Date(sada.Year(), sada.Month(), 1, 0, 0, 0, 0, sada.Location())
|
||||
poslednji := prvi.AddDate(0, 1, -1)
|
||||
odStr = prvi.Format("2006-01-02")
|
||||
doStr = poslednji.Format("2006-01-02")
|
||||
}
|
||||
|
||||
od := parsiraDatumOpcionalno(odStr)
|
||||
do := parsiraDatumOpcionalno(doStr)
|
||||
|
||||
kirZapisi, err := h.PdvKirRepo.Lista(r.Context(), od, do)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju knjige izdatih računa", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
kprZapisi, err := h.PdvKprRepo.Lista(r.Context(), od, do)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju knjige primljenih računa", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
kirSume := model.SumirajKir(kirZapisi)
|
||||
kprSume := model.SumirajKpr(kprZapisi)
|
||||
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "pdv-obracun"
|
||||
ps.NaslovStranice = "PDV obračun"
|
||||
h.renderujTemplate(w, "pdv_obracun", PodaciPdvObracun{
|
||||
PodaciStranice: ps,
|
||||
Od: odStr,
|
||||
Do: doStr,
|
||||
KirSume: kirSume,
|
||||
KprSume: kprSume,
|
||||
Obracun: model.ObracunajPdv(kirSume, kprSume),
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user