Klijent: izbor JMBG/Br. lične karte i kontakt osoba za pravno lice

- Nova kolona tip_identifikacije ('jmbg'/'licna_karta'), migracija 062; postojeći klijenti podrazumevano 'jmbg'
- model.Klijent: polje TipIdentifikacije + helper OznakaIdentifikacije za prikaz
- sqlite/klijent.go: kolona u svim upitima (Lista, DohvatiID, ListaFilter, Kreiraj, Izmeni)
- parseFormuKlijenta i mozdaKreirajKlijenta čitaju tip_identifikacije (fallback 'jmbg')
- Forme klijenta i servisnog naloga: selektor vrste dokumenta + polje vrednosti
- Servisni nalog: kontakt osoba (ime/prezime) za pravno lice
- Validnost broja (JMBG 13 cifara, broj lične karte) ostavljena za kasnije
This commit is contained in:
2026-06-21 12:28:39 +02:00
parent 045ea766f1
commit 8bdefd0561
7 changed files with 129 additions and 60 deletions
+25 -16
View File
@@ -22,15 +22,15 @@ func NoviKlijentRepo(db *sql.DB) *KlijentRepo {
// Lista vraća listu klijenata sa opcionom pretragom po imenu, prezimenu ili nazivu firme // 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) { func (r *KlijentRepo) Lista(ctx context.Context, pretraga string) ([]model.Klijent, error) {
upit := ` upit := `
SELECT id, tip, ime, prezime, jmbg, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
FROM klijenti FROM klijenti
WHERE 1=1` WHERE 1=1`
args := []any{} args := []any{}
if pretraga != "" { if pretraga != "" {
upit += " AND (ime LIKE ? OR prezime LIKE ? OR (ime || ' ' || prezime) LIKE ? OR naziv_firme LIKE ? OR telefon LIKE ? OR email LIKE ?)" upit += " AND (ime LIKE ? OR prezime LIKE ? OR (ime || ' ' || prezime) LIKE ? OR naziv_firme LIKE ? OR telefon LIKE ? OR email LIKE ?)"
p := "%" + pretraga + "%" p := "%" + pretraga + "%"
args = append(args, p, p, p, p, p, p) args = append(args, p, p, p, p, p, p)
} }
@@ -45,9 +45,9 @@ upit += " AND (ime LIKE ? OR prezime LIKE ? OR (ime || ' ' || prezime) LIKE ? OR
var rezultat []model.Klijent var rezultat []model.Klijent
for redovi.Next() { for redovi.Next() {
var k model.Klijent var k model.Klijent
var ime, prezime, jmbg, nazivFirme, pib, telefon, email, mesto, napomena sql.NullString var ime, prezime, jmbg, tipIdent, nazivFirme, pib, telefon, email, mesto, napomena sql.NullString
err := redovi.Scan( err := redovi.Scan(
&k.ID, &k.Tip, &ime, &prezime, &jmbg, &nazivFirme, &pib, &telefon, &email, &mesto, &napomena, &k.DatumUnosa, &k.ID, &k.Tip, &ime, &prezime, &jmbg, &tipIdent, &nazivFirme, &pib, &telefon, &email, &mesto, &napomena, &k.DatumUnosa,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("ntech: KlijentRepo.Lista: scan: %w", err) return nil, fmt.Errorf("ntech: KlijentRepo.Lista: scan: %w", err)
@@ -55,6 +55,7 @@ upit += " AND (ime LIKE ? OR prezime LIKE ? OR (ime || ' ' || prezime) LIKE ? OR
k.Ime = ime.String k.Ime = ime.String
k.Prezime = prezime.String k.Prezime = prezime.String
k.JMBG = jmbg.String k.JMBG = jmbg.String
k.TipIdentifikacije = tipIdent.String
k.NazivFirme = nazivFirme.String k.NazivFirme = nazivFirme.String
k.PIB = pib.String k.PIB = pib.String
k.Telefon = telefon.String k.Telefon = telefon.String
@@ -70,12 +71,12 @@ upit += " AND (ime LIKE ? OR prezime LIKE ? OR (ime || ' ' || prezime) LIKE ? OR
// DohvatiID vraća jednog klijenta po ID-u // DohvatiID vraća jednog klijenta po ID-u
func (r *KlijentRepo) DohvatiID(ctx context.Context, id int64) (*model.Klijent, error) { func (r *KlijentRepo) DohvatiID(ctx context.Context, id int64) (*model.Klijent, error) {
var k model.Klijent var k model.Klijent
var ime, prezime, jmbg, nazivFirme, pib, telefon, email, mesto, napomena sql.NullString var ime, prezime, jmbg, tipIdent, nazivFirme, pib, telefon, email, mesto, napomena sql.NullString
err := r.db.QueryRowContext(ctx, ` err := r.db.QueryRowContext(ctx, `
SELECT id, tip, ime, prezime, jmbg, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
FROM klijenti WHERE id = ?`, id).Scan( FROM klijenti WHERE id = ?`, id).Scan(
&k.ID, &k.Tip, &ime, &prezime, &jmbg, &nazivFirme, &pib, &telefon, &email, &mesto, &napomena, &k.DatumUnosa, &k.ID, &k.Tip, &ime, &prezime, &jmbg, &tipIdent, &nazivFirme, &pib, &telefon, &email, &mesto, &napomena, &k.DatumUnosa,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("ntech: KlijentRepo.DohvatiID: %w", err) return nil, fmt.Errorf("ntech: KlijentRepo.DohvatiID: %w", err)
@@ -84,6 +85,7 @@ func (r *KlijentRepo) DohvatiID(ctx context.Context, id int64) (*model.Klijent,
k.Ime = ime.String k.Ime = ime.String
k.Prezime = prezime.String k.Prezime = prezime.String
k.JMBG = jmbg.String k.JMBG = jmbg.String
k.TipIdentifikacije = tipIdent.String
k.NazivFirme = nazivFirme.String k.NazivFirme = nazivFirme.String
k.PIB = pib.String k.PIB = pib.String
k.Telefon = telefon.String k.Telefon = telefon.String
@@ -99,10 +101,13 @@ func (r *KlijentRepo) Kreiraj(ctx context.Context, k *model.Klijent) (int64, err
if k.Tip == "" { if k.Tip == "" {
k.Tip = "fizicko" k.Tip = "fizicko"
} }
if k.TipIdentifikacije == "" {
k.TipIdentifikacije = "jmbg"
}
rezultat, err := r.db.ExecContext(ctx, ` rezultat, err := r.db.ExecContext(ctx, `
INSERT INTO klijenti (tip, ime, prezime, jmbg, naziv_firme, pib, telefon, email, mesto, napomena) INSERT INTO klijenti (tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
k.Tip, nullString(k.Ime), nullString(k.Prezime), nullString(k.JMBG), k.Tip, nullString(k.Ime), nullString(k.Prezime), nullString(k.JMBG), k.TipIdentifikacije,
nullString(k.NazivFirme), nullString(k.PIB), nullString(k.Telefon), nullString(k.NazivFirme), nullString(k.PIB), nullString(k.Telefon),
nullString(k.Email), nullString(k.Mesto), nullString(k.Napomena), nullString(k.Email), nullString(k.Mesto), nullString(k.Napomena),
) )
@@ -123,12 +128,15 @@ func (r *KlijentRepo) Izmeni(ctx context.Context, k *model.Klijent) error {
if k.Tip == "" { if k.Tip == "" {
k.Tip = "fizicko" k.Tip = "fizicko"
} }
if k.TipIdentifikacije == "" {
k.TipIdentifikacije = "jmbg"
}
_, err := r.db.ExecContext(ctx, ` _, err := r.db.ExecContext(ctx, `
UPDATE klijenti SET UPDATE klijenti SET
tip = ?, ime = ?, prezime = ?, jmbg = ?, naziv_firme = ?, tip = ?, ime = ?, prezime = ?, jmbg = ?, tip_identifikacije = ?, naziv_firme = ?,
pib = ?, telefon = ?, email = ?, mesto = ?, napomena = ? pib = ?, telefon = ?, email = ?, mesto = ?, napomena = ?
WHERE id = ?`, WHERE id = ?`,
k.Tip, nullString(k.Ime), nullString(k.Prezime), nullString(k.JMBG), k.Tip, nullString(k.Ime), nullString(k.Prezime), nullString(k.JMBG), k.TipIdentifikacije,
nullString(k.NazivFirme), nullString(k.PIB), nullString(k.Telefon), nullString(k.NazivFirme), nullString(k.PIB), nullString(k.Telefon),
nullString(k.Email), nullString(k.Mesto), nullString(k.Napomena), k.ID, nullString(k.Email), nullString(k.Mesto), nullString(k.Napomena), k.ID,
) )
@@ -142,7 +150,7 @@ func (r *KlijentRepo) Izmeni(ctx context.Context, k *model.Klijent) error {
// ListaFilter vraća listu klijenata sa limitom i offsetom (paginacija) // ListaFilter vraća listu klijenata sa limitom i offsetom (paginacija)
func (r *KlijentRepo) ListaFilter(ctx context.Context, filter db.KlijentFilter) ([]model.Klijent, error) { func (r *KlijentRepo) ListaFilter(ctx context.Context, filter db.KlijentFilter) ([]model.Klijent, error) {
upit := ` upit := `
SELECT id, tip, ime, prezime, jmbg, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
FROM klijenti FROM klijenti
WHERE 1=1` WHERE 1=1`
@@ -178,9 +186,9 @@ func (r *KlijentRepo) ListaFilter(ctx context.Context, filter db.KlijentFilter)
var rezultat []model.Klijent var rezultat []model.Klijent
for redovi.Next() { for redovi.Next() {
var k model.Klijent var k model.Klijent
var ime, prezime, jmbg, nazivFirme, pib, telefon, email, mesto, napomena sql.NullString var ime, prezime, jmbg, tipIdent, nazivFirme, pib, telefon, email, mesto, napomena sql.NullString
err := redovi.Scan( err := redovi.Scan(
&k.ID, &k.Tip, &ime, &prezime, &jmbg, &nazivFirme, &pib, &telefon, &email, &mesto, &napomena, &k.DatumUnosa, &k.ID, &k.Tip, &ime, &prezime, &jmbg, &tipIdent, &nazivFirme, &pib, &telefon, &email, &mesto, &napomena, &k.DatumUnosa,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("ntech: KlijentRepo.ListaFilter: scan: %w", err) return nil, fmt.Errorf("ntech: KlijentRepo.ListaFilter: scan: %w", err)
@@ -188,6 +196,7 @@ func (r *KlijentRepo) ListaFilter(ctx context.Context, filter db.KlijentFilter)
k.Ime = ime.String k.Ime = ime.String
k.Prezime = prezime.String k.Prezime = prezime.String
k.JMBG = jmbg.String k.JMBG = jmbg.String
k.TipIdentifikacije = tipIdent.String
k.NazivFirme = nazivFirme.String k.NazivFirme = nazivFirme.String
k.PIB = pib.String k.PIB = pib.String
k.Telefon = telefon.String k.Telefon = telefon.String
+17 -10
View File
@@ -300,17 +300,24 @@ func parseFormuKlijenta(r *http.Request) (model.Klijent, string) {
return model.Klijent{Tip: tip}, "Adresa e-pošte nije ispravna." return model.Klijent{Tip: tip}, "Adresa e-pošte nije ispravna."
} }
// tip identifikacionog broja: 'jmbg' ili 'licna_karta' (podrazumevano jmbg)
tipIdent := r.FormValue("tip_identifikacije")
if tipIdent != "jmbg" && tipIdent != "licna_karta" {
tipIdent = "jmbg"
}
return model.Klijent{ return model.Klijent{
Tip: tip, Tip: tip,
Ime: ime, Ime: ime,
Prezime: strings.TrimSpace(r.FormValue("prezime")), Prezime: strings.TrimSpace(r.FormValue("prezime")),
JMBG: strings.TrimSpace(r.FormValue("jmbg")), JMBG: strings.TrimSpace(r.FormValue("jmbg")),
NazivFirme: nazivFirme, TipIdentifikacije: tipIdent,
PIB: strings.TrimSpace(r.FormValue("pib")), NazivFirme: nazivFirme,
Telefon: strings.TrimSpace(r.FormValue("telefon")), PIB: strings.TrimSpace(r.FormValue("pib")),
Email: email, Telefon: strings.TrimSpace(r.FormValue("telefon")),
Mesto: strings.TrimSpace(r.FormValue("mesto")), Email: email,
Napomena: strings.TrimSpace(r.FormValue("napomena")), Mesto: strings.TrimSpace(r.FormValue("mesto")),
Napomena: strings.TrimSpace(r.FormValue("napomena")),
}, "" }, ""
} }
+16 -9
View File
@@ -619,16 +619,23 @@ func (h *Handler) mozdaKreirajKlijenta(ctx context.Context, r *http.Request, nal
return "Adresa e-pošte nije ispravna." return "Adresa e-pošte nije ispravna."
} }
// tip identifikacionog broja: 'jmbg' ili 'licna_karta' (podrazumevano jmbg)
tipIdent := r.FormValue("tip_identifikacije")
if tipIdent != "jmbg" && tipIdent != "licna_karta" {
tipIdent = "jmbg"
}
klijent := model.Klijent{ klijent := model.Klijent{
Tip: tip, Tip: tip,
Ime: ime, Ime: ime,
Prezime: strings.TrimSpace(r.FormValue("prezime")), Prezime: strings.TrimSpace(r.FormValue("prezime")),
JMBG: strings.TrimSpace(r.FormValue("jmbg")), JMBG: strings.TrimSpace(r.FormValue("jmbg")),
NazivFirme: nazivFirme, TipIdentifikacije: tipIdent,
PIB: strings.TrimSpace(r.FormValue("pib")), NazivFirme: nazivFirme,
Telefon: strings.TrimSpace(r.FormValue("telefon")), PIB: strings.TrimSpace(r.FormValue("pib")),
Email: email, Telefon: strings.TrimSpace(r.FormValue("telefon")),
Mesto: strings.TrimSpace(r.FormValue("mesto")), Email: email,
Mesto: strings.TrimSpace(r.FormValue("mesto")),
} }
id, err := h.KlijentiRepo.Kreiraj(ctx, &klijent) id, err := h.KlijentiRepo.Kreiraj(ctx, &klijent)
+23 -12
View File
@@ -7,18 +7,21 @@ import (
// Klijent predstavlja jednog klijenta — fizičko lice ili firmu // Klijent predstavlja jednog klijenta — fizičko lice ili firmu
type Klijent struct { type Klijent struct {
ID int64 ID int64
Tip string Tip string
Ime string Ime string
Prezime string Prezime string
JMBG string JMBG string // vrednost identifikacionog broja (JMBG ili broj lične karte)
NazivFirme string // TipIdentifikacije govori šta je u polju JMBG: "jmbg" ili "licna_karta".
PIB string // Validnost vrednosti (JMBG = 13 cifara, format broja lične karte) proverava se kasnije.
Telefon string TipIdentifikacije string
Email string NazivFirme string
Mesto string PIB string
Napomena string Telefon string
DatumUnosa time.Time Email string
Mesto string
Napomena string
DatumUnosa time.Time
} }
// PunoIme vraća ime i prezime za fizičko lice, ili naziv firme za pravno // PunoIme vraća ime i prezime za fizičko lice, ili naziv firme za pravno
@@ -28,3 +31,11 @@ func (k Klijent) PunoIme() string {
} }
return strings.TrimSpace(k.Ime + " " + k.Prezime) return strings.TrimSpace(k.Ime + " " + k.Prezime)
} }
// OznakaIdentifikacije vraća labelu za prikaz: „Br. lične karte" ili „JMBG" (podrazumevano)
func (k Klijent) OznakaIdentifikacije() string {
if k.TipIdentifikacije == "licna_karta" {
return "Br. lične karte"
}
return "JMBG"
}
@@ -0,0 +1,4 @@
-- Dodaje tip identifikacionog broja klijenta (fizičko lice): 'jmbg' ili 'licna_karta'.
-- Vrednost broja se i dalje čuva u koloni jmbg; ova kolona govori šta je uneto.
-- Postojeći klijenti podrazumevano dobijaju 'jmbg' jer je to do sada i bio JMBG.
ALTER TABLE klijenti ADD COLUMN tip_identifikacije TEXT NOT NULL DEFAULT 'jmbg';
+13 -4
View File
@@ -77,10 +77,19 @@
placeholder="npr. Petrović"> placeholder="npr. Petrović">
</div> </div>
</div> </div>
<div style="max-width:260px;"> <div class="forma-grid-2">
<label class="polje-labela">JMBG</label> <div>
<input type="text" name="jmbg" value="{{.Klijent.JMBG}}" <label class="polje-labela">Vrsta dokumenta</label>
placeholder="13 cifara" maxlength="13"> <select name="tip_identifikacije">
<option value="jmbg" {{if ne .Klijent.TipIdentifikacije "licna_karta"}}selected{{end}}>JMBG</option>
<option value="licna_karta" {{if eq .Klijent.TipIdentifikacije "licna_karta"}}selected{{end}}>Br. lične karte</option>
</select>
</div>
<div>
<label class="polje-labela">JMBG / Br. lične karte</label>
<input type="text" name="jmbg" value="{{.Klijent.JMBG}}"
placeholder="JMBG (13 cifara) ili broj lične karte">
</div>
</div> </div>
</div> </div>
+31 -9
View File
@@ -161,9 +161,18 @@
<input type="text" name="prezime" x-model="noviPrezime" style="width:100%;"> <input type="text" name="prezime" x-model="noviPrezime" style="width:100%;">
</div> </div>
</div> </div>
<div> <div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<label class="polje-labela">JMBG</label> <div>
<input type="text" name="jmbg" style="width:100%;"> <label class="polje-labela">Vrsta dokumenta</label>
<select name="tip_identifikacije" style="width:100%;">
<option value="jmbg">JMBG</option>
<option value="licna_karta">Br. lične karte</option>
</select>
</div>
<div>
<label class="polje-labela">JMBG / Br. lične karte</label>
<input type="text" name="jmbg" style="width:100%;">
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -171,13 +180,26 @@
<!-- pravno lice --> <!-- pravno lice -->
<template x-if="tip==='pravno'"> <template x-if="tip==='pravno'">
<div class="kolona" style="gap:12px;"> <div class="kolona" style="gap:12px;">
<div> <div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<label class="polje-labela">Naziv firme</label> <div>
<input type="text" name="naziv_firme" x-model="noviNaziv" style="width:100%;"> <label class="polje-labela">Naziv firme</label>
<input type="text" name="naziv_firme" x-model="noviNaziv" style="width:100%;">
</div>
<div>
<label class="polje-labela">PIB</label>
<input type="text" name="pib" style="width:100%;">
</div>
</div> </div>
<div> <!-- kontakt osoba u firmi — čuva se u poljima Ime/Prezime klijenta -->
<label class="polje-labela">PIB</label> <div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<input type="text" name="pib" style="width:100%;"> <div>
<label class="polje-labela">Kontakt osoba — ime</label>
<input type="text" name="ime" x-model="noviIme" style="width:100%;">
</div>
<div>
<label class="polje-labela">Kontakt osoba — prezime</label>
<input type="text" name="prezime" x-model="noviPrezime" style="width:100%;">
</div>
</div> </div>
</div> </div>
</template> </template>