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
+24 -15
View File
@@ -22,14 +22,14 @@ func NoviKlijentRepo(db *sql.DB) *KlijentRepo {
// 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, 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 1=1`
args := []any{}
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 + "%"
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
for redovi.Next() {
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(
&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 {
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.Prezime = prezime.String
k.JMBG = jmbg.String
k.TipIdentifikacije = tipIdent.String
k.NazivFirme = nazivFirme.String
k.PIB = pib.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
func (r *KlijentRepo) DohvatiID(ctx context.Context, id int64) (*model.Klijent, error) {
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, `
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(
&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 {
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.Prezime = prezime.String
k.JMBG = jmbg.String
k.TipIdentifikacije = tipIdent.String
k.NazivFirme = nazivFirme.String
k.PIB = pib.String
k.Telefon = telefon.String
@@ -99,10 +101,13 @@ func (r *KlijentRepo) Kreiraj(ctx context.Context, k *model.Klijent) (int64, err
if k.Tip == "" {
k.Tip = "fizicko"
}
if k.TipIdentifikacije == "" {
k.TipIdentifikacije = "jmbg"
}
rezultat, err := r.db.ExecContext(ctx, `
INSERT INTO klijenti (tip, ime, prezime, jmbg, naziv_firme, pib, telefon, email, mesto, napomena)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
k.Tip, nullString(k.Ime), nullString(k.Prezime), nullString(k.JMBG),
INSERT INTO klijenti (tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
k.Tip, nullString(k.Ime), nullString(k.Prezime), nullString(k.JMBG), k.TipIdentifikacije,
nullString(k.NazivFirme), nullString(k.PIB), nullString(k.Telefon),
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 == "" {
k.Tip = "fizicko"
}
if k.TipIdentifikacije == "" {
k.TipIdentifikacije = "jmbg"
}
_, err := r.db.ExecContext(ctx, `
UPDATE klijenti SET
tip = ?, ime = ?, prezime = ?, jmbg = ?, naziv_firme = ?,
tip = ?, ime = ?, prezime = ?, jmbg = ?, tip_identifikacije = ?, naziv_firme = ?,
pib = ?, telefon = ?, email = ?, mesto = ?, napomena = ?
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.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)
func (r *KlijentRepo) ListaFilter(ctx context.Context, filter db.KlijentFilter) ([]model.Klijent, error) {
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
WHERE 1=1`
@@ -178,9 +186,9 @@ func (r *KlijentRepo) ListaFilter(ctx context.Context, filter db.KlijentFilter)
var rezultat []model.Klijent
for redovi.Next() {
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(
&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 {
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.Prezime = prezime.String
k.JMBG = jmbg.String
k.TipIdentifikacije = tipIdent.String
k.NazivFirme = nazivFirme.String
k.PIB = pib.String
k.Telefon = telefon.String
+7
View File
@@ -300,11 +300,18 @@ func parseFormuKlijenta(r *http.Request) (model.Klijent, string) {
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{
Tip: tip,
Ime: ime,
Prezime: strings.TrimSpace(r.FormValue("prezime")),
JMBG: strings.TrimSpace(r.FormValue("jmbg")),
TipIdentifikacije: tipIdent,
NazivFirme: nazivFirme,
PIB: strings.TrimSpace(r.FormValue("pib")),
Telefon: strings.TrimSpace(r.FormValue("telefon")),
+7
View File
@@ -619,11 +619,18 @@ func (h *Handler) mozdaKreirajKlijenta(ctx context.Context, r *http.Request, nal
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{
Tip: tip,
Ime: ime,
Prezime: strings.TrimSpace(r.FormValue("prezime")),
JMBG: strings.TrimSpace(r.FormValue("jmbg")),
TipIdentifikacije: tipIdent,
NazivFirme: nazivFirme,
PIB: strings.TrimSpace(r.FormValue("pib")),
Telefon: strings.TrimSpace(r.FormValue("telefon")),
+12 -1
View File
@@ -11,7 +11,10 @@ type Klijent struct {
Tip string
Ime string
Prezime string
JMBG string
JMBG string // vrednost identifikacionog broja (JMBG ili broj lične karte)
// TipIdentifikacije govori šta je u polju JMBG: "jmbg" ili "licna_karta".
// Validnost vrednosti (JMBG = 13 cifara, format broja lične karte) proverava se kasnije.
TipIdentifikacije string
NazivFirme string
PIB string
Telefon string
@@ -28,3 +31,11 @@ func (k Klijent) PunoIme() string {
}
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';
+12 -3
View File
@@ -77,10 +77,19 @@
placeholder="npr. Petrović">
</div>
</div>
<div style="max-width:260px;">
<label class="polje-labela">JMBG</label>
<div class="forma-grid-2">
<div>
<label class="polje-labela">Vrsta dokumenta</label>
<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="13 cifara" maxlength="13">
placeholder="JMBG (13 cifara) ili broj lične karte">
</div>
</div>
</div>
+23 -1
View File
@@ -161,16 +161,26 @@
<input type="text" name="prezime" x-model="noviPrezime" style="width:100%;">
</div>
</div>
<div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label class="polje-labela">JMBG</label>
<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>
</template>
<!-- pravno lice -->
<template x-if="tip==='pravno'">
<div class="kolona" style="gap:12px;">
<div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label class="polje-labela">Naziv firme</label>
<input type="text" name="naziv_firme" x-model="noviNaziv" style="width:100%;">
@@ -180,6 +190,18 @@
<input type="text" name="pib" style="width:100%;">
</div>
</div>
<!-- kontakt osoba u firmi — čuva se u poljima Ime/Prezime klijenta -->
<div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<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>
</template>
<!-- zajednička polja -->