Files
GoNtech/internal/db/sqlite/trosak.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

149 lines
4.0 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 {
if jeUnique(err) {
return 0, db.ErrTrosakDuplikatSifre
}
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 {
if jeUnique(err) {
return db.ErrTrosakDuplikatSifre
}
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
}