Dodavanje modula klijenata — lista, forma, izmena, brisanje, ispravka migracije
This commit is contained in:
@@ -45,3 +45,12 @@ type DobavljacRepository interface {
|
||||
Izmeni(ctx context.Context, d *model.Dobavljac) error
|
||||
Obrisi(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
// KlijentRepository definiše operacije nad klijentima
|
||||
type KlijentRepository interface {
|
||||
Lista(ctx context.Context, pretraga string) ([]model.Klijent, error)
|
||||
DohvatiID(ctx context.Context, id int64) (*model.Klijent, error)
|
||||
Kreiraj(ctx context.Context, k *model.Klijent) (int64, error)
|
||||
Izmeni(ctx context.Context, k *model.Klijent) error
|
||||
Obrisi(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"ntech/internal/model"
|
||||
)
|
||||
|
||||
// KlijentRepo je SQLite implementacija KlijentRepository interfejsa
|
||||
type KlijentRepo struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NoviKlijentRepo kreira novi KlijentRepo
|
||||
func NoviKlijentRepo(db *sql.DB) *KlijentRepo {
|
||||
return &KlijentRepo{db: db}
|
||||
}
|
||||
|
||||
// Lista vraća listu klijenata sa opcionom pretragom po imenu, prezimenu ili nazivu firme
|
||||
func (r *KlijentRepo) Lista(ctx context.Context, pretraga string) ([]model.Klijent, error) {
|
||||
upit := `
|
||||
SELECT id, ime, prezime, naziv_firme, pib, telefon, email, napomena, datum_unosa
|
||||
FROM klijenti
|
||||
WHERE 1=1`
|
||||
|
||||
args := []any{}
|
||||
|
||||
if pretraga != "" {
|
||||
upit += " AND (ime LIKE ? OR prezime LIKE ? OR naziv_firme LIKE ?)"
|
||||
p := "%" + pretraga + "%"
|
||||
args = append(args, p, p, p)
|
||||
}
|
||||
|
||||
upit += " ORDER BY datum_unosa DESC"
|
||||
|
||||
redovi, err := r.db.QueryContext(ctx, upit, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.Lista: %w", err)
|
||||
}
|
||||
defer redovi.Close()
|
||||
|
||||
var rezultat []model.Klijent
|
||||
for redovi.Next() {
|
||||
var k model.Klijent
|
||||
var ime, prezime, nazivFirme, pib, telefon, email, napomena sql.NullString
|
||||
err := redovi.Scan(
|
||||
&k.ID, &ime, &prezime, &nazivFirme, &pib, &telefon, &email, &napomena, &k.DatumUnosa,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.Lista: scan: %w", err)
|
||||
}
|
||||
k.Ime = ime.String
|
||||
k.Prezime = prezime.String
|
||||
k.NazivFirme = nazivFirme.String
|
||||
k.PIB = pib.String
|
||||
k.Telefon = telefon.String
|
||||
k.Email = email.String
|
||||
k.Napomena = napomena.String
|
||||
rezultat = append(rezultat, k)
|
||||
}
|
||||
|
||||
return rezultat, nil
|
||||
}
|
||||
|
||||
// DohvatiID vraća jednog klijenta po ID-u
|
||||
func (r *KlijentRepo) DohvatiID(ctx context.Context, id int64) (*model.Klijent, error) {
|
||||
var k model.Klijent
|
||||
var ime, prezime, nazivFirme, pib, telefon, email, napomena sql.NullString
|
||||
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT id, ime, prezime, naziv_firme, pib, telefon, email, napomena, datum_unosa
|
||||
FROM klijenti WHERE id = ?`, id).Scan(
|
||||
&k.ID, &ime, &prezime, &nazivFirme, &pib, &telefon, &email, &napomena, &k.DatumUnosa,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.DohvatiID: %w", err)
|
||||
}
|
||||
|
||||
k.Ime = ime.String
|
||||
k.Prezime = prezime.String
|
||||
k.NazivFirme = nazivFirme.String
|
||||
k.PIB = pib.String
|
||||
k.Telefon = telefon.String
|
||||
k.Email = email.String
|
||||
k.Napomena = napomena.String
|
||||
|
||||
return &k, nil
|
||||
}
|
||||
|
||||
// Kreiraj dodaje novog klijenta u bazu
|
||||
func (r *KlijentRepo) Kreiraj(ctx context.Context, k *model.Klijent) (int64, error) {
|
||||
rezultat, err := r.db.ExecContext(ctx, `
|
||||
INSERT INTO klijenti (ime, prezime, naziv_firme, pib, telefon, email, napomena)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
nullString(k.Ime), nullString(k.Prezime), nullString(k.NazivFirme),
|
||||
nullString(k.PIB), nullString(k.Telefon), nullString(k.Email), nullString(k.Napomena),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ntech: KlijentRepo.Kreiraj: %w", err)
|
||||
}
|
||||
|
||||
id, err := rezultat.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ntech: KlijentRepo.Kreiraj: last insert id: %w", err)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// Izmeni ažurira postojećeg klijenta
|
||||
func (r *KlijentRepo) Izmeni(ctx context.Context, k *model.Klijent) error {
|
||||
_, err := r.db.ExecContext(ctx, `
|
||||
UPDATE klijenti SET
|
||||
ime = ?, prezime = ?, naziv_firme = ?, pib = ?, telefon = ?, email = ?, napomena = ?
|
||||
WHERE id = ?`,
|
||||
nullString(k.Ime), nullString(k.Prezime), nullString(k.NazivFirme),
|
||||
nullString(k.PIB), nullString(k.Telefon), nullString(k.Email), nullString(k.Napomena),
|
||||
k.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: KlijentRepo.Izmeni: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obrisi briše klijenta po ID-u
|
||||
func (r *KlijentRepo) Obrisi(ctx context.Context, id int64) error {
|
||||
_, err := r.db.ExecContext(ctx, "DELETE FROM klijenti WHERE id = ?", id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: KlijentRepo.Obrisi: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -14,6 +14,7 @@ type Handler struct {
|
||||
KategorijeRepo db.KategorijaRepository
|
||||
DobavljaciRepo db.DobavljacRepository
|
||||
NabavkeRepo db.NabavkaRepository
|
||||
KlijentiRepo db.KlijentRepository
|
||||
}
|
||||
|
||||
// Novi kreira novi Handler sa datom bazom
|
||||
@@ -24,5 +25,6 @@ func Novi(baza *sql.DB) *Handler {
|
||||
KategorijeRepo: sqlite.NovaKategorijaRepo(baza),
|
||||
DobavljaciRepo: sqlite.NoviDobavljacRepo(baza),
|
||||
NabavkeRepo: sqlite.NoviNabavkaRepo(baza),
|
||||
KlijentiRepo: sqlite.NoviKlijentRepo(baza),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"ntech/internal/db/sqlite"
|
||||
"ntech/internal/model"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// PodaciKlijenata su podaci za stranicu sa listom klijenata
|
||||
type PodaciKlijenata struct {
|
||||
model.PodaciStranice
|
||||
Klijenti []model.Klijent
|
||||
Pretraga string
|
||||
Sacuvano bool
|
||||
Obrisan bool
|
||||
}
|
||||
|
||||
// PodaciFormeKlijenta su podaci za formu novog/izmenjenog klijenta
|
||||
type PodaciFormeKlijenta struct {
|
||||
model.PodaciStranice
|
||||
Klijent model.Klijent
|
||||
Greska string
|
||||
Izmena bool
|
||||
}
|
||||
|
||||
// Klijenti renderuje listu svih klijenata sa opcionom pretragom
|
||||
func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
|
||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
pretraga := r.URL.Query().Get("pretraga")
|
||||
|
||||
klijenti, err := h.KlijentiRepo.Lista(r.Context(), pretraga)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju klijenata", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
podaci := PodaciKlijenata{
|
||||
PodaciStranice: model.PodaciStranice{
|
||||
Stranica: "klijenti",
|
||||
NaslovStranice: "Klijenti",
|
||||
Tema: podesavanja["tema"],
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
LogoTip: podesavanja["logo_tip"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
Korisnik: "Admin",
|
||||
},
|
||||
Klijenti: klijenti,
|
||||
Pretraga: pretraga,
|
||||
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
||||
Obrisan: r.URL.Query().Get("obrisan") == "1",
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles(
|
||||
"web/templates/teme/podrazumevana/base.html",
|
||||
"web/templates/komponente/sidebar.html",
|
||||
"web/templates/komponente/topbar.html",
|
||||
"web/templates/stranice/klijenti.html",
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("greška pri učitavanju šablona: %v", err)
|
||||
http.Error(w, "Greška pri učitavanju stranice", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := tmpl.ExecuteTemplate(w, "base", podaci); err != nil {
|
||||
log.Printf("greška pri renderovanju: %v", err)
|
||||
http.Error(w, "Greška pri prikazu stranice", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// NoviKlijent prikazuje praznu formu za unos novog klijenta
|
||||
func (h *Handler) NoviKlijent(w http.ResponseWriter, r *http.Request) {
|
||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
renderujFormuKlijenta(w, PodaciFormeKlijenta{
|
||||
PodaciStranice: model.PodaciStranice{
|
||||
Stranica: "klijenti",
|
||||
NaslovStranice: "Novi klijent",
|
||||
Tema: podesavanja["tema"],
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
LogoTip: podesavanja["logo_tip"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
Korisnik: "Admin",
|
||||
},
|
||||
Izmena: false,
|
||||
})
|
||||
}
|
||||
|
||||
// SacuvajKlijenta prima POST formu i upisuje novog klijenta u bazu
|
||||
func (h *Handler) SacuvajKlijenta(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "Greška pri čitanju forme", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
klijent, greska := parseFormuKlijenta(r)
|
||||
if greska != "" {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
renderujFormuKlijenta(w, PodaciFormeKlijenta{
|
||||
PodaciStranice: model.PodaciStranice{
|
||||
Stranica: "klijenti",
|
||||
NaslovStranice: "Novi klijent",
|
||||
Tema: podesavanja["tema"],
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
LogoTip: podesavanja["logo_tip"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
Korisnik: "Admin",
|
||||
},
|
||||
Klijent: klijent,
|
||||
Greska: greska,
|
||||
Izmena: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := h.KlijentiRepo.Kreiraj(r.Context(), &klijent); err != nil {
|
||||
log.Printf("greška pri čuvanju klijenta: %v", err)
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
renderujFormuKlijenta(w, PodaciFormeKlijenta{
|
||||
PodaciStranice: model.PodaciStranice{
|
||||
Stranica: "klijenti",
|
||||
NaslovStranice: "Novi klijent",
|
||||
Tema: podesavanja["tema"],
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
LogoTip: podesavanja["logo_tip"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
Korisnik: "Admin",
|
||||
},
|
||||
Klijent: klijent,
|
||||
Greska: "Došlo je do greške pri čuvanju. Pokušajte ponovo.",
|
||||
Izmena: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/klijenti?sacuvano=1", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// IzmeniKlijenta učitava klijenta po ID-u i prikazuje popunjenu formu za izmenu
|
||||
func (h *Handler) IzmeniKlijenta(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := parseID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID klijenta", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
klijent, err := h.KlijentiRepo.DohvatiID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Klijent nije pronađen", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
renderujFormuKlijenta(w, PodaciFormeKlijenta{
|
||||
PodaciStranice: model.PodaciStranice{
|
||||
Stranica: "klijenti",
|
||||
NaslovStranice: "Izmeni klijenta",
|
||||
Tema: podesavanja["tema"],
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
LogoTip: podesavanja["logo_tip"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
Korisnik: "Admin",
|
||||
},
|
||||
Klijent: *klijent,
|
||||
Izmena: true,
|
||||
})
|
||||
}
|
||||
|
||||
// SacuvajIzmenuKlijenta prima POST formu i ažurira postojećeg klijenta u bazi
|
||||
func (h *Handler) SacuvajIzmenuKlijenta(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := parseID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID klijenta", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "Greška pri čitanju forme", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
klijent, greska := parseFormuKlijenta(r)
|
||||
if greska != "" {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
klijent.ID = id
|
||||
renderujFormuKlijenta(w, PodaciFormeKlijenta{
|
||||
PodaciStranice: model.PodaciStranice{
|
||||
Stranica: "klijenti",
|
||||
NaslovStranice: "Izmeni klijenta",
|
||||
Tema: podesavanja["tema"],
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
LogoTip: podesavanja["logo_tip"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
Korisnik: "Admin",
|
||||
},
|
||||
Klijent: klijent,
|
||||
Greska: greska,
|
||||
Izmena: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
klijent.ID = id
|
||||
if err := h.KlijentiRepo.Izmeni(r.Context(), &klijent); err != nil {
|
||||
log.Printf("greška pri čuvanju izmene klijenta: %v", err)
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
renderujFormuKlijenta(w, PodaciFormeKlijenta{
|
||||
PodaciStranice: model.PodaciStranice{
|
||||
Stranica: "klijenti",
|
||||
NaslovStranice: "Izmeni klijenta",
|
||||
Tema: podesavanja["tema"],
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
LogoTip: podesavanja["logo_tip"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
Korisnik: "Admin",
|
||||
},
|
||||
Klijent: klijent,
|
||||
Greska: "Došlo je do greške pri čuvanju. Pokušajte ponovo.",
|
||||
Izmena: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/klijenti?sacuvano=1", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// ObrisiKlijenta prima POST zahtev i briše klijenta po ID-u
|
||||
func (h *Handler) ObrisiKlijenta(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := parseID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID klijenta", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.KlijentiRepo.Obrisi(r.Context(), id); err != nil {
|
||||
http.Error(w, "Greška pri brisanju klijenta", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/klijenti?obrisan=1", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// parseFormuKlijenta čita polja iz HTTP forme, validira ih i vraća model i eventualnu grešku
|
||||
func parseFormuKlijenta(r *http.Request) (model.Klijent, string) {
|
||||
ime := strings.TrimSpace(r.FormValue("ime"))
|
||||
nazivFirme := strings.TrimSpace(r.FormValue("naziv_firme"))
|
||||
|
||||
if ime == "" && nazivFirme == "" {
|
||||
return model.Klijent{}, "Mora biti uneto ime i prezime ili naziv firme."
|
||||
}
|
||||
|
||||
email := strings.TrimSpace(r.FormValue("email"))
|
||||
if email != "" && !strings.Contains(email, "@") {
|
||||
return model.Klijent{}, "Adresa e-pošte nije ispravna."
|
||||
}
|
||||
|
||||
return model.Klijent{
|
||||
Ime: ime,
|
||||
Prezime: strings.TrimSpace(r.FormValue("prezime")),
|
||||
NazivFirme: nazivFirme,
|
||||
PIB: strings.TrimSpace(r.FormValue("pib")),
|
||||
Telefon: strings.TrimSpace(r.FormValue("telefon")),
|
||||
Email: email,
|
||||
Napomena: strings.TrimSpace(r.FormValue("napomena")),
|
||||
}, ""
|
||||
}
|
||||
|
||||
// renderujFormuKlijenta renderuje HTML šablon forme za unos ili izmenu klijenta
|
||||
func renderujFormuKlijenta(w http.ResponseWriter, podaci PodaciFormeKlijenta) {
|
||||
tmpl, err := template.ParseFiles(
|
||||
"web/templates/teme/podrazumevana/base.html",
|
||||
"web/templates/komponente/sidebar.html",
|
||||
"web/templates/komponente/topbar.html",
|
||||
"web/templates/stranice/klijent_forma.html",
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("greška pri učitavanju šablona: %v", err)
|
||||
http.Error(w, "Greška pri učitavanju stranice", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := tmpl.ExecuteTemplate(w, "base", podaci); err != nil {
|
||||
log.Printf("greška pri renderovanju: %v", err)
|
||||
http.Error(w, "Greška pri prikazu stranice", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// Klijent predstavlja jednog klijenta — fizičko lice ili firmu
|
||||
type Klijent struct {
|
||||
ID int64
|
||||
Ime string
|
||||
Prezime string
|
||||
NazivFirme string
|
||||
PIB string
|
||||
Telefon string
|
||||
Email string
|
||||
Napomena string
|
||||
DatumUnosa time.Time
|
||||
}
|
||||
Reference in New Issue
Block a user