From 26c829fef3f42dd5f1f9c0aa1f298fd3c070e1a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Sat, 13 Jun 2026 21:22:48 +0200 Subject: [PATCH] =?UTF-8?q?feat(pdv):=20KIR/KPR=20evidencija=20=E2=80=94?= =?UTF-8?q?=20migracija,=20model=20i=20repozitorijum=20(Faza=202a)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tabele pdv_kir i pdv_kpr (iznosi po vrsti stope), modeli PdvKir/PdvKpr, repozitorijumi sa filterom perioda i integracioni test (datum round-trip, nullable datum plaćanja). --- internal/db/repository.go | 16 ++ internal/db/sqlite/pdv_evidencija.go | 243 ++++++++++++++++++++++ internal/db/sqlite/pdv_evidencija_test.go | 129 ++++++++++++ internal/handler/handler.go | 6 + internal/model/pdv_evidencija.go | 45 ++++ migrations/041_pdv_evidencija.sql | 46 ++++ 6 files changed, 485 insertions(+) create mode 100644 internal/db/sqlite/pdv_evidencija.go create mode 100644 internal/db/sqlite/pdv_evidencija_test.go create mode 100644 internal/model/pdv_evidencija.go create mode 100644 migrations/041_pdv_evidencija.sql diff --git a/internal/db/repository.go b/internal/db/repository.go index b7ed66a..0cfc3e1 100644 --- a/internal/db/repository.go +++ b/internal/db/repository.go @@ -32,6 +32,22 @@ type PdvStopaRepository interface { PostaviAktivnu(ctx context.Context, id int64, aktivna bool) error } +// PdvKirRepository definiše operacije nad knjigom izdatih računa (KIR) +type PdvKirRepository interface { + Lista(ctx context.Context, od, do time.Time) ([]model.PdvKir, error) + DohvatiID(ctx context.Context, id int64) (*model.PdvKir, error) + Kreiraj(ctx context.Context, k *model.PdvKir) (int64, error) + Obrisi(ctx context.Context, id int64) error +} + +// PdvKprRepository definiše operacije nad knjigom primljenih računa (KPR) +type PdvKprRepository interface { + Lista(ctx context.Context, od, do time.Time) ([]model.PdvKpr, error) + DohvatiID(ctx context.Context, id int64) (*model.PdvKpr, error) + Kreiraj(ctx context.Context, k *model.PdvKpr) (int64, error) + Obrisi(ctx context.Context, id int64) error +} + // ArtikalFilter definiše parametre za filtriranje liste artikala type ArtikalFilter struct { Pretraga string diff --git a/internal/db/sqlite/pdv_evidencija.go b/internal/db/sqlite/pdv_evidencija.go new file mode 100644 index 0000000..8e2b470 --- /dev/null +++ b/internal/db/sqlite/pdv_evidencija.go @@ -0,0 +1,243 @@ +package sqlite + +import ( + "context" + "database/sql" + "fmt" + "time" + + "ntech/internal/model" +) + +// --- KIR: knjiga izdatih računa --- + +// PdvKirRepo je SQLite implementacija PdvKirRepository interfejsa +type PdvKirRepo struct { + db *sql.DB +} + +// NoviPdvKirRepo kreira novi PdvKirRepo +func NoviPdvKirRepo(db *sql.DB) *PdvKirRepo { + return &PdvKirRepo{db: db} +} + +// Lista vraća zapise KIR-a u zadatom periodu (po datumu prometa); nulti datum znači bez granice +func (r *PdvKirRepo) Lista(ctx context.Context, od, do time.Time) ([]model.PdvKir, error) { + upit := ` + SELECT id, datum_prometa, datum_knjizenja, broj_dokumenta, + kupac_naziv, COALESCE(kupac_pib, ''), COALESCE(kupac_mesto, ''), + osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna, + osloboden_sa_pravom, osloboden_bez_prava, ukupno, + COALESCE(napomena, ''), datum_unosa + FROM pdv_kir WHERE 1=1` + args := []any{} + if !od.IsZero() { + upit += " AND datum_prometa >= ?" + args = append(args, od) + } + if !do.IsZero() { + upit += " AND datum_prometa <= ?" + args = append(args, do) + } + upit += " ORDER BY datum_prometa ASC, id ASC" + + redovi, err := r.db.QueryContext(ctx, upit, args...) + if err != nil { + return nil, fmt.Errorf("ntech: PdvKirRepo.Lista: %w", err) + } + defer redovi.Close() + + var rezultat []model.PdvKir + for redovi.Next() { + var k model.PdvKir + if err := redovi.Scan( + &k.ID, &k.DatumPrometa, &k.DatumKnjizenja, &k.BrojDokumenta, + &k.KupacNaziv, &k.KupacPib, &k.KupacMesto, + &k.OsnovicaOpsta, &k.PdvOpsta, &k.OsnovicaPosebna, &k.PdvPosebna, + &k.OslobodenSaPravom, &k.OslobodenBezPrava, &k.Ukupno, + &k.Napomena, &k.DatumUnosa, + ); err != nil { + return nil, fmt.Errorf("ntech: PdvKirRepo.Lista: scan: %w", err) + } + rezultat = append(rezultat, k) + } + if err := redovi.Err(); err != nil { + return nil, fmt.Errorf("ntech: PdvKirRepo.Lista: %w", err) + } + return rezultat, nil +} + +// DohvatiID vraća jedan zapis KIR-a po identifikatoru +func (r *PdvKirRepo) DohvatiID(ctx context.Context, id int64) (*model.PdvKir, error) { + var k model.PdvKir + err := r.db.QueryRowContext(ctx, ` + SELECT id, datum_prometa, datum_knjizenja, broj_dokumenta, + kupac_naziv, COALESCE(kupac_pib, ''), COALESCE(kupac_mesto, ''), + osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna, + osloboden_sa_pravom, osloboden_bez_prava, ukupno, + COALESCE(napomena, ''), datum_unosa + FROM pdv_kir WHERE id = ?`, id).Scan( + &k.ID, &k.DatumPrometa, &k.DatumKnjizenja, &k.BrojDokumenta, + &k.KupacNaziv, &k.KupacPib, &k.KupacMesto, + &k.OsnovicaOpsta, &k.PdvOpsta, &k.OsnovicaPosebna, &k.PdvPosebna, + &k.OslobodenSaPravom, &k.OslobodenBezPrava, &k.Ukupno, + &k.Napomena, &k.DatumUnosa, + ) + if err != nil { + return nil, fmt.Errorf("ntech: PdvKirRepo.DohvatiID: %w", err) + } + return &k, nil +} + +// Kreiraj dodaje novi zapis u KIR i vraća njegov id +func (r *PdvKirRepo) Kreiraj(ctx context.Context, k *model.PdvKir) (int64, error) { + rez, err := r.db.ExecContext(ctx, ` + INSERT INTO pdv_kir ( + datum_prometa, datum_knjizenja, broj_dokumenta, + kupac_naziv, kupac_pib, kupac_mesto, + osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna, + osloboden_sa_pravom, osloboden_bez_prava, ukupno, napomena + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + k.DatumPrometa, k.DatumKnjizenja, k.BrojDokumenta, + k.KupacNaziv, k.KupacPib, k.KupacMesto, + k.OsnovicaOpsta, k.PdvOpsta, k.OsnovicaPosebna, k.PdvPosebna, + k.OslobodenSaPravom, k.OslobodenBezPrava, k.Ukupno, k.Napomena) + if err != nil { + return 0, fmt.Errorf("ntech: PdvKirRepo.Kreiraj: %w", err) + } + id, err := rez.LastInsertId() + if err != nil { + return 0, fmt.Errorf("ntech: PdvKirRepo.Kreiraj: id: %w", err) + } + return id, nil +} + +// Obrisi briše zapis KIR-a +func (r *PdvKirRepo) Obrisi(ctx context.Context, id int64) error { + if _, err := r.db.ExecContext(ctx, "DELETE FROM pdv_kir WHERE id = ?", id); err != nil { + return fmt.Errorf("ntech: PdvKirRepo.Obrisi: %w", err) + } + return nil +} + +// --- KPR: knjiga primljenih računa --- + +// PdvKprRepo je SQLite implementacija PdvKprRepository interfejsa +type PdvKprRepo struct { + db *sql.DB +} + +// NoviPdvKprRepo kreira novi PdvKprRepo +func NoviPdvKprRepo(db *sql.DB) *PdvKprRepo { + return &PdvKprRepo{db: db} +} + +// Lista vraća zapise KPR-a u zadatom periodu (po datumu prometa); nulti datum znači bez granice +func (r *PdvKprRepo) Lista(ctx context.Context, od, do time.Time) ([]model.PdvKpr, error) { + upit := ` + SELECT id, datum_prometa, datum_knjizenja, datum_placanja, broj_dokumenta, + dobavljac_naziv, COALESCE(dobavljac_pib, ''), COALESCE(dobavljac_mesto, ''), + osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna, + pdv_bez_odbitka, osloboden_nabavka, ukupno, + COALESCE(napomena, ''), datum_unosa + FROM pdv_kpr WHERE 1=1` + args := []any{} + if !od.IsZero() { + upit += " AND datum_prometa >= ?" + args = append(args, od) + } + if !do.IsZero() { + upit += " AND datum_prometa <= ?" + args = append(args, do) + } + upit += " ORDER BY datum_prometa ASC, id ASC" + + redovi, err := r.db.QueryContext(ctx, upit, args...) + if err != nil { + return nil, fmt.Errorf("ntech: PdvKprRepo.Lista: %w", err) + } + defer redovi.Close() + + var rezultat []model.PdvKpr + for redovi.Next() { + k, err := skenirajKpr(redovi.Scan) + if err != nil { + return nil, fmt.Errorf("ntech: PdvKprRepo.Lista: scan: %w", err) + } + rezultat = append(rezultat, k) + } + if err := redovi.Err(); err != nil { + return nil, fmt.Errorf("ntech: PdvKprRepo.Lista: %w", err) + } + return rezultat, nil +} + +// DohvatiID vraća jedan zapis KPR-a po identifikatoru +func (r *PdvKprRepo) DohvatiID(ctx context.Context, id int64) (*model.PdvKpr, error) { + red := r.db.QueryRowContext(ctx, ` + SELECT id, datum_prometa, datum_knjizenja, datum_placanja, broj_dokumenta, + dobavljac_naziv, COALESCE(dobavljac_pib, ''), COALESCE(dobavljac_mesto, ''), + osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna, + pdv_bez_odbitka, osloboden_nabavka, ukupno, + COALESCE(napomena, ''), datum_unosa + FROM pdv_kpr WHERE id = ?`, id) + k, err := skenirajKpr(red.Scan) + if err != nil { + return nil, fmt.Errorf("ntech: PdvKprRepo.DohvatiID: %w", err) + } + return &k, nil +} + +// skenirajKpr čita jedan red KPR-a; datum_placanja je nullable pa ide preko sql.NullTime +func skenirajKpr(scan func(...any) error) (model.PdvKpr, error) { + var k model.PdvKpr + var datumPlacanja sql.NullTime + 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.DatumUnosa, + ); err != nil { + return model.PdvKpr{}, err + } + if datumPlacanja.Valid { + k.DatumPlacanja = &datumPlacanja.Time + } + return k, nil +} + +// Kreiraj dodaje novi zapis u KPR i vraća njegov id +func (r *PdvKprRepo) Kreiraj(ctx context.Context, k *model.PdvKpr) (int64, error) { + var datumPlacanja any + if k.DatumPlacanja != nil { + datumPlacanja = *k.DatumPlacanja + } + 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 + ) 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) + if err != nil { + return 0, fmt.Errorf("ntech: PdvKprRepo.Kreiraj: %w", err) + } + id, err := rez.LastInsertId() + if err != nil { + return 0, fmt.Errorf("ntech: PdvKprRepo.Kreiraj: id: %w", err) + } + return id, nil +} + +// Obrisi briše zapis KPR-a +func (r *PdvKprRepo) Obrisi(ctx context.Context, id int64) error { + if _, err := r.db.ExecContext(ctx, "DELETE FROM pdv_kpr WHERE id = ?", id); err != nil { + return fmt.Errorf("ntech: PdvKprRepo.Obrisi: %w", err) + } + return nil +} diff --git a/internal/db/sqlite/pdv_evidencija_test.go b/internal/db/sqlite/pdv_evidencija_test.go new file mode 100644 index 0000000..2397a11 --- /dev/null +++ b/internal/db/sqlite/pdv_evidencija_test.go @@ -0,0 +1,129 @@ +package sqlite + +import ( + "context" + "testing" + "time" + + "ntech/internal/model" +) + +// istiDan poredi datume po godini/mesecu/danu (izbegava razlike u vremenskoj zoni/času) +func istiDan(a, b time.Time) bool { + ay, am, ad := a.Date() + by, bm, bd := b.Date() + return ay == by && am == bm && ad == bd +} + +func TestPdvKirRepo(t *testing.T) { + db := testDB(t) + repo := NoviPdvKirRepo(db) + ctx := context.Background() + + dp := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC) + dk := time.Date(2026, 6, 2, 0, 0, 0, 0, time.UTC) + + id, err := repo.Kreiraj(ctx, &model.PdvKir{ + DatumPrometa: dp, DatumKnjizenja: dk, BrojDokumenta: "R-1", + KupacNaziv: "Kupac doo", KupacPib: "123456789", KupacMesto: "Niš", + OsnovicaOpsta: 100, PdvOpsta: 20, Ukupno: 120, + }) + if err != nil { + t.Fatalf("Kreiraj: %v", err) + } + + t.Run("datum i iznosi round-trip", func(t *testing.T) { + k, err := repo.DohvatiID(ctx, id) + if err != nil { + t.Fatalf("DohvatiID: %v", err) + } + if !istiDan(k.DatumPrometa, dp) { + t.Errorf("datum_prometa = %v, očekivano %v", k.DatumPrometa, dp) + } + if !istiDan(k.DatumKnjizenja, dk) { + t.Errorf("datum_knjizenja = %v, očekivano %v", k.DatumKnjizenja, dk) + } + if k.KupacNaziv != "Kupac doo" || k.KupacPib != "123456789" || k.KupacMesto != "Niš" { + t.Errorf("kupac podaci ne odgovaraju: %+v", k) + } + if k.OsnovicaOpsta != 100 || k.PdvOpsta != 20 || k.Ukupno != 120 { + t.Errorf("iznosi ne odgovaraju: %+v", k) + } + }) + + t.Run("filter perioda", func(t *testing.T) { + uPeriodu, err := repo.Lista(ctx, time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 6, 30, 0, 0, 0, 0, time.UTC)) + if err != nil { + t.Fatalf("Lista u periodu: %v", err) + } + if len(uPeriodu) != 1 { + t.Errorf("u junu očekivano 1 zapis, dobijeno %d", len(uPeriodu)) + } + vanPerioda, err := repo.Lista(ctx, time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 7, 31, 0, 0, 0, 0, time.UTC)) + if err != nil { + t.Fatalf("Lista van perioda: %v", err) + } + if len(vanPerioda) != 0 { + t.Errorf("u julu očekivano 0 zapisa, dobijeno %d", len(vanPerioda)) + } + }) + + t.Run("brisanje", func(t *testing.T) { + if err := repo.Obrisi(ctx, id); err != nil { + t.Fatalf("Obrisi: %v", err) + } + sve, _ := repo.Lista(ctx, time.Time{}, time.Time{}) + if len(sve) != 0 { + t.Errorf("posle brisanja očekivano 0, dobijeno %d", len(sve)) + } + }) +} + +func TestPdvKprRepo(t *testing.T) { + db := testDB(t) + repo := NoviPdvKprRepo(db) + ctx := context.Background() + + dp := time.Date(2026, 6, 10, 0, 0, 0, 0, time.UTC) + dpl := time.Date(2026, 6, 15, 0, 0, 0, 0, time.UTC) + + t.Run("sa datumom plaćanja", func(t *testing.T) { + id, err := repo.Kreiraj(ctx, &model.PdvKpr{ + DatumPrometa: dp, DatumKnjizenja: dp, DatumPlacanja: &dpl, BrojDokumenta: "U-1", + DobavljacNaziv: "Dobavljač doo", OsnovicaOpsta: 200, PdvOpsta: 40, Ukupno: 240, + }) + if err != nil { + t.Fatalf("Kreiraj: %v", err) + } + k, err := repo.DohvatiID(ctx, id) + if err != nil { + t.Fatalf("DohvatiID: %v", err) + } + if k.DatumPlacanja == nil { + t.Fatalf("datum_placanja je nil, očekivan %v", dpl) + } + if !istiDan(*k.DatumPlacanja, dpl) { + t.Errorf("datum_placanja = %v, očekivano %v", *k.DatumPlacanja, dpl) + } + if k.OsnovicaOpsta != 200 || k.PdvOpsta != 40 { + t.Errorf("iznosi ne odgovaraju: %+v", k) + } + }) + + t.Run("bez datuma plaćanja (NULL)", func(t *testing.T) { + id, err := repo.Kreiraj(ctx, &model.PdvKpr{ + DatumPrometa: dp, DatumKnjizenja: dp, DatumPlacanja: nil, BrojDokumenta: "U-2", + DobavljacNaziv: "Dobavljač 2", OsnovicaPosebna: 50, PdvPosebna: 5, Ukupno: 55, + }) + if err != nil { + t.Fatalf("Kreiraj: %v", err) + } + k, err := repo.DohvatiID(ctx, id) + if err != nil { + t.Fatalf("DohvatiID: %v", err) + } + if k.DatumPlacanja != nil { + t.Errorf("datum_placanja = %v, očekivan nil", *k.DatumPlacanja) + } + }) +} diff --git a/internal/handler/handler.go b/internal/handler/handler.go index c0b96cc..5c6fb55 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -36,6 +36,8 @@ type Handler struct { LoginIstorijsaRepo db.LoginIstorijsaRepository DozvoleRepo db.DozvoleRepository PdvStopeRepo db.PdvStopaRepository + PdvKirRepo db.PdvKirRepository + PdvKprRepo db.PdvKprRepository Verzija string AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju) Templates map[string]*template.Template @@ -96,6 +98,8 @@ func Novi(baza *sql.DB, totpKljuc []byte) *Handler { LoginIstorijsaRepo: sqlite.NoviLoginIstorijsaRepo(baza), DozvoleRepo: sqlite.NoviDozvoleRepo(baza, middleware.ImaDozvolu, middleware.SveAkcije()), PdvStopeRepo: sqlite.NoviPdvStopaRepo(baza), + PdvKirRepo: sqlite.NoviPdvKirRepo(baza), + PdvKprRepo: sqlite.NoviPdvKprRepo(baza), } } @@ -122,6 +126,8 @@ func (h *Handler) reinicijalizujRepozitorijume(novaDB *sql.DB) { h.LoginIstorijsaRepo = sqlite.NoviLoginIstorijsaRepo(novaDB) h.DozvoleRepo = sqlite.NoviDozvoleRepo(novaDB, middleware.ImaDozvolu, middleware.SveAkcije()) h.PdvStopeRepo = sqlite.NoviPdvStopaRepo(novaDB) + h.PdvKirRepo = sqlite.NoviPdvKirRepo(novaDB) + h.PdvKprRepo = sqlite.NoviPdvKprRepo(novaDB) } // zahtevajDozvolu vraća prijavljenog korisnika ako njegova uloga sme da izvrši akciju. diff --git a/internal/model/pdv_evidencija.go b/internal/model/pdv_evidencija.go new file mode 100644 index 0000000..39c41f0 --- /dev/null +++ b/internal/model/pdv_evidencija.go @@ -0,0 +1,45 @@ +package model + +import "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 + DatumUnosa time.Time +} + +// 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 + DatumUnosa time.Time +} diff --git a/migrations/041_pdv_evidencija.sql b/migrations/041_pdv_evidencija.sql new file mode 100644 index 0000000..880634c --- /dev/null +++ b/migrations/041_pdv_evidencija.sql @@ -0,0 +1,46 @@ +-- PDV evidencija (Faza 2a knjigovodstvenog modula): knjiga izdatih računa (KIR) +-- i knjiga primljenih računa (KPR). Iznosi se vode po VRSTI stope (opšta/posebna), +-- ne po procentu — promena stope zakonom ne razbija kolone. +-- Napomena: ovo je osnovna knjiga; pun POPDV obrazac (uvoz, avansi, poljoprivreda) +-- dolazi u kasnijoj iteraciji. + +-- Knjiga izdatih računa (izlazni PDV). +CREATE TABLE IF NOT EXISTS pdv_kir ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + datum_prometa DATE NOT NULL, -- datum prometa / izdavanja računa + datum_knjizenja DATE NOT NULL, -- datum knjiženja u KIR + broj_dokumenta TEXT NOT NULL, -- broj računa ili drugog dokumenta + kupac_naziv TEXT NOT NULL, + kupac_pib TEXT, -- PIB ili JMBG kupca + kupac_mesto TEXT, + osnovica_opsta REAL NOT NULL DEFAULT 0, -- osnovica oporeziva opštom stopom + pdv_opsta REAL NOT NULL DEFAULT 0, -- obračunati PDV po opštoj stopi + osnovica_posebna REAL NOT NULL DEFAULT 0, -- osnovica oporeziva posebnom stopom + pdv_posebna REAL NOT NULL DEFAULT 0, -- obračunati PDV po posebnoj stopi + osloboden_sa_pravom REAL NOT NULL DEFAULT 0, -- oslobođen promet sa pravom na odbitak + osloboden_bez_prava REAL NOT NULL DEFAULT 0, -- oslobođen promet bez prava na odbitak + ukupno REAL NOT NULL DEFAULT 0, -- ukupna naknada sa PDV + napomena TEXT, + datum_unosa DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- Knjiga primljenih računa (ulazni PDV). +CREATE TABLE IF NOT EXISTS pdv_kpr ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + datum_prometa DATE NOT NULL, -- datum prometa / prijema računa + datum_knjizenja DATE NOT NULL, -- datum knjiženja u KPR + datum_placanja DATE, -- datum plaćanja (može biti prazan) + broj_dokumenta TEXT NOT NULL, + dobavljac_naziv TEXT NOT NULL, + dobavljac_pib TEXT, + dobavljac_mesto TEXT, + osnovica_opsta REAL NOT NULL DEFAULT 0, + pdv_opsta REAL NOT NULL DEFAULT 0, -- PDV koji se može odbiti (opšta) + osnovica_posebna REAL NOT NULL DEFAULT 0, + pdv_posebna REAL NOT NULL DEFAULT 0, -- PDV koji se može odbiti (posebna) + pdv_bez_odbitka REAL NOT NULL DEFAULT 0, -- PDV bez prava na odbitak + osloboden_nabavka REAL NOT NULL DEFAULT 0, -- nabavka bez PDV / oslobođena + ukupno REAL NOT NULL DEFAULT 0, + napomena TEXT, + datum_unosa DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +);