diff --git a/internal/db/sqlite/pdv_evidencija.go b/internal/db/sqlite/pdv_evidencija.go index 8f620c0..f6a7ace 100644 --- a/internal/db/sqlite/pdv_evidencija.go +++ b/internal/db/sqlite/pdv_evidencija.go @@ -171,7 +171,7 @@ func (r *PdvKprRepo) Lista(ctx context.Context, od, do time.Time) ([]model.PdvKp dobavljac_naziv, COALESCE(dobavljac_pib, ''), COALESCE(dobavljac_mesto, ''), osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna, pdv_bez_odbitka, osloboden_nabavka, ukupno, - COALESCE(napomena, ''), izvor, izvor_id, datum_unosa + COALESCE(napomena, ''), izvor, izvor_id, uvoz, datum_unosa FROM pdv_kpr WHERE 1=1` args := []any{} if !od.IsZero() { @@ -211,7 +211,7 @@ func (r *PdvKprRepo) DohvatiID(ctx context.Context, id int64) (*model.PdvKpr, er dobavljac_naziv, COALESCE(dobavljac_pib, ''), COALESCE(dobavljac_mesto, ''), osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna, pdv_bez_odbitka, osloboden_nabavka, ukupno, - COALESCE(napomena, ''), izvor, izvor_id, datum_unosa + COALESCE(napomena, ''), izvor, izvor_id, uvoz, datum_unosa FROM pdv_kpr WHERE id = ?`, id) k, err := skenirajKpr(red.Scan) if err != nil { @@ -225,12 +225,13 @@ func skenirajKpr(scan func(...any) error) (model.PdvKpr, error) { var k model.PdvKpr var datumPlacanja sql.NullTime var izvorID sql.NullInt64 + var uvoz int64 if err := scan( &k.ID, &k.DatumPrometa, &k.DatumKnjizenja, &datumPlacanja, &k.BrojDokumenta, &k.DobavljacNaziv, &k.DobavljacPib, &k.DobavljacMesto, &k.OsnovicaOpsta, &k.PdvOpsta, &k.OsnovicaPosebna, &k.PdvPosebna, &k.PdvBezOdbitka, &k.OslobodenNabavka, &k.Ukupno, - &k.Napomena, &k.Izvor, &izvorID, &k.DatumUnosa, + &k.Napomena, &k.Izvor, &izvorID, &uvoz, &k.DatumUnosa, ); err != nil { return model.PdvKpr{}, err } @@ -240,6 +241,7 @@ func skenirajKpr(scan func(...any) error) (model.PdvKpr, error) { if izvorID.Valid { k.IzvorID = &izvorID.Int64 } + k.Uvoz = uvoz != 0 return k, nil } @@ -249,18 +251,22 @@ func (r *PdvKprRepo) Kreiraj(ctx context.Context, k *model.PdvKpr) (int64, error if k.DatumPlacanja != nil { datumPlacanja = *k.DatumPlacanja } + uvoz := 0 + if k.Uvoz { + uvoz = 1 + } rez, err := r.db.ExecContext(ctx, ` INSERT INTO pdv_kpr ( datum_prometa, datum_knjizenja, datum_placanja, broj_dokumenta, dobavljac_naziv, dobavljac_pib, dobavljac_mesto, osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna, - pdv_bez_odbitka, osloboden_nabavka, ukupno, napomena, izvor, izvor_id - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + pdv_bez_odbitka, osloboden_nabavka, ukupno, napomena, izvor, izvor_id, uvoz + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, k.DatumPrometa, k.DatumKnjizenja, datumPlacanja, k.BrojDokumenta, k.DobavljacNaziv, k.DobavljacPib, k.DobavljacMesto, k.OsnovicaOpsta, k.PdvOpsta, k.OsnovicaPosebna, k.PdvPosebna, k.PdvBezOdbitka, k.OslobodenNabavka, k.Ukupno, k.Napomena, - izvorIliRucno(k.Izvor), izvorIDArg(k.IzvorID)) + izvorIliRucno(k.Izvor), izvorIDArg(k.IzvorID), uvoz) if err != nil { return 0, fmt.Errorf("ntech: PdvKprRepo.Kreiraj: %w", err) } diff --git a/internal/handler/pdv_kpr.go b/internal/handler/pdv_kpr.go index 0a79452..9a17c97 100644 --- a/internal/handler/pdv_kpr.go +++ b/internal/handler/pdv_kpr.go @@ -128,6 +128,7 @@ func (h *Handler) SacuvajPdvKpr(w http.ResponseWriter, r *http.Request) { PdvBezOdbitka: parsiraIznos(r.FormValue("pdv_bez_odbitka")), OslobodenNabavka: parsiraIznos(r.FormValue("osloboden_nabavka")), Napomena: strings.TrimSpace(r.FormValue("napomena")), + Uvoz: r.FormValue("uvoz") == "1", } // datum plaćanja je opcionalan if dp := parsiraDatumOpcionalno(r.FormValue("datum_placanja")); !dp.IsZero() { diff --git a/internal/handler/pdv_obracun.go b/internal/handler/pdv_obracun.go index f7882ab..379fd91 100644 --- a/internal/handler/pdv_obracun.go +++ b/internal/handler/pdv_obracun.go @@ -59,6 +59,19 @@ func (h *Handler) PdvObracunStranica(w http.ResponseWriter, r *http.Request) { kirSume := model.SumirajKir(kirZapisi) kprSume := model.SumirajKpr(kprZapisi) + // razdvajamo KPR na domaće i uvozne — uvoz se u PPPDV mapira u polja 006/106, + // domaća nabavka u 008/108; obračun obaveze ostaje na ukupnom KPR-u (uvozni PDV je odbitni) + var kprDomaci, kprUvozni []model.PdvKpr + for _, z := range kprZapisi { + if z.Uvoz { + kprUvozni = append(kprUvozni, z) + } else { + kprDomaci = append(kprDomaci, z) + } + } + kprDomaceSume := model.SumirajKpr(kprDomaci) + kprUvozSume := model.SumirajKpr(kprUvozni) + ps := h.popuniPodaciStranice(r, podesavanja) ps.Stranica = "pdv-obracun" ps.NaslovStranice = "PDV obračun" @@ -69,6 +82,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), + PPPDV: model.MapirajPPPDV(kirSume, kprDomaceSume, kprUvozSume), }) } diff --git a/internal/model/pdv_evidencija.go b/internal/model/pdv_evidencija.go index 08252e3..0813593 100644 --- a/internal/model/pdv_evidencija.go +++ b/internal/model/pdv_evidencija.go @@ -138,6 +138,7 @@ type PdvKpr struct { Napomena string Izvor string // "rucno" | "prodaja" | "nabavka" IzvorID *int64 // id izvorne nabavke (nil za ručni unos) + Uvoz bool // true = uvoz (JCI) → PPPDV 006/106; false = domaća nabavka → 008/108 DatumUnosa time.Time } @@ -287,9 +288,10 @@ func (p PPPDV) Polje110Apsolutno() int64 { } // 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 { +// KPR je razdvojen: uvoz (JCI) ide u polja 006/106 (prethodni porez pri uvozu), +// domaća nabavka u 008/108 (ostali prethodni porez). +// ⚠ PDV nadoknada poljoprivredniku (007/107) se i dalje ne prati zasebno (ostaje 0). +func MapirajPPPDV(kir PdvKirSume, kprDomace, kprUvoz PdvKprSume) PPPDV { uDinare := func(v float64) int64 { return int64(math.Round(v)) } p := PPPDV{ Polje001: uDinare(kir.OslobodenSaPravom), @@ -298,8 +300,10 @@ func MapirajPPPDV(kir PdvKirSume, kpr PdvKprSume) PPPDV { Polje103: uDinare(kir.PdvOpsta), Polje004: uDinare(kir.OsnovicaPosebna), Polje104: uDinare(kir.PdvPosebna), - Polje008: uDinare(kpr.OsnovicaOpsta + kpr.OsnovicaPosebna), - Polje108: uDinare(kpr.PdvOpsta + kpr.PdvPosebna), + Polje006: uDinare(kprUvoz.OsnovicaOpsta + kprUvoz.OsnovicaPosebna), + Polje106: uDinare(kprUvoz.PdvOpsta + kprUvoz.PdvPosebna), + Polje008: uDinare(kprDomace.OsnovicaOpsta + kprDomace.OsnovicaPosebna), + Polje108: uDinare(kprDomace.PdvOpsta + kprDomace.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 diff --git a/internal/model/pdv_evidencija_test.go b/internal/model/pdv_evidencija_test.go index 3e7feef..a206764 100644 --- a/internal/model/pdv_evidencija_test.go +++ b/internal/model/pdv_evidencija_test.go @@ -108,7 +108,7 @@ func TestMapirajPPPDV(t *testing.T) { PdvBezOdbitka: 25, // ne sme da uđe nigde u PPPDV } - p := MapirajPPPDV(kir, kpr) + p := MapirajPPPDV(kir, kpr, PdvKprSume{}) 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) @@ -120,7 +120,7 @@ func TestMapirajPPPDV(t *testing.T) { 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 + // u ovom scenariju nema uvoza (prazan kprUvoz) ni poljoprivrednika → 006/106/007/107 = 0 if p.Polje006 != 0 || p.Polje106 != 0 || p.Polje007 != 0 || p.Polje107 != 0 { t.Errorf("006/106/007/107 moraju biti 0") } @@ -137,8 +137,25 @@ func TestMapirajPPPDV(t *testing.T) { } // scenario povraćaja: izlazni < odbitni → 110 negativan, Povracaj=true - p2 := MapirajPPPDV(PdvKirSume{PdvOpsta: 30}, PdvKprSume{PdvOpsta: 90}) + p2 := MapirajPPPDV(PdvKirSume{PdvOpsta: 30}, PdvKprSume{PdvOpsta: 90}, PdvKprSume{}) 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()) } + + // scenario uvoza: uvozni KPR ide u 006/106, domaći u 008/108 + p3 := MapirajPPPDV( + PdvKirSume{}, + PdvKprSume{OsnovicaOpsta: 1000, PdvOpsta: 200}, // domaće → 008/108 + PdvKprSume{OsnovicaOpsta: 500, PdvOpsta: 100, OsnovicaPosebna: 50, PdvPosebna: 5}, // uvoz → 006/106 + ) + if p3.Polje006 != 550 || p3.Polje106 != 105 { + t.Errorf("uvoz 006/106 = %d/%d, očekivano 550/105", p3.Polje006, p3.Polje106) + } + if p3.Polje008 != 1000 || p3.Polje108 != 200 { + t.Errorf("domaće 008/108 = %d/%d, očekivano 1000/200", p3.Polje008, p3.Polje108) + } + // 009 = 006+007+008 = 550+0+1000 = 1550; 109 = 106+107+108 = 105+0+200 = 305 + if p3.Polje009 != 1550 || p3.Polje109 != 305 { + t.Errorf("zbir prethodnog 009/109 = %d/%d, očekivano 1550/305", p3.Polje009, p3.Polje109) + } } diff --git a/migrations/048_pdv_kpr_uvoz.sql b/migrations/048_pdv_kpr_uvoz.sql new file mode 100644 index 0000000..c62363a --- /dev/null +++ b/migrations/048_pdv_kpr_uvoz.sql @@ -0,0 +1,6 @@ +-- Zastavica „uvoz" na zapisima knjige primljenih računa (KPR). +-- Uvoz se unosi RUČNO sa JCI (osnovica i PDV iz carinskog dokumenta drže postojeće +-- kolone osnovica_opsta / pdv_opsta). Zastavica služi da se uvozni PDV u PPPDV obrascu +-- rutira u polja 006/106 (prethodni porez pri uvozu) umesto 008/108 (ostali prethodni). +-- 0 = domaća nabavka (podrazumevano), 1 = uvoz. +ALTER TABLE pdv_kpr ADD COLUMN uvoz INTEGER NOT NULL DEFAULT 0; diff --git a/web/templates/stranice/pdv_kpr.html b/web/templates/stranice/pdv_kpr.html index 09725b7..714ad64 100644 --- a/web/templates/stranice/pdv_kpr.html +++ b/web/templates/stranice/pdv_kpr.html @@ -47,7 +47,7 @@ {{range .Zapisi}}