e1ee5c3765
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.
267 lines
7.9 KiB
Go
267 lines
7.9 KiB
Go
package model
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
)
|
||
|
||
// PdvKir je jedan zapis u knjizi izdatih računa (izlazni PDV).
|
||
// Iznosi se vode po vrsti stope (opšta/posebna) — vidi migraciju 041.
|
||
type PdvKir struct {
|
||
ID int64
|
||
DatumPrometa time.Time
|
||
DatumKnjizenja time.Time
|
||
BrojDokumenta string
|
||
KupacNaziv string
|
||
KupacPib string
|
||
KupacMesto string
|
||
OsnovicaOpsta float64
|
||
PdvOpsta float64
|
||
OsnovicaPosebna float64
|
||
PdvPosebna float64
|
||
OslobodenSaPravom float64
|
||
OslobodenBezPrava float64
|
||
Ukupno float64
|
||
Napomena string
|
||
Izvor string // "rucno" | "prodaja" | "nabavka"
|
||
IzvorID *int64 // id izvornog naloga (nil za ručni unos)
|
||
DatumUnosa time.Time
|
||
}
|
||
|
||
// OslobodenUkupno vraća zbir oslobođenog prometa (sa i bez prava na odbitak).
|
||
func (k PdvKir) OslobodenUkupno() float64 {
|
||
return k.OslobodenSaPravom + k.OslobodenBezPrava
|
||
}
|
||
|
||
// OznakaPoreskogBroja vraća „JMBG" ako uneti broj ima 13 cifara (fizičko lice),
|
||
// inače „PIB" (pravno lice / preduzetnik — PIB ima 9 cifara).
|
||
func (k PdvKir) OznakaPoreskogBroja() string {
|
||
cifre := 0
|
||
for _, r := range k.KupacPib {
|
||
if r >= '0' && r <= '9' {
|
||
cifre++
|
||
}
|
||
}
|
||
if cifre == 13 {
|
||
return "JMBG"
|
||
}
|
||
return "PIB"
|
||
}
|
||
|
||
// PdvKirSume su zbirovi kolona KIR-a (za red „ukupno" u pregledu knjige).
|
||
type PdvKirSume struct {
|
||
OsnovicaOpsta float64
|
||
PdvOpsta float64
|
||
OsnovicaPosebna float64
|
||
PdvPosebna float64
|
||
OslobodenSaPravom float64
|
||
OslobodenBezPrava float64
|
||
Ukupno float64
|
||
}
|
||
|
||
// OslobodenUkupno vraća zbir oslobođenog prometa (sa i bez prava na odbitak).
|
||
func (s PdvKirSume) OslobodenUkupno() float64 {
|
||
return s.OslobodenSaPravom + s.OslobodenBezPrava
|
||
}
|
||
|
||
// SumirajKir sabira sve kolone iz liste KIR zapisa.
|
||
func SumirajKir(zapisi []PdvKir) PdvKirSume {
|
||
var s PdvKirSume
|
||
for _, z := range zapisi {
|
||
s.OsnovicaOpsta += z.OsnovicaOpsta
|
||
s.PdvOpsta += z.PdvOpsta
|
||
s.OsnovicaPosebna += z.OsnovicaPosebna
|
||
s.PdvPosebna += z.PdvPosebna
|
||
s.OslobodenSaPravom += z.OslobodenSaPravom
|
||
s.OslobodenBezPrava += z.OslobodenBezPrava
|
||
s.Ukupno += z.Ukupno
|
||
}
|
||
return s
|
||
}
|
||
|
||
// KirIzProdaje gradi KIR zapis iz prodaje: stavke se grupišu po PDV stopi
|
||
// (20→opšta, 10→posebna, ostalo→oslobođeno). CenaPoKomadu je prodajna cena SA PDV,
|
||
// pa se osnovica izvodi deljenjem sa (1 + stopa/100).
|
||
func KirIzProdaje(nalog ProdajniNalog, stavke []StavkaProdaje, kupacNaziv, kupacPib, kupacMesto string) PdvKir {
|
||
id := nalog.ID
|
||
k := PdvKir{
|
||
DatumPrometa: nalog.Datum,
|
||
DatumKnjizenja: nalog.Datum,
|
||
BrojDokumenta: nalog.BrojNaloga,
|
||
KupacNaziv: kupacNaziv,
|
||
KupacPib: kupacPib,
|
||
KupacMesto: kupacMesto,
|
||
Izvor: "prodaja",
|
||
IzvorID: &id,
|
||
}
|
||
for _, s := range stavke {
|
||
ukupnoLinija := float64(s.Kolicina) * s.CenaPoKomadu
|
||
osnovica := ukupnoLinija
|
||
if s.PdvStopa > 0 {
|
||
osnovica = ukupnoLinija / (1 + s.PdvStopa/100)
|
||
}
|
||
pdv := ukupnoLinija - osnovica
|
||
switch s.PdvStopa {
|
||
case 20:
|
||
k.OsnovicaOpsta += osnovica
|
||
k.PdvOpsta += pdv
|
||
case 10:
|
||
k.OsnovicaPosebna += osnovica
|
||
k.PdvPosebna += pdv
|
||
default:
|
||
// 0% / oslobođeno — osnovica bez PDV-a u oslobođen promet sa pravom na odbitak
|
||
k.OslobodenSaPravom += osnovica
|
||
}
|
||
k.Ukupno += ukupnoLinija
|
||
}
|
||
return k
|
||
}
|
||
|
||
// PdvKpr je jedan zapis u knjizi primljenih računa (ulazni PDV).
|
||
type PdvKpr struct {
|
||
ID int64
|
||
DatumPrometa time.Time
|
||
DatumKnjizenja time.Time
|
||
DatumPlacanja *time.Time // može biti prazan
|
||
BrojDokumenta string
|
||
DobavljacNaziv string
|
||
DobavljacPib string
|
||
DobavljacMesto string
|
||
OsnovicaOpsta float64
|
||
PdvOpsta float64
|
||
OsnovicaPosebna float64
|
||
PdvPosebna float64
|
||
PdvBezOdbitka float64
|
||
OslobodenNabavka float64
|
||
Ukupno float64
|
||
Napomena string
|
||
Izvor string // "rucno" | "prodaja" | "nabavka"
|
||
IzvorID *int64 // id izvorne nabavke (nil za ručni unos)
|
||
DatumUnosa time.Time
|
||
}
|
||
|
||
// OznakaPoreskogBroja vraća „JMBG" za 13-cifreni broj, inače „PIB" (dobavljači su obično firme).
|
||
func (k PdvKpr) OznakaPoreskogBroja() string {
|
||
cifre := 0
|
||
for _, r := range k.DobavljacPib {
|
||
if r >= '0' && r <= '9' {
|
||
cifre++
|
||
}
|
||
}
|
||
if cifre == 13 {
|
||
return "JMBG"
|
||
}
|
||
return "PIB"
|
||
}
|
||
|
||
// PdvKprSume su zbirovi kolona KPR-a (za red „ukupno" u pregledu knjige).
|
||
type PdvKprSume struct {
|
||
OsnovicaOpsta float64
|
||
PdvOpsta float64
|
||
OsnovicaPosebna float64
|
||
PdvPosebna float64
|
||
PdvBezOdbitka float64
|
||
OslobodenNabavka float64
|
||
Ukupno float64
|
||
}
|
||
|
||
// SumirajKpr sabira sve kolone iz liste KPR zapisa.
|
||
func SumirajKpr(zapisi []PdvKpr) PdvKprSume {
|
||
var s PdvKprSume
|
||
for _, z := range zapisi {
|
||
s.OsnovicaOpsta += z.OsnovicaOpsta
|
||
s.PdvOpsta += z.PdvOpsta
|
||
s.OsnovicaPosebna += z.OsnovicaPosebna
|
||
s.PdvPosebna += z.PdvPosebna
|
||
s.PdvBezOdbitka += z.PdvBezOdbitka
|
||
s.OslobodenNabavka += z.OslobodenNabavka
|
||
s.Ukupno += z.Ukupno
|
||
}
|
||
return s
|
||
}
|
||
|
||
// NabavkaStavkaPdv je jedna stavka nabavke sa pripadajućom PDV stopom (iz artikla).
|
||
type NabavkaStavkaPdv struct {
|
||
Osnovica float64 // nabavna vrednost stavke (cena × količina) — tretira se kao osnovica bez PDV
|
||
PdvStopa float64
|
||
}
|
||
|
||
// KprIzNabavke gradi KPR zapis iz nabavke. PDV se izvodi iz stope artikla po stavci
|
||
// (⚠ aproksimacija: nabavna cena = osnovica bez PDV; stvaran PDV sa računa dobavljača može
|
||
// se razlikovati). Grupiše po stopi (20→opšta, 10→posebna, ostalo→oslobođena nabavka).
|
||
// Broj dokumenta je sintetički ("NAB-<id>") jer nabavka ne čuva broj računa dobavljača.
|
||
func KprIzNabavke(nabavka Nabavka, dobavljacNaziv, dobavljacPib, dobavljacMesto string, stavke []NabavkaStavkaPdv) PdvKpr {
|
||
id := nabavka.ID
|
||
k := PdvKpr{
|
||
DatumPrometa: nabavka.Datum,
|
||
DatumKnjizenja: nabavka.Datum,
|
||
BrojDokumenta: fmt.Sprintf("NAB-%d", id),
|
||
DobavljacNaziv: dobavljacNaziv,
|
||
DobavljacPib: dobavljacPib,
|
||
DobavljacMesto: dobavljacMesto,
|
||
Napomena: nabavka.Napomena,
|
||
Izvor: "nabavka",
|
||
IzvorID: &id,
|
||
}
|
||
for _, s := range stavke {
|
||
pdv := s.Osnovica * s.PdvStopa / 100
|
||
switch s.PdvStopa {
|
||
case 20:
|
||
k.OsnovicaOpsta += s.Osnovica
|
||
k.PdvOpsta += pdv
|
||
case 10:
|
||
k.OsnovicaPosebna += s.Osnovica
|
||
k.PdvPosebna += pdv
|
||
default:
|
||
k.OslobodenNabavka += s.Osnovica
|
||
}
|
||
k.Ukupno += s.Osnovica + pdv
|
||
}
|
||
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
|
||
}
|