Umesto ručnog parsiranja (prethodni commit), migracija 103 rekreira obe tabele sa DATETIME kolonom (kao svuda drugde u šemi) — SQLite ne podržava ALTER COLUMN, pa je urađeno kreiraj-novu/prebaci-podatke/obriši-staru/ preimenuj. Fizički format podataka se ne menja (SQLite nema pravi numerički afinitet za DATETIME nad ovakvim stringom), menja se samo deklarisani tip kolone koji driver čita da odluči string vs time.Time pri Scan-u. Uklonjen parseDatumUnosa workaround iz utils.go — skenirajUslugu/skenirajTrosak sada rade identično kao svi ostali skenirajX helperi (direktan Scan u time.Time). Potvrđeno: migracija testirana na praznoj i na postojećoj bazi sa podacima (6 usluga, ID-jevi i datumi netaknuti), /usluge i /troskovi vraćaju 200.
182 lines
5.4 KiB
Go
182 lines
5.4 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"ntech/internal/db"
|
|
"ntech/internal/model"
|
|
)
|
|
|
|
// UslugaRepo je SQLite implementacija db.UslugaRepository
|
|
type UslugaRepo struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NoviUslugaRepo kreira repozitorijum usluga nad datom bazom
|
|
func NoviUslugaRepo(baza *sql.DB) *UslugaRepo {
|
|
return &UslugaRepo{db: baza}
|
|
}
|
|
|
|
// skenirajUslugu čita jedan red u model.Usluga
|
|
func skenirajUslugu(s interface {
|
|
Scan(dest ...any) error
|
|
}) (model.Usluga, error) {
|
|
var u model.Usluga
|
|
var sifra sql.NullString
|
|
var arhiviran int
|
|
if err := s.Scan(&u.ID, &sifra, &u.Naziv, &u.Kategorija, &u.JedinicaMere, &u.Cena, &u.PdvStopa, &u.Opis, &arhiviran, &u.DatumUnosa); err != nil {
|
|
return u, err
|
|
}
|
|
u.Sifra = sifra.String
|
|
u.Arhiviran = arhiviran == 1
|
|
return u, nil
|
|
}
|
|
|
|
const uslugaKolone = "id, sifra, naziv, kategorija, jedinica_mere, cena, pdv_stopa, opis, arhiviran, datum_unosa"
|
|
|
|
// Lista vraća usluge prema filteru (pretraga po nazivu/šifri/kategoriji, aktivne/arhivirane)
|
|
func (r *UslugaRepo) Lista(ctx context.Context, filter db.UslugaFilter) ([]model.Usluga, error) {
|
|
upit := "SELECT " + uslugaKolone + " FROM usluge WHERE 1=1"
|
|
var args []any
|
|
|
|
if filter.Pretraga != "" {
|
|
upit += " AND (naziv LIKE ? OR sifra LIKE ? OR kategorija LIKE ?)"
|
|
t := "%" + filter.Pretraga + "%"
|
|
args = append(args, t, t, t)
|
|
}
|
|
if filter.Arhivirani {
|
|
upit += " AND arhiviran = 1"
|
|
} else {
|
|
upit += " AND arhiviran = 0"
|
|
}
|
|
upit += " ORDER BY naziv"
|
|
if filter.Limit > 0 {
|
|
upit += " LIMIT ? OFFSET ?"
|
|
args = append(args, filter.Limit, filter.Offset)
|
|
}
|
|
|
|
redovi, err := r.db.QueryContext(ctx, upit, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ntech: UslugaRepo.Lista: %w", err)
|
|
}
|
|
defer redovi.Close()
|
|
|
|
var usluge []model.Usluga
|
|
for redovi.Next() {
|
|
u, err := skenirajUslugu(redovi)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ntech: UslugaRepo.Lista: %w", err)
|
|
}
|
|
usluge = append(usluge, u)
|
|
}
|
|
return usluge, redovi.Err()
|
|
}
|
|
|
|
// PrebrojiPoFilteru vraća broj usluga koje zadovoljavaju filter (za paginaciju)
|
|
func (r *UslugaRepo) PrebrojiPoFilteru(ctx context.Context, filter db.UslugaFilter) (int, error) {
|
|
upit := "SELECT COUNT(*) FROM usluge WHERE 1=1"
|
|
var args []any
|
|
if filter.Pretraga != "" {
|
|
upit += " AND (naziv LIKE ? OR sifra LIKE ? OR kategorija LIKE ?)"
|
|
t := "%" + filter.Pretraga + "%"
|
|
args = append(args, t, t, t)
|
|
}
|
|
if filter.Arhivirani {
|
|
upit += " AND arhiviran = 1"
|
|
} else {
|
|
upit += " AND arhiviran = 0"
|
|
}
|
|
var n int
|
|
if err := r.db.QueryRowContext(ctx, upit, args...).Scan(&n); err != nil {
|
|
return 0, fmt.Errorf("ntech: UslugaRepo.PrebrojiPoFilteru: %w", err)
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// DohvatiID vraća jednu uslugu po ID-u
|
|
func (r *UslugaRepo) DohvatiID(ctx context.Context, id int64) (*model.Usluga, error) {
|
|
red := r.db.QueryRowContext(ctx, "SELECT "+uslugaKolone+" FROM usluge WHERE id = ?", id)
|
|
u, err := skenirajUslugu(red)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ntech: UslugaRepo.DohvatiID: %w", err)
|
|
}
|
|
return &u, nil
|
|
}
|
|
|
|
// Kreiraj upisuje novu uslugu i vraća njen ID
|
|
func (r *UslugaRepo) Kreiraj(ctx context.Context, u *model.Usluga) (int64, error) {
|
|
var sifra any
|
|
if u.Sifra != "" {
|
|
sifra = u.Sifra
|
|
}
|
|
rez, err := r.db.ExecContext(ctx, `
|
|
INSERT INTO usluge (sifra, naziv, kategorija, jedinica_mere, cena, pdv_stopa, opis)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
sifra, u.Naziv, u.Kategorija, u.JedinicaMere, u.Cena, u.PdvStopa, u.Opis,
|
|
)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("ntech: UslugaRepo.Kreiraj: %w", err)
|
|
}
|
|
id, err := rez.LastInsertId()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("ntech: UslugaRepo.Kreiraj: last insert id: %w", err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// Izmeni ažurira postojeću uslugu
|
|
func (r *UslugaRepo) Izmeni(ctx context.Context, u *model.Usluga) error {
|
|
var sifra any
|
|
if u.Sifra != "" {
|
|
sifra = u.Sifra
|
|
}
|
|
_, err := r.db.ExecContext(ctx, `
|
|
UPDATE usluge SET sifra = ?, naziv = ?, kategorija = ?, jedinica_mere = ?, cena = ?, pdv_stopa = ?, opis = ?
|
|
WHERE id = ?`,
|
|
sifra, u.Naziv, u.Kategorija, u.JedinicaMere, u.Cena, u.PdvStopa, u.Opis, u.ID,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("ntech: UslugaRepo.Izmeni: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Obrisi trajno briše uslugu po ID-u
|
|
func (r *UslugaRepo) Obrisi(ctx context.Context, id int64) error {
|
|
if _, err := r.db.ExecContext(ctx, "DELETE FROM usluge WHERE id = ?", id); err != nil {
|
|
return fmt.Errorf("ntech: UslugaRepo.Obrisi: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Kategorije vraća distinct neprazne kategorije usluga (za predlog pri unosu)
|
|
func (r *UslugaRepo) Kategorije(ctx context.Context) ([]string, error) {
|
|
redovi, err := r.db.QueryContext(ctx, "SELECT DISTINCT kategorija FROM usluge WHERE kategorija != '' ORDER BY kategorija")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ntech: UslugaRepo.Kategorije: %w", err)
|
|
}
|
|
defer redovi.Close()
|
|
var kat []string
|
|
for redovi.Next() {
|
|
var k string
|
|
if err := redovi.Scan(&k); err != nil {
|
|
return nil, fmt.Errorf("ntech: UslugaRepo.Kategorije: %w", err)
|
|
}
|
|
kat = append(kat, k)
|
|
}
|
|
return kat, redovi.Err()
|
|
}
|
|
|
|
// SledecaSifra vraća predlog sledeće auto-šifre usluge u formatu USL-001 … USL-999
|
|
func (r *UslugaRepo) SledecaSifra(ctx context.Context) (string, error) {
|
|
var maks sql.NullInt64
|
|
err := r.db.QueryRowContext(ctx,
|
|
"SELECT MAX(CAST(SUBSTR(sifra, 5) AS INTEGER)) FROM usluge WHERE sifra LIKE 'USL-%'").Scan(&maks)
|
|
if err != nil {
|
|
return "USL-001", fmt.Errorf("ntech: UslugaRepo.SledecaSifra: %w", err)
|
|
}
|
|
return fmt.Sprintf("USL-%03d", maks.Int64+1), nil
|
|
}
|