Files
GoNtech/internal/db/sqlite/pdv_stopa.go
T
Dasko 524c6de6ba PDV: profil firme blokira nemoguće kombinacije, stope iz šifarnika umesto hardkoda
- Podešavanja: nemoguća kombinacija (samo evidencija + PDV/fiskalizacija=da)
  se sad odbija i na serveru, ne samo u brauzeru (validirajProfilFirme).
- Servis: fiskalni račun/refund više ne iskazuje PDV kad firma nije PDV
  obveznik (stavkeFiskalnogServisa nulira stopu, isto kao Prodaja).
- Avans: uklonjena hardkodovana konstanta 20% PDV — stopa se sad uzima iz
  šifarnika (Podešavanja → Kalkulacija i PDV) ili je 0 kad firma nije
  obveznik.
- fiskal.NapraviZahtev: uklonjen fallback koji je prepisivao namerno
  postavljenu 0% stopu nazad na 20% (poništavao je zaštitu Prodaje za
  ne-PDV firme).
- Forme (usluge, artikli) i validacija PDV stope u prodaji sad koriste
  šifarnik umesto literala 20 / 0,10,20.

Detaljna pravna analiza i status svake stavke u docs/Greške.md (van git-a).
2026-07-03 01:50:57 +02:00

124 lines
3.9 KiB
Go

package sqlite
import (
"context"
"database/sql"
"fmt"
"ntech/internal/model"
)
// PdvStopaRepo je SQLite implementacija PdvStopaRepository interfejsa
type PdvStopaRepo struct {
db *sql.DB
}
// NoviPdvStopaRepo kreira novi PdvStopaRepo
func NoviPdvStopaRepo(db *sql.DB) *PdvStopaRepo {
return &PdvStopaRepo{db: db}
}
// Lista vraća stope iz šifarnika; ako je samoAktivne true, izostavlja arhivirane
func (r *PdvStopaRepo) Lista(ctx context.Context, samoAktivne bool) ([]model.PdvStopa, error) {
upit := `
SELECT id, naziv, stopa, oznaka, aktivna, redosled, datum_unosa
FROM pdv_stope
WHERE 1=1`
if samoAktivne {
upit += " AND aktivna = 1"
}
upit += " ORDER BY redosled ASC, stopa DESC"
redovi, err := r.db.QueryContext(ctx, upit)
if err != nil {
return nil, fmt.Errorf("ntech: PdvStopaRepo.Lista: %w", err)
}
defer redovi.Close()
var rezultat []model.PdvStopa
for redovi.Next() {
var s model.PdvStopa
if err := redovi.Scan(&s.ID, &s.Naziv, &s.Stopa, &s.Oznaka, &s.Aktivna, &s.Redosled, &s.DatumUnosa); err != nil {
return nil, fmt.Errorf("ntech: PdvStopaRepo.Lista: scan: %w", err)
}
rezultat = append(rezultat, s)
}
if err := redovi.Err(); err != nil {
return nil, fmt.Errorf("ntech: PdvStopaRepo.Lista: %w", err)
}
return rezultat, nil
}
// DohvatiID vraća jednu stopu po identifikatoru
func (r *PdvStopaRepo) DohvatiID(ctx context.Context, id int64) (*model.PdvStopa, error) {
var s model.PdvStopa
err := r.db.QueryRowContext(ctx, `
SELECT id, naziv, stopa, oznaka, aktivna, redosled, datum_unosa
FROM pdv_stope WHERE id = ?`, id).
Scan(&s.ID, &s.Naziv, &s.Stopa, &s.Oznaka, &s.Aktivna, &s.Redosled, &s.DatumUnosa)
if err != nil {
return nil, fmt.Errorf("ntech: PdvStopaRepo.DohvatiID: %w", err)
}
return &s, nil
}
// Kreiraj dodaje novu stopu i vraća njen id
func (r *PdvStopaRepo) Kreiraj(ctx context.Context, s *model.PdvStopa) (int64, error) {
rez, err := r.db.ExecContext(ctx, `
INSERT INTO pdv_stope (naziv, stopa, oznaka, aktivna, redosled)
VALUES (?, ?, ?, ?, ?)`,
s.Naziv, s.Stopa, s.Oznaka, s.Aktivna, s.Redosled)
if err != nil {
return 0, fmt.Errorf("ntech: PdvStopaRepo.Kreiraj: %w", err)
}
id, err := rez.LastInsertId()
if err != nil {
return 0, fmt.Errorf("ntech: PdvStopaRepo.Kreiraj: id: %w", err)
}
return id, nil
}
// PodrazumevanaOpsta vraća aktivnu opštu stopu iz šifarnika (prva po redosledu),
// ili nil ako u šifarniku nema nijedne aktivne opšte stope — poziocalac tada mora
// sam odlučiti o fallback vrednosti (šifarnik nikad ne sme biti prazan u praksi,
// ali ne oslanjamo se na to).
func (r *PdvStopaRepo) PodrazumevanaOpsta(ctx context.Context) (*model.PdvStopa, error) {
var s model.PdvStopa
err := r.db.QueryRowContext(ctx, `
SELECT id, naziv, stopa, oznaka, aktivna, redosled, datum_unosa
FROM pdv_stope
WHERE oznaka = 'opsta' AND aktivna = 1
ORDER BY redosled ASC, stopa DESC
LIMIT 1`).
Scan(&s.ID, &s.Naziv, &s.Stopa, &s.Oznaka, &s.Aktivna, &s.Redosled, &s.DatumUnosa)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("ntech: PdvStopaRepo.PodrazumevanaOpsta: %w", err)
}
return &s, nil
}
// Izmeni menja podatke postojeće stope (osim datuma unosa)
func (r *PdvStopaRepo) Izmeni(ctx context.Context, s *model.PdvStopa) error {
_, err := r.db.ExecContext(ctx, `
UPDATE pdv_stope
SET naziv = ?, stopa = ?, oznaka = ?, aktivna = ?, redosled = ?
WHERE id = ?`,
s.Naziv, s.Stopa, s.Oznaka, s.Aktivna, s.Redosled, s.ID)
if err != nil {
return fmt.Errorf("ntech: PdvStopaRepo.Izmeni: %w", err)
}
return nil
}
// PostaviAktivnu arhivira (false) ili vraća u upotrebu (true) stopu, bez brisanja
func (r *PdvStopaRepo) PostaviAktivnu(ctx context.Context, id int64, aktivna bool) error {
_, err := r.db.ExecContext(ctx, "UPDATE pdv_stope SET aktivna = ? WHERE id = ?", aktivna, id)
if err != nil {
return fmt.Errorf("ntech: PdvStopaRepo.PostaviAktivnu: %w", err)
}
return nil
}