Servis novi nalog: kartice, izbor/kreiranje klijenta, novi broj naloga
- Forma naloga razdvojena u zasebne kartice (Uređaj, Klijent, Status i datumi, Cene/Napomena) - Nova kartica Klijent: radio fizičko/pravno, živa pretraga postojećih (Alpine, CSP-safe) po punom imenu/imenu/prezimenu odnosno nazivu firme; izbor postojećeg ili unos novog - Enter u pretrazi bez izbora popunjava polja novog klijenta (ime/prezime ili naziv firme) - SacuvajNalog/SacuvajIzmenaNaloga: helper mozdaKreirajKlijenta kreira novog klijenta iz forme kada nije izabran postojeći - Broj naloga: format SN-MMGG-NNN sa mesečnim resetom brojača (001-999) - Dodat CSRF token u formu naloga (POST je ranije vraćao 403) - Šablonski helper json za ugradnju liste klijenata u Alpine
This commit is contained in:
@@ -30,11 +30,15 @@ func NoviServisRepo(db *sql.DB) *ServisRepo {
|
||||
return &ServisRepo{db: db}
|
||||
}
|
||||
|
||||
// SledeciBroj generiše sledeći broj naloga u formatu SN-GGGG-NNNN
|
||||
// SledeciBroj generiše sledeći broj naloga u formatu SN-MMGG-NNN
|
||||
// (MM mesec, GG dvocifrena godina); brojač NNN se resetuje svakog meseca
|
||||
func (r *ServisRepo) SledeciBroj(ctx context.Context) (string, error) {
|
||||
godina := time.Now().Year()
|
||||
uzorak := fmt.Sprintf("SN-%d-%%", godina)
|
||||
sada := time.Now()
|
||||
// prefiks "SN-MMGG-" je dug 8 karaktera, pa brojač počinje od 9. karaktera
|
||||
prefiks := fmt.Sprintf("SN-%02d%02d-", int(sada.Month()), sada.Year()%100)
|
||||
uzorak := prefiks + "%"
|
||||
|
||||
// COALESCE(MAX, 0)+1 → prvi nalog u mesecu dobija 001
|
||||
var sledeci int
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT COALESCE(MAX(CAST(SUBSTR(broj_naloga, 9) AS INTEGER)), 0) + 1
|
||||
@@ -44,7 +48,7 @@ func (r *ServisRepo) SledeciBroj(ctx context.Context) (string, error) {
|
||||
return "", fmt.Errorf("ntech: ServisRepo.SledeciBroj: %w", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("SN-%d-%04d", godina, sledeci), nil
|
||||
return fmt.Sprintf("%s%03d", prefiks, sledeci), nil
|
||||
}
|
||||
|
||||
// Lista vraća listu servisnih naloga sa imenom klijenta, opcionim filterima
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
@@ -65,6 +66,15 @@ var sablonskeFunkcije = template.FuncMap{
|
||||
},
|
||||
// telefon formatira srpski broj telefona radi lakšeg čitanja: "0641234567" → "064 123 4567"
|
||||
"telefon": formatirajTelefon,
|
||||
// json serijalizuje vrednost u JSON za ugradnju u <script> (npr. Alpine x-data);
|
||||
// template.JS sprečava da html/template dodatno escapuje rezultat
|
||||
"json": func(v any) (template.JS, error) {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ntech: json helper: %w", err)
|
||||
}
|
||||
return template.JS(b), nil
|
||||
},
|
||||
// statusPre vraća true ako je `a` pre `b` u redosledu statusa
|
||||
"statusPre": func(a, b string, statusi []string) bool {
|
||||
ia, ib := -1, -1
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -162,6 +163,26 @@ func (h *Handler) SacuvajNalog(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// ako nije izabran postojeći klijent, eventualno kreiraj novog iz polja forme
|
||||
if greska := h.mozdaKreirajKlijenta(r.Context(), r, &nalog); greska != "" {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
klijenti, _ := h.KlijentiRepo.Lista(r.Context(), "")
|
||||
tehnicari, _ := h.KorisniciRepo.Lista(r.Context())
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "servis"
|
||||
ps.NaslovStranice = "Novi nalog"
|
||||
h.renderujFormuNaloga(w, PodaciFormeNaloga{
|
||||
PodaciStranice: ps,
|
||||
Nalog: nalog,
|
||||
Klijenti: klijenti,
|
||||
Tehnicari: tehnicari,
|
||||
SviStatusi: model.SviStatusi,
|
||||
Greska: greska,
|
||||
Izmena: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
id, err := h.ServisRepo.Kreiraj(r.Context(), &nalog)
|
||||
if err != nil {
|
||||
slog.Error("greška pri čuvanju naloga", "error", err)
|
||||
@@ -272,6 +293,27 @@ func (h *Handler) SacuvajIzmenaNaloga(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
nalog.ID = id
|
||||
|
||||
// ako nije izabran postojeći klijent, eventualno kreiraj novog iz polja forme
|
||||
if greska := h.mozdaKreirajKlijenta(r.Context(), r, &nalog); greska != "" {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
klijenti, _ := h.KlijentiRepo.Lista(r.Context(), "")
|
||||
tehnicari, _ := h.KorisniciRepo.Lista(r.Context())
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "servis"
|
||||
ps.NaslovStranice = "Izmeni nalog"
|
||||
h.renderujFormuNaloga(w, PodaciFormeNaloga{
|
||||
PodaciStranice: ps,
|
||||
Nalog: nalog,
|
||||
Klijenti: klijenti,
|
||||
Tehnicari: tehnicari,
|
||||
SviStatusi: model.SviStatusi,
|
||||
Greska: greska,
|
||||
Izmena: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.ServisRepo.Izmeni(r.Context(), &nalog); err != nil {
|
||||
slog.Error("greška pri čuvanju izmene naloga", "error", err)
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
@@ -548,6 +590,56 @@ func parseFormuNaloga(r *http.Request) (model.ServisniNalog, string) {
|
||||
return nalog, ""
|
||||
}
|
||||
|
||||
// mozdaKreirajKlijenta kreira novog klijenta iz polja forme naloga kada nije
|
||||
// izabran postojeći (klijent_id prazno), a uneto je ime (fizičko) ili naziv firme
|
||||
// (pravno). Klijent je opcioni — ako nema dovoljno podataka, nalog ostaje bez njega.
|
||||
// Vraća srpsku poruku o grešci za prikaz, ili prazan string ako je sve u redu.
|
||||
func (h *Handler) mozdaKreirajKlijenta(ctx context.Context, r *http.Request, nalog *model.ServisniNalog) string {
|
||||
if nalog.KlijentID != nil {
|
||||
return "" // izabran postojeći klijent — ništa ne kreiramo
|
||||
}
|
||||
|
||||
tip := r.FormValue("tip")
|
||||
if tip != "fizicko" && tip != "pravno" {
|
||||
tip = "fizicko"
|
||||
}
|
||||
ime := strings.TrimSpace(r.FormValue("ime"))
|
||||
nazivFirme := strings.TrimSpace(r.FormValue("naziv_firme"))
|
||||
|
||||
// bez minimalnih podataka nalog ostaje bez klijenta
|
||||
if tip == "fizicko" && ime == "" {
|
||||
return ""
|
||||
}
|
||||
if tip == "pravno" && nazivFirme == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
email := strings.TrimSpace(r.FormValue("email"))
|
||||
if email != "" && !strings.Contains(email, "@") {
|
||||
return "Adresa e-pošte nije ispravna."
|
||||
}
|
||||
|
||||
klijent := model.Klijent{
|
||||
Tip: tip,
|
||||
Ime: ime,
|
||||
Prezime: strings.TrimSpace(r.FormValue("prezime")),
|
||||
JMBG: strings.TrimSpace(r.FormValue("jmbg")),
|
||||
NazivFirme: nazivFirme,
|
||||
PIB: strings.TrimSpace(r.FormValue("pib")),
|
||||
Telefon: strings.TrimSpace(r.FormValue("telefon")),
|
||||
Email: email,
|
||||
Mesto: strings.TrimSpace(r.FormValue("mesto")),
|
||||
}
|
||||
|
||||
id, err := h.KlijentiRepo.Kreiraj(ctx, &klijent)
|
||||
if err != nil {
|
||||
slog.Error("greška pri kreiranju klijenta iz naloga", "error", err)
|
||||
return "Došlo je do greške pri čuvanju klijenta. Pokušajte ponovo."
|
||||
}
|
||||
nalog.KlijentID = &id
|
||||
return ""
|
||||
}
|
||||
|
||||
// defaultGarancija vraća datum garancije na osnovu datuma prijema i podešavanja;
|
||||
// vraća nil ako je rok 0 ili podešavanje nedostaje
|
||||
func defaultGarancija(datumPrijema time.Time, podesavanja map[string]string) *time.Time {
|
||||
|
||||
Reference in New Issue
Block a user