Servis: inline promena statusa direktno iz detalja naloga

- Novi POST /servis/{id}/status ruta sa dozvolom servis.izmeni
- AzurirajStatus metoda u repou — menja samo status; pri prelasku u
  Završeno/Preuzeto automatski postavlja datum_zavrsetka ako nije već setovan
- Dropdown sa svim statusima i dugme „Promeni" u zaglavlju stranice detalja
This commit is contained in:
2026-06-20 00:45:25 +02:00
parent b65fb02146
commit 41e6282404
5 changed files with 62 additions and 1 deletions
+1
View File
@@ -339,6 +339,7 @@ func main() {
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}", h.DetaljiNaloga)
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}/stampa", h.StampaServisa)
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}/otpremnica", h.StampaOtpremnice)
r.With(doz("servis.izmeni")).Post("/servis/{id}/status", h.PromeniStatus)
r.With(doz("servis.izmeni")).Post("/servis/{id}/delovi", h.DodajDeloNalogu)
r.With(doz("servis.izmeni")).Post("/servis/{id}/delovi/{deo_id}/obrisi", h.ObrisiDeloNaloga)
r.Get("/izvestaji", h.Izvestaji)
+1
View File
@@ -114,6 +114,7 @@ type ServisRepository interface {
DohvatiID(ctx context.Context, id int64) (*model.ServisniNalog, error)
Kreiraj(ctx context.Context, n *model.ServisniNalog) (int64, error)
Izmeni(ctx context.Context, n *model.ServisniNalog) error
AzurirajStatus(ctx context.Context, id int64, status string) error
Obrisi(ctx context.Context, id int64) error
SledeciBroj(ctx context.Context) (string, error)
}
+18
View File
@@ -152,6 +152,24 @@ func (r *ServisRepo) Izmeni(ctx context.Context, n *model.ServisniNalog) error {
return nil
}
// AzurirajStatus menja samo status naloga; ako nalog prelazi u završno stanje
// i datum_zavrsetka još nije postavljen, automatski ga postavlja na danas.
func (r *ServisRepo) AzurirajStatus(ctx context.Context, id int64, status string) error {
var upit string
if status == model.StatusZavrseno || status == model.StatusPreuzeto {
upit = `UPDATE servisni_nalozi SET status = ?,
datum_zavrsetka = COALESCE(datum_zavrsetka, date('now', 'localtime'))
WHERE id = ?`
} else {
upit = `UPDATE servisni_nalozi SET status = ? WHERE id = ?`
}
_, err := r.db.ExecContext(ctx, upit, status, id)
if err != nil {
return fmt.Errorf("ntech: ServisRepo.AzurirajStatus: %w", err)
}
return nil
}
// Obrisi briše servisni nalog po ID-u
func (r *ServisRepo) Obrisi(ctx context.Context, id int64) error {
_, err := r.db.ExecContext(ctx, "DELETE FROM servisni_nalozi WHERE id = ?", id)
+30
View File
@@ -49,6 +49,7 @@ type PodaciDetaljiNaloga struct {
UkupnoDelovi float64
UkupnoSve float64
PreostaloSve float64
SviStatusi []string
}
// Servis renderuje listu servisnih naloga sa opcionom pretragom i filterom statusa
@@ -389,6 +390,7 @@ func (h *Handler) DetaljiNaloga(w http.ResponseWriter, r *http.Request) {
UkupnoDelovi: ukupnoDelovi,
UkupnoSve: ukupnoSve,
PreostaloSve: preostaloSve,
SviStatusi: model.SviStatusi,
}
h.renderujTemplate(w, "servis_detalji", podaci)
@@ -756,3 +758,31 @@ func (h *Handler) StampaOtpremnice(w http.ResponseWriter, r *http.Request) {
PIB: podesavanja["pib"],
})
}
// PromeniStatus obrađuje POST /servis/{id}/status i menja samo status naloga
func (h *Handler) PromeniStatus(w http.ResponseWriter, r *http.Request) {
id, err := parseID(chi.URLParam(r, "id"))
if err != nil {
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Greška pri čitanju forme", http.StatusBadRequest)
return
}
noviStatus := strings.TrimSpace(r.FormValue("status"))
dozvoljenStatusi := map[string]bool{}
for _, s := range model.SviStatusi {
dozvoljenStatusi[s] = true
}
if !dozvoljenStatusi[noviStatus] {
http.Error(w, "Nepoznat status", http.StatusBadRequest)
return
}
if err := h.ServisRepo.AzurirajStatus(r.Context(), id, noviStatus); err != nil {
slog.Error("greška pri promeni statusa naloga", "id", id, "error", err)
http.Error(w, "Greška pri promeni statusa", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/servis/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther)
}
@@ -43,7 +43,18 @@
<span style="font-size:20px;font-weight:600;color:var(--tekst-glavni);font-family:monospace;">
{{.Nalog.BrojNaloga}}
</span>
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
{{template "status-badge-detalji" .Nalog.Status}}
<form method="post" action="/servis/{{.Nalog.ID}}/status" style="display:flex;align-items:center;gap:6px;">
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
<select name="status" style="font-size:13px;padding:4px 8px;border-radius:6px;border:0.5px solid var(--ivica);background:var(--kartica-pozadina);color:var(--tekst-glavni);cursor:pointer;">
{{range .SviStatusi}}
<option value="{{.}}"{{if eq . $.Nalog.Status}} selected{{end}}>{{.}}</option>
{{end}}
</select>
<button type="submit" class="btn-sekundarno" style="font-size:13px;padding:4px 12px;">Promeni</button>
</form>
</div>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<a href="/servis/{{.Nalog.ID}}/stampa" target="_blank" class="btn-sekundarno">