- 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)
188 lines
5.5 KiB
Go
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
|
|
}
|