From bdb0f4b1ae77877423cc38422621ddaca207182f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Fri, 19 Jun 2026 19:35:24 +0200 Subject: [PATCH] =?UTF-8?q?Magacin:=20dodati=20=C5=A1ifra=20i=20barkod=20a?= =?UTF-8?q?rtikla=20sa=20auto-generisanjem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migracija 055: kolone sifra i barkod u tabeli artikli (UNIQUE indeksi) - Model, repozitorijum i handleri ažurirani za nova polja - Pretraga u magacinu pokriva i šifru i barkod - Forma predlaže sledeću šifru (ART-NNNNN), korisnik može izmeniti - Ako se ostavi prazno, šifra se auto-dodeljuje po ID-u pri čuvanju --- internal/db/repository.go | 2 + internal/db/sqlite/artikal.go | 62 +++++++++++++++++++---- internal/handler/magacin_forma.go | 19 +++++++ internal/model/artikal.go | 2 + migrations/055_artikal_sifra_barkod.sql | 5 ++ web/templates/stranice/magacin_forma.html | 18 +++++++ 6 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 migrations/055_artikal_sifra_barkod.sql diff --git a/internal/db/repository.go b/internal/db/repository.go index 92131f9..be52b6e 100644 --- a/internal/db/repository.go +++ b/internal/db/repository.go @@ -17,6 +17,8 @@ type ArtikalRepository interface { AzurirajCene(ctx context.Context, id int64, nabavna, prodajna float64) error PremestiKategoriju(ctx context.Context, id int64, kategorijaID *int64) error Obrisi(ctx context.Context, id int64) error + // SledecaSifra vraća predlog sledeće auto-šifre (npr. ART-00042) + SledecaSifra(ctx context.Context) (string, error) } // KategorijaRepository definiše operacije nad kategorijama diff --git a/internal/db/sqlite/artikal.go b/internal/db/sqlite/artikal.go index d7c9bbb..6ee041c 100644 --- a/internal/db/sqlite/artikal.go +++ b/internal/db/sqlite/artikal.go @@ -23,7 +23,7 @@ func NoviArtikalRepo(db *sql.DB) *ArtikalRepo { func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]model.ArtikalSaKategorijom, error) { upit := ` SELECT - a.id, a.kategorija_id, a.naziv, a.opis, + a.id, a.kategorija_id, a.sifra, a.barkod, a.naziv, a.opis, a.kolicina, a.kolicina_min, a.lokacija, a.nabavna_cena, a.prodajna_cena, a.pdv_stopa, a.marza, a.napomena, a.datum_unosa, COALESCE(k.naziv, '') as kategorija_naziv, k.marza as kategorija_marza @@ -34,9 +34,9 @@ func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]mod args := []any{} if filter.Pretraga != "" { - upit += " AND (a.naziv LIKE ? OR a.lokacija LIKE ? OR k.naziv LIKE ?)" + upit += " AND (a.naziv LIKE ? OR a.sifra LIKE ? OR a.barkod LIKE ? OR a.lokacija LIKE ? OR k.naziv LIKE ?)" t := "%" + filter.Pretraga + "%" - args = append(args, t, t, t) + args = append(args, t, t, t, t, t) } if filter.KategorijaID != nil { @@ -60,10 +60,11 @@ func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]mod for redovi.Next() { var a model.ArtikalSaKategorijom var kategorijaID sql.NullInt64 + var sifra, barkod sql.NullString var marza, katMarza sql.NullFloat64 err := redovi.Scan( - &a.ID, &kategorijaID, &a.Naziv, &a.Opis, + &a.ID, &kategorijaID, &sifra, &barkod, &a.Naziv, &a.Opis, &a.Kolicina, &a.KolicinMin, &a.Lokacija, &a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &marza, &a.Napomena, &a.DatumUnosa, &a.KategorijaNaziv, &katMarza, @@ -75,6 +76,12 @@ func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]mod if kategorijaID.Valid { a.KategorijaID = &kategorijaID.Int64 } + if sifra.Valid { + a.Sifra = sifra.String + } + if barkod.Valid { + a.Barkod = barkod.String + } if marza.Valid { a.Marza = &marza.Float64 } @@ -94,13 +101,14 @@ func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]mod func (r *ArtikalRepo) DohvatiID(ctx context.Context, id int64) (*model.Artikal, error) { var a model.Artikal var kategorijaID sql.NullInt64 + var sifra, barkod sql.NullString var marza sql.NullFloat64 err := r.db.QueryRowContext(ctx, ` - SELECT id, kategorija_id, naziv, opis, kolicina, kolicina_min, + SELECT id, kategorija_id, sifra, barkod, naziv, opis, kolicina, kolicina_min, lokacija, nabavna_cena, prodajna_cena, pdv_stopa, marza, napomena, datum_unosa FROM artikli WHERE id = ?`, id).Scan( - &a.ID, &kategorijaID, &a.Naziv, &a.Opis, + &a.ID, &kategorijaID, &sifra, &barkod, &a.Naziv, &a.Opis, &a.Kolicina, &a.KolicinMin, &a.Lokacija, &a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &marza, &a.Napomena, &a.DatumUnosa, ) @@ -111,6 +119,12 @@ func (r *ArtikalRepo) DohvatiID(ctx context.Context, id int64) (*model.Artikal, if kategorijaID.Valid { a.KategorijaID = &kategorijaID.Int64 } + if sifra.Valid { + a.Sifra = sifra.String + } + if barkod.Valid { + a.Barkod = barkod.String + } if marza.Valid { a.Marza = &marza.Float64 } @@ -120,12 +134,20 @@ func (r *ArtikalRepo) DohvatiID(ctx context.Context, id int64) (*model.Artikal, // Kreiraj dodaje novi artikal u bazu func (r *ArtikalRepo) Kreiraj(ctx context.Context, a *model.Artikal) (int64, error) { + var sifra, barkod any + if a.Sifra != "" { + sifra = a.Sifra + } + if a.Barkod != "" { + barkod = a.Barkod + } + rezultat, err := r.db.ExecContext(ctx, ` INSERT INTO artikli - (kategorija_id, naziv, opis, kolicina, kolicina_min, lokacija, + (kategorija_id, sifra, barkod, naziv, opis, kolicina, kolicina_min, lokacija, nabavna_cena, prodajna_cena, pdv_stopa, marza, napomena) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - a.KategorijaID, a.Naziv, a.Opis, a.Kolicina, a.KolicinMin, + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + a.KategorijaID, sifra, barkod, a.Naziv, a.Opis, a.Kolicina, a.KolicinMin, a.Lokacija, a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.Marza, a.Napomena, ) if err != nil { @@ -142,13 +164,21 @@ func (r *ArtikalRepo) Kreiraj(ctx context.Context, a *model.Artikal) (int64, err // Izmeni ažurira postojeći artikal func (r *ArtikalRepo) Izmeni(ctx context.Context, a *model.Artikal) error { + var sifra, barkod any + if a.Sifra != "" { + sifra = a.Sifra + } + if a.Barkod != "" { + barkod = a.Barkod + } + _, err := r.db.ExecContext(ctx, ` UPDATE artikli SET - kategorija_id = ?, naziv = ?, opis = ?, kolicina = ?, + kategorija_id = ?, sifra = ?, barkod = ?, naziv = ?, opis = ?, kolicina = ?, kolicina_min = ?, lokacija = ?, nabavna_cena = ?, prodajna_cena = ?, pdv_stopa = ?, marza = ?, napomena = ? WHERE id = ?`, - a.KategorijaID, a.Naziv, a.Opis, a.Kolicina, + a.KategorijaID, sifra, barkod, a.Naziv, a.Opis, a.Kolicina, a.KolicinMin, a.Lokacija, a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.Marza, a.Napomena, a.ID, ) @@ -159,6 +189,16 @@ func (r *ArtikalRepo) Izmeni(ctx context.Context, a *model.Artikal) error { return nil } +// SledecaSifra vraća predlog sledeće auto-šifre u formatu ART-XXXXX +func (r *ArtikalRepo) SledecaSifra(ctx context.Context) (string, error) { + var n int64 + err := r.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM artikli").Scan(&n) + if err != nil { + return "", fmt.Errorf("ntech: ArtikalRepo.SledecaSifra: %w", err) + } + return fmt.Sprintf("ART-%05d", n+1), nil +} + // AzurirajCene menja samo nabavnu i prodajnu cenu artikla (kalkulacija pri prijemu robe). func (r *ArtikalRepo) AzurirajCene(ctx context.Context, id int64, nabavna, prodajna float64) error { _, err := r.db.ExecContext(ctx, diff --git a/internal/handler/magacin_forma.go b/internal/handler/magacin_forma.go index 67ef383..7328639 100644 --- a/internal/handler/magacin_forma.go +++ b/internal/handler/magacin_forma.go @@ -37,12 +37,19 @@ func (h *Handler) NoviArtikal(w http.ResponseWriter, r *http.Request) { return } + predlogSifre, err := h.Artikli.SledecaSifra(r.Context()) + if err != nil { + slog.Error("greška pri generisanju predloga šifre", "err", err) + predlogSifre = "ART-00001" + } + ps := h.popuniPodaciStranice(r, podesavanja) ps.Stranica = "magacin" ps.NaslovStranice = "Novi artikal" h.renderujFormuArtikla(w, PodaciFormeArtikla{ PodaciStranice: ps, Kategorije: kategorije, + Artikal: model.Artikal{Sifra: predlogSifre}, Izmena: false, }) } @@ -87,6 +94,16 @@ func (h *Handler) SacuvajArtikal(w http.ResponseWriter, r *http.Request) { return } + // ako korisnik nije uneo šifru, auto-generišemo po ID-u + if artikal.Sifra == "" { + autoSifra := fmt.Sprintf("ART-%05d", id) + artikal.ID = id + artikal.Sifra = autoSifra + if err := h.Artikli.Izmeni(r.Context(), &artikal); err != nil { + slog.Error("greška pri upisu auto-šifre", "id", id, "err", err) + } + } + // fetch zahtev (iz modala) dobija JSON sa ID-em i nazivom novog artikla if r.Header.Get("X-Requested-With") == "fetch" { w.Header().Set("Content-Type", "application/json") @@ -221,6 +238,8 @@ func parseFormuArtikla(r *http.Request) (model.Artikal, string) { var artikal model.Artikal artikal.Naziv = naziv + artikal.Sifra = r.FormValue("sifra") + artikal.Barkod = r.FormValue("barkod") artikal.Opis = r.FormValue("opis") artikal.Lokacija = r.FormValue("lokacija") artikal.Napomena = r.FormValue("napomena") diff --git a/internal/model/artikal.go b/internal/model/artikal.go index c49cb6c..baa357c 100644 --- a/internal/model/artikal.go +++ b/internal/model/artikal.go @@ -6,6 +6,8 @@ import "time" type Artikal struct { ID int64 KategorijaID *int64 + Sifra string + Barkod string Naziv string Opis string Kolicina int diff --git a/migrations/055_artikal_sifra_barkod.sql b/migrations/055_artikal_sifra_barkod.sql new file mode 100644 index 0000000..5751166 --- /dev/null +++ b/migrations/055_artikal_sifra_barkod.sql @@ -0,0 +1,5 @@ +ALTER TABLE artikli ADD COLUMN sifra TEXT; +ALTER TABLE artikli ADD COLUMN barkod TEXT; + +CREATE UNIQUE INDEX IF NOT EXISTS idx_artikli_sifra ON artikli(sifra) WHERE sifra IS NOT NULL; +CREATE UNIQUE INDEX IF NOT EXISTS idx_artikli_barkod ON artikli(barkod) WHERE barkod IS NOT NULL; diff --git a/web/templates/stranice/magacin_forma.html b/web/templates/stranice/magacin_forma.html index f8d254f..98e996b 100644 --- a/web/templates/stranice/magacin_forma.html +++ b/web/templates/stranice/magacin_forma.html @@ -25,6 +25,24 @@
+ +
+
+ + +
Ako ostaviš prazno, šifra se automatski dodeljuje.
+
+
+ + +
Barkod sa pakovanja (opciono).
+
+
+