Usluge: zaseban modul (cenovnik usluga)
Usluga je zaseban entitet, ne artikal: svoja tabela usluge (naziv, šifra, jedinica mere, cena, PDV, opis), repo, handleri i forma. Auto-šifra USL-001…999, jedinica mere (usluga/sat/kom). Bez nabavne/prodajne cene, dobavljača, lagera i marže. /usluge lista + CRUD; stari tip-filter handler uklonjen iz magacina.
This commit is contained in:
@@ -104,6 +104,28 @@ type ArtikalFilter struct {
|
||||
Offset int
|
||||
}
|
||||
|
||||
// UslugaFilter su parametri za listanje usluga
|
||||
type UslugaFilter struct {
|
||||
Pretraga string
|
||||
Arhivirani bool
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
// UslugaRepository definiše operacije nad uslugama (zaseban entitet od artikala)
|
||||
type UslugaRepository interface {
|
||||
Lista(ctx context.Context, filter UslugaFilter) ([]model.Usluga, error)
|
||||
PrebrojiPoFilteru(ctx context.Context, filter UslugaFilter) (int, error)
|
||||
DohvatiID(ctx context.Context, id int64) (*model.Usluga, error)
|
||||
Kreiraj(ctx context.Context, u *model.Usluga) (int64, error)
|
||||
Izmeni(ctx context.Context, u *model.Usluga) error
|
||||
Obrisi(ctx context.Context, id int64) error
|
||||
// Kategorije vraća postojeće (distinct) kategorije usluga za predlog pri unosu
|
||||
Kategorije(ctx context.Context) ([]string, error)
|
||||
// SledecaSifra vraća predlog sledeće auto-šifre usluge (USL-001 … USL-999)
|
||||
SledecaSifra(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
// NabavkaRepository definiše operacije nad nabavkama
|
||||
type NabavkaRepository interface {
|
||||
Lista(ctx context.Context) ([]model.NabavkaSaDetaljem, error)
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user