feat(pdv): KIR/KPR evidencija — migracija, model i repozitorijum (Faza 2a)
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).
This commit is contained in:
@@ -32,6 +32,22 @@ type PdvStopaRepository interface {
|
|||||||
PostaviAktivnu(ctx context.Context, id int64, aktivna bool) error
|
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
|
// ArtikalFilter definiše parametre za filtriranje liste artikala
|
||||||
type ArtikalFilter struct {
|
type ArtikalFilter struct {
|
||||||
Pretraga string
|
Pretraga string
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -36,6 +36,8 @@ type Handler struct {
|
|||||||
LoginIstorijsaRepo db.LoginIstorijsaRepository
|
LoginIstorijsaRepo db.LoginIstorijsaRepository
|
||||||
DozvoleRepo db.DozvoleRepository
|
DozvoleRepo db.DozvoleRepository
|
||||||
PdvStopeRepo db.PdvStopaRepository
|
PdvStopeRepo db.PdvStopaRepository
|
||||||
|
PdvKirRepo db.PdvKirRepository
|
||||||
|
PdvKprRepo db.PdvKprRepository
|
||||||
Verzija string
|
Verzija string
|
||||||
AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju)
|
AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju)
|
||||||
Templates map[string]*template.Template
|
Templates map[string]*template.Template
|
||||||
@@ -96,6 +98,8 @@ func Novi(baza *sql.DB, totpKljuc []byte) *Handler {
|
|||||||
LoginIstorijsaRepo: sqlite.NoviLoginIstorijsaRepo(baza),
|
LoginIstorijsaRepo: sqlite.NoviLoginIstorijsaRepo(baza),
|
||||||
DozvoleRepo: sqlite.NoviDozvoleRepo(baza, middleware.ImaDozvolu, middleware.SveAkcije()),
|
DozvoleRepo: sqlite.NoviDozvoleRepo(baza, middleware.ImaDozvolu, middleware.SveAkcije()),
|
||||||
PdvStopeRepo: sqlite.NoviPdvStopaRepo(baza),
|
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.LoginIstorijsaRepo = sqlite.NoviLoginIstorijsaRepo(novaDB)
|
||||||
h.DozvoleRepo = sqlite.NoviDozvoleRepo(novaDB, middleware.ImaDozvolu, middleware.SveAkcije())
|
h.DozvoleRepo = sqlite.NoviDozvoleRepo(novaDB, middleware.ImaDozvolu, middleware.SveAkcije())
|
||||||
h.PdvStopeRepo = sqlite.NoviPdvStopaRepo(novaDB)
|
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.
|
// zahtevajDozvolu vraća prijavljenog korisnika ako njegova uloga sme da izvrši akciju.
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user