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-") 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 }