Files
GoNtech/internal/db/sqlite/usluga.go
T
Dasko bc60288a0d Sprečeno dupliranje prodaje/servisa, alat za usklađivanje šifri artikla, ispravke osvežavanja stranica, zaštita od duplikata kategorije/artikla/usluge/troška
- Prodaja i servis: broj naloga se generiše unutar transakcije upisa umesto unapred u handleru, čime se otklanja duplo kreiranje naloga pri dvostrukom slanju forme
- Servis izmena: uklonjen sudar generičkog i sopstvenog auto-snimanja forme (dva paralelna POST-a)
- Magacin: novi alat 'Uskladi šifre sa kategorijama' za masovnu ispravku šifri artikala posle uvođenja šifarnika po kategoriji
- Ispravljeno da forme na 9 stranica (kategorije, admin korisnici/dozvole, profil, podešavanja) posle akcije osveže prikaz umesto da ostanu zastarele do F5
- Kategorija, artikal, usluga i trošak: sprečeno dupliranje naziva/šifre/koda (UNIQUE indeksi + razumljive poruke grešaka)
2026-07-07 03:50:31 +02:00

188 lines
5.5 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 {
if jeUnique(err) {
return 0, db.ErrUslugaDuplikatSifre
}
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 {
if jeUnique(err) {
return db.ErrUslugaDuplikatSifre
}
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
}