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.
143 lines
3.9 KiB
Go
143 lines
3.9 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"ntech/internal/db"
|
|
"ntech/internal/model"
|
|
)
|
|
|
|
// TrosakRepo je SQLite implementacija db.TrosakRepository
|
|
type TrosakRepo struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NoviTrosakRepo kreira repozitorijum troškova nad datom bazom
|
|
func NoviTrosakRepo(baza *sql.DB) *TrosakRepo {
|
|
return &TrosakRepo{db: baza}
|
|
}
|
|
|
|
// skenirajTrosak čita jedan red u model.Trosak
|
|
func skenirajTrosak(s interface {
|
|
Scan(dest ...any) error
|
|
}) (model.Trosak, error) {
|
|
var t model.Trosak
|
|
var sifra sql.NullString
|
|
var arhiviran int
|
|
if err := s.Scan(&t.ID, &sifra, &t.Naziv, &t.Cena, &t.Opis, &arhiviran, &t.DatumUnosa); err != nil {
|
|
return t, err
|
|
}
|
|
t.Sifra = sifra.String
|
|
t.Arhiviran = arhiviran == 1
|
|
return t, nil
|
|
}
|
|
|
|
const trosakKolone = "id, sifra, naziv, cena, opis, arhiviran, datum_unosa"
|
|
|
|
// Lista vraća troškove prema filteru (pretraga po nazivu/šifri, aktivne/arhivirane)
|
|
func (r *TrosakRepo) Lista(ctx context.Context, filter db.TrosakFilter) ([]model.Trosak, error) {
|
|
upit := "SELECT " + trosakKolone + " FROM troskovi WHERE 1=1"
|
|
var args []any
|
|
|
|
if filter.Pretraga != "" {
|
|
upit += " AND (naziv LIKE ? OR sifra LIKE ?)"
|
|
t := "%" + filter.Pretraga + "%"
|
|
args = append(args, 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: TrosakRepo.Lista: %w", err)
|
|
}
|
|
defer redovi.Close()
|
|
|
|
var troskovi []model.Trosak
|
|
for redovi.Next() {
|
|
t, err := skenirajTrosak(redovi)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ntech: TrosakRepo.Lista: %w", err)
|
|
}
|
|
troskovi = append(troskovi, t)
|
|
}
|
|
return troskovi, redovi.Err()
|
|
}
|
|
|
|
// DohvatiID vraća jedan trošak po ID-u
|
|
func (r *TrosakRepo) DohvatiID(ctx context.Context, id int64) (*model.Trosak, error) {
|
|
red := r.db.QueryRowContext(ctx, "SELECT "+trosakKolone+" FROM troskovi WHERE id = ?", id)
|
|
t, err := skenirajTrosak(red)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ntech: TrosakRepo.DohvatiID: %w", err)
|
|
}
|
|
return &t, nil
|
|
}
|
|
|
|
// Kreiraj upisuje nov trošak i vraća njegov ID
|
|
func (r *TrosakRepo) Kreiraj(ctx context.Context, t *model.Trosak) (int64, error) {
|
|
var sifra any
|
|
if t.Sifra != "" {
|
|
sifra = t.Sifra
|
|
}
|
|
rez, err := r.db.ExecContext(ctx, `
|
|
INSERT INTO troskovi (sifra, naziv, cena, opis)
|
|
VALUES (?, ?, ?, ?)`,
|
|
sifra, t.Naziv, t.Cena, t.Opis,
|
|
)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("ntech: TrosakRepo.Kreiraj: %w", err)
|
|
}
|
|
id, err := rez.LastInsertId()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("ntech: TrosakRepo.Kreiraj: last insert id: %w", err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// Izmeni ažurira postojeći trošak
|
|
func (r *TrosakRepo) Izmeni(ctx context.Context, t *model.Trosak) error {
|
|
var sifra any
|
|
if t.Sifra != "" {
|
|
sifra = t.Sifra
|
|
}
|
|
_, err := r.db.ExecContext(ctx, `
|
|
UPDATE troskovi SET sifra = ?, naziv = ?, cena = ?, opis = ?
|
|
WHERE id = ?`,
|
|
sifra, t.Naziv, t.Cena, t.Opis, t.ID,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("ntech: TrosakRepo.Izmeni: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Obrisi trajno briše trošak po ID-u
|
|
func (r *TrosakRepo) Obrisi(ctx context.Context, id int64) error {
|
|
if _, err := r.db.ExecContext(ctx, "DELETE FROM troskovi WHERE id = ?", id); err != nil {
|
|
return fmt.Errorf("ntech: TrosakRepo.Obrisi: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SledecaSifra vraća predlog sledeće auto-šifre troška u formatu TRO-001 … TRO-999
|
|
func (r *TrosakRepo) SledecaSifra(ctx context.Context) (string, error) {
|
|
var maks sql.NullInt64
|
|
err := r.db.QueryRowContext(ctx,
|
|
"SELECT MAX(CAST(SUBSTR(sifra, 5) AS INTEGER)) FROM troskovi WHERE sifra LIKE 'TRO-%'").Scan(&maks)
|
|
if err != nil {
|
|
return "TRO-001", fmt.Errorf("ntech: TrosakRepo.SledecaSifra: %w", err)
|
|
}
|
|
return fmt.Sprintf("TRO-%03d", maks.Int64+1), nil
|
|
}
|