Servis: tok dijagnostike/popravke, kartice Utvrđeni kvar i Urađeno, cena dijagnostike
- Cena dijagnostike u Podešavanja → Servis; na nalogu se predlaže iz podešavanja - Odbijanje popravke: status Završeno + naplata samo dijagnostike (popravka_odbijena), sklanjaju se Radovi/Delovi, Ukupno = dijagnostika - Kartica Nalaz dijagnostike preimenovana u Utvrđeni kvar; editabilna samo u dijagnostici, kasnije readonly - Nova kartica Urađeno (polje uradjeno) — upis dok je U popravci/Čeka delove, posle readonly - Dugmad statusa: Završi u U popravci; promena statusa osvežava ceo sadržaj - Predračun dostupan od Primljeno; radni nalog u U dijagnostici/Čeka delove - Dropdown radova izostavlja već dodate usluge; kartica radova se osvežava pri izmeni - Javni status: badge U dijagnostici, prikaz utvrđenog kvara, wordwrap naziva u tabeli - Štampa: prelom između stavki, ponavljanje zaglavlja, završni blok bez preloma
This commit is contained in:
@@ -196,6 +196,10 @@ type ServisRepository interface {
|
||||
AzurirajPredvidjenDatum(ctx context.Context, id int64, predvidjenDatum *time.Time) error
|
||||
AzurirajTehnicar(ctx context.Context, id int64, tehnicarID *int64) error
|
||||
AzurirajNapomenuKlijentu(ctx context.Context, id int64, tekst string) error
|
||||
AzurirajNalazDijagnostike(ctx context.Context, id int64, tekst string) error
|
||||
AzurirajUradjeno(ctx context.Context, id int64, tekst string) error
|
||||
AzurirajCenaDijagnostike(ctx context.Context, id int64, cena float64) error
|
||||
OdbijPopravku(ctx context.Context, id int64, cena float64) error
|
||||
Obrisi(ctx context.Context, id int64, korisnikID *int64) error
|
||||
SledeciBroj(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func (r *ServisRepo) Lista(ctx context.Context, pretraga, status string) ([]mode
|
||||
sn.id, sn.klijent_id, sn.tehnicar_id, sn.broj_naloga, sn.uredjaj, sn.serijski_broj,
|
||||
sn.opis_kvara, sn.trazene_nadogradnje, sn.status, sn.cena_od, sn.cena_do, sn.cena_konacna,
|
||||
sn.avans, sn.napomena, sn.garancija_do, sn.garancija_dana, sn.datum_prijema, sn.datum_zavrsetka, sn.predvidjen_datum,
|
||||
sn.ostecenja, sn.pin_uredjaja, sn.pribor, sn.napomena_klijentu, sn.javni_token,
|
||||
sn.ostecenja, sn.pin_uredjaja, sn.pribor, sn.napomena_klijentu, sn.nalaz_dijagnostike, sn.uradjeno, sn.cena_dijagnostike, sn.popravka_odbijena, sn.javni_token,
|
||||
COALESCE(kp.naziv, '') AS klijent_naziv
|
||||
FROM servisni_nalozi sn
|
||||
LEFT JOIN klijent_prikaz kp ON kp.id = sn.klijent_id
|
||||
@@ -105,7 +105,7 @@ func (r *ServisRepo) DohvatiID(ctx context.Context, id int64) (*model.ServisniNa
|
||||
id, klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj,
|
||||
opis_kvara, trazene_nadogradnje, status, cena_od, cena_do, cena_konacna,
|
||||
avans, napomena, garancija_do, garancija_dana, datum_prijema, datum_zavrsetka, predvidjen_datum,
|
||||
ostecenja, pin_uredjaja, pribor, napomena_klijentu, javni_token
|
||||
ostecenja, pin_uredjaja, pribor, napomena_klijentu, nalaz_dijagnostike, uradjeno, cena_dijagnostike, popravka_odbijena, javni_token
|
||||
FROM servisni_nalozi WHERE id = ?`, id)
|
||||
|
||||
var n model.ServisniNalog
|
||||
@@ -157,7 +157,7 @@ func (r *ServisRepo) DohvatiJavniToken(ctx context.Context, token string) (*mode
|
||||
id, klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj,
|
||||
opis_kvara, trazene_nadogradnje, status, cena_od, cena_do, cena_konacna,
|
||||
avans, napomena, garancija_do, garancija_dana, datum_prijema, datum_zavrsetka, predvidjen_datum,
|
||||
ostecenja, pin_uredjaja, pribor, napomena_klijentu, javni_token
|
||||
ostecenja, pin_uredjaja, pribor, napomena_klijentu, nalaz_dijagnostike, uradjeno, cena_dijagnostike, popravka_odbijena, javni_token
|
||||
FROM servisni_nalozi WHERE javni_token = ?`, token)
|
||||
|
||||
var n model.ServisniNalog
|
||||
@@ -192,13 +192,15 @@ func (r *ServisRepo) Izmeni(ctx context.Context, n *model.ServisniNalog) error {
|
||||
// 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 {
|
||||
// svaka „normalna" promena statusa poništava oznaku odbijene popravke
|
||||
// (npr. serviser se predomisli i vrati nalog iz „Završeno" u dijagnostiku/popravku)
|
||||
var upit string
|
||||
if status == model.StatusZavrseno || status == model.StatusPreuzeto {
|
||||
upit = `UPDATE servisni_nalozi SET status = ?,
|
||||
upit = `UPDATE servisni_nalozi SET status = ?, popravka_odbijena = 0,
|
||||
datum_zavrsetka = COALESCE(datum_zavrsetka, date('now', 'localtime'))
|
||||
WHERE id = ?`
|
||||
} else {
|
||||
upit = `UPDATE servisni_nalozi SET status = ? WHERE id = ?`
|
||||
upit = `UPDATE servisni_nalozi SET status = ?, popravka_odbijena = 0 WHERE id = ?`
|
||||
}
|
||||
_, err := r.db.ExecContext(ctx, upit, status, id)
|
||||
if err != nil {
|
||||
@@ -207,6 +209,19 @@ func (r *ServisRepo) AzurirajStatus(ctx context.Context, id int64, status string
|
||||
return nil
|
||||
}
|
||||
|
||||
// OdbijPopravku označava da je klijent odbio popravku posle dijagnostike:
|
||||
// nalog prelazi u „Završeno", upisuje se oznaka i cena dijagnostike (taksa za pregled).
|
||||
func (r *ServisRepo) OdbijPopravku(ctx context.Context, id int64, cena float64) error {
|
||||
_, err := r.db.ExecContext(ctx, `UPDATE servisni_nalozi
|
||||
SET status = ?, popravka_odbijena = 1, cena_dijagnostike = ?,
|
||||
datum_zavrsetka = COALESCE(datum_zavrsetka, date('now', 'localtime'))
|
||||
WHERE id = ?`, model.StatusZavrseno, cena, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisRepo.OdbijPopravku: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AzurirajGaranciju postavlja ili briše datum garancije na servisnom nalogu.
|
||||
// garancijaDo == nil → bez garancije.
|
||||
func (r *ServisRepo) AzurirajGaranciju(ctx context.Context, id int64, garancijaDo *time.Time) error {
|
||||
@@ -288,6 +303,39 @@ func (r *ServisRepo) AzurirajNapomenuKlijentu(ctx context.Context, id int64, tek
|
||||
return nil
|
||||
}
|
||||
|
||||
// AzurirajNalazDijagnostike postavlja tekst nalaza dijagnostike (dijagnoza kvara i predlog popravke) na nalogu
|
||||
func (r *ServisRepo) AzurirajNalazDijagnostike(ctx context.Context, id int64, tekst string) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
"UPDATE servisni_nalozi SET nalaz_dijagnostike = ? WHERE id = ?", tekst, id,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisRepo.AzurirajNalazDijagnostike: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AzurirajUradjeno čuva tekst „šta je urađeno" na servisnom nalogu
|
||||
func (r *ServisRepo) AzurirajUradjeno(ctx context.Context, id int64, tekst string) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
"UPDATE servisni_nalozi SET uradjeno = ? WHERE id = ?", tekst, id,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisRepo.AzurirajUradjeno: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AzurirajCenaDijagnostike postavlja cenu dijagnostike (taksa kad klijent ne prihvati popravku)
|
||||
func (r *ServisRepo) AzurirajCenaDijagnostike(ctx context.Context, id int64, cena float64) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
"UPDATE servisni_nalozi SET cena_dijagnostike = ? WHERE id = ?", cena, id,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisRepo.AzurirajCenaDijagnostike: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obrisi briše servisni nalog po ID-u
|
||||
// Obrisi briše servisni nalog i vraća ugrađene delove na stanje u magacinu (u transakciji)
|
||||
func (r *ServisRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) error {
|
||||
@@ -369,16 +417,17 @@ func (r *ServisRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) er
|
||||
// klijentNaziv je opcioni pokazivač, nil kada se čita bez JOIN-a
|
||||
func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *string) error {
|
||||
var klijentID, tehnicarID sql.NullInt64
|
||||
var serijskiBroj, napomena, ostecenja, pinUredjaja, pribor, napomenaKlijentu, javniToken sql.NullString
|
||||
var serijskiBroj, napomena, ostecenja, pinUredjaja, pribor, napomenaKlijentu, nalazDijagnostike, uradjeno, javniToken sql.NullString
|
||||
var cenaOd, cenaDo, cenaKonacna, avans sql.NullFloat64
|
||||
var garancijaDo, datumZavrsetka, predvidjenDatum sql.NullTime
|
||||
var garancijaDana sql.NullInt64
|
||||
var popravkaOdbijena sql.NullInt64
|
||||
|
||||
args := []any{
|
||||
&n.ID, &klijentID, &tehnicarID, &n.BrojNaloga, &n.Uredjaj, &serijskiBroj,
|
||||
&n.OpisKvara, &n.TrazeneNadogradnje, &n.Status, &cenaOd, &cenaDo, &cenaKonacna,
|
||||
&avans, &napomena, &garancijaDo, &garancijaDana, &n.DatumPrijema, &datumZavrsetka, &predvidjenDatum,
|
||||
&ostecenja, &pinUredjaja, &pribor, &napomenaKlijentu, &javniToken,
|
||||
&ostecenja, &pinUredjaja, &pribor, &napomenaKlijentu, &nalazDijagnostike, &uradjeno, &n.CenaDijagnostike, &popravkaOdbijena, &javniToken,
|
||||
}
|
||||
|
||||
if klijentNaziv != nil {
|
||||
@@ -403,6 +452,8 @@ func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *st
|
||||
n.PinUredjaja = pinUredjaja.String
|
||||
n.Pribor = pribor.String
|
||||
n.NapomenaKlijentu = napomenaKlijentu.String
|
||||
n.NalazDijagnostike = nalazDijagnostike.String
|
||||
n.Uradjeno = uradjeno.String
|
||||
if cenaOd.Valid {
|
||||
v := cenaOd.Float64
|
||||
n.CenaOd = &v
|
||||
@@ -435,6 +486,7 @@ func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *st
|
||||
v := int(garancijaDana.Int64)
|
||||
n.GarancijaDana = &v
|
||||
}
|
||||
n.PopravkaOdbijena = popravkaOdbijena.Int64 != 0
|
||||
n.JavniToken = javniToken.String
|
||||
|
||||
return nil
|
||||
|
||||
@@ -63,6 +63,7 @@ type PodaciPodesavanja struct {
|
||||
BackupBrojKopija string
|
||||
KalkulacijaMarza string
|
||||
ServisGarancijaDana string
|
||||
ServisCenaDijagnostike string
|
||||
PredracunRokDana string
|
||||
PredvidjenRokDana string
|
||||
ServisUslovi string
|
||||
@@ -403,6 +404,20 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// podrazumevana cena dijagnostike (din, ≥ 0)
|
||||
if v := strings.TrimSpace(r.FormValue("servis_cena_dijagnostike")); v != "" {
|
||||
cena, err := strconv.ParseFloat(strings.ReplaceAll(v, ",", "."), 64)
|
||||
if err != nil || cena < 0 {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Cena dijagnostike mora biti broj veći ili jednak 0.")
|
||||
http.Redirect(w, r, sledeci, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_cena_dijagnostike", strconv.FormatFloat(cena, 'f', -1, 64)); err != nil {
|
||||
http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// rok važenja predračuna u danima (1–90)
|
||||
if v := strings.TrimSpace(r.FormValue("predracun_rok_dana")); v != "" {
|
||||
n, err := strconv.Atoi(v)
|
||||
@@ -831,6 +846,7 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
|
||||
BackupBrojKopija: vrednostIliDefault(podesavanja, "backup_broj_kopija", "7"),
|
||||
KalkulacijaMarza: vrednostIliDefault(podesavanja, "kalkulacija_marza", "20"),
|
||||
ServisGarancijaDana: vrednostIliDefault(podesavanja, "servis_garancija_dana", "60"),
|
||||
ServisCenaDijagnostike: vrednostIliDefault(podesavanja, "servis_cena_dijagnostike", "0"),
|
||||
PredracunRokDana: vrednostIliDefault(podesavanja, "predracun_rok_dana", "7"),
|
||||
PredvidjenRokDana: vrednostIliDefault(podesavanja, "predvidjen_rok_dana", "15"),
|
||||
ServisUslovi: vrednostIliDefault(podesavanja, "servis_uslovi", podrazumevaniUsloviServisa),
|
||||
|
||||
+174
-37
@@ -50,23 +50,25 @@ type PodaciFormeNaloga struct {
|
||||
// PodaciDetaljiNaloga su podaci za pregled jednog servisnog naloga
|
||||
type PodaciDetaljiNaloga struct {
|
||||
model.PodaciStranice
|
||||
Nalog model.ServisniNalog
|
||||
KlijentNaziv string
|
||||
TehnicarNaziv string
|
||||
Tehnicari []model.Korisnik
|
||||
GarancijaDefault string // podrazumevana garancija (prijem + meseci), format 2006-01-02
|
||||
BezGarancije bool // u bazi nalog nema garanciju (GarancijaDo == NULL)
|
||||
ServisniDelovi []model.ServisniDeoSaArtiklom
|
||||
ServisniRadovi []model.ServisniRad
|
||||
Artikli []model.ArtikalSaKategorijom
|
||||
Usluge []model.Usluga
|
||||
Sacuvano bool
|
||||
UkupnoDelovi float64
|
||||
UkupnoRadovi float64
|
||||
UkupnoSve float64
|
||||
PreostaloSve float64
|
||||
ZakljucanStatus bool // onemogući promenu statusa dok ima potraživanih delova
|
||||
SviStatusi []string
|
||||
Nalog model.ServisniNalog
|
||||
KlijentNaziv string
|
||||
TehnicarNaziv string
|
||||
Tehnicari []model.Korisnik
|
||||
GarancijaDefault string // podrazumevana garancija (prijem + meseci), format 2006-01-02
|
||||
BezGarancije bool // u bazi nalog nema garanciju (GarancijaDo == NULL)
|
||||
ServisniDelovi []model.ServisniDeoSaArtiklom
|
||||
ServisniRadovi []model.ServisniRad
|
||||
Artikli []model.ArtikalSaKategorijom
|
||||
Usluge []model.Usluga
|
||||
Sacuvano bool
|
||||
UkupnoDelovi float64
|
||||
UkupnoRadovi float64
|
||||
UkupnoSve float64
|
||||
PreostaloSve float64
|
||||
ZakljucanStatus bool // onemogući promenu statusa dok ima potraživanih delova
|
||||
CenaDijagnostikePredlog string // podrazumevana cena dijagnostike iz podešavanja (za prefill input-a)
|
||||
KorisceneUsluge map[int64]bool // ID-evi usluga već dodatih na nalog — izostavljaju se iz dropdown-a
|
||||
SviStatusi []string
|
||||
}
|
||||
|
||||
// Servis renderuje listu servisnih naloga sa opcionom pretragom i filterom statusa
|
||||
@@ -595,11 +597,23 @@ func (h *Handler) DetaljiNaloga(w http.ResponseWriter, r *http.Request) {
|
||||
for _, d := range delovi {
|
||||
ukupnoDelovi += d.Ukupno()
|
||||
}
|
||||
// skup već dodatih usluga — izostavljaju se iz dropdown-a da se ista usluga ne doda dvaput
|
||||
korisceneUsluge := make(map[int64]bool, len(radovi))
|
||||
for _, rad := range radovi {
|
||||
ukupnoRadovi += rad.Ukupno()
|
||||
if rad.UslugaID != 0 {
|
||||
korisceneUsluge[rad.UslugaID] = true
|
||||
}
|
||||
}
|
||||
// ukupno za klijenta zavisi od ishoda dijagnostike:
|
||||
// – klijent odbio popravku → naplaćuje se samo cena dijagnostike
|
||||
// – inače (popravka u toku/gotova) → radovi + delovi, dijagnostika se ne gleda
|
||||
var ukupnoSve float64
|
||||
if nalog.PopravkaOdbijena {
|
||||
ukupnoSve = nalog.CenaDijagnostike
|
||||
} else {
|
||||
ukupnoSve = ukupnoRadovi + ukupnoDelovi
|
||||
}
|
||||
// cena rada = zbir radova (usluga); ukupno za klijenta = radovi + delovi
|
||||
ukupnoSve := ukupnoRadovi + ukupnoDelovi
|
||||
avans := 0.0
|
||||
if nalog.Avans != nil {
|
||||
avans = *nalog.Avans
|
||||
@@ -621,24 +635,26 @@ func (h *Handler) DetaljiNaloga(w http.ResponseWriter, r *http.Request) {
|
||||
ps.Stranica = "servis"
|
||||
ps.NaslovStranice = "Detalji naloga"
|
||||
podaci := PodaciDetaljiNaloga{
|
||||
PodaciStranice: ps,
|
||||
Nalog: *nalog,
|
||||
KlijentNaziv: klijentNaziv,
|
||||
TehnicarNaziv: tehnicarNaziv,
|
||||
Tehnicari: tehnicari,
|
||||
GarancijaDefault: garancijaDefault,
|
||||
BezGarancije: bezGarancije,
|
||||
ServisniDelovi: delovi,
|
||||
ServisniRadovi: radovi,
|
||||
Artikli: artikli,
|
||||
Usluge: usluge,
|
||||
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
||||
UkupnoDelovi: ukupnoDelovi,
|
||||
UkupnoRadovi: ukupnoRadovi,
|
||||
UkupnoSve: ukupnoSve,
|
||||
PreostaloSve: preostaloSve,
|
||||
ZakljucanStatus: zakljucanStatus,
|
||||
SviStatusi: model.SviStatusi,
|
||||
PodaciStranice: ps,
|
||||
Nalog: *nalog,
|
||||
KlijentNaziv: klijentNaziv,
|
||||
TehnicarNaziv: tehnicarNaziv,
|
||||
Tehnicari: tehnicari,
|
||||
GarancijaDefault: garancijaDefault,
|
||||
BezGarancije: bezGarancije,
|
||||
ServisniDelovi: delovi,
|
||||
ServisniRadovi: radovi,
|
||||
Artikli: artikli,
|
||||
Usluge: usluge,
|
||||
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
||||
UkupnoDelovi: ukupnoDelovi,
|
||||
UkupnoRadovi: ukupnoRadovi,
|
||||
UkupnoSve: ukupnoSve,
|
||||
PreostaloSve: preostaloSve,
|
||||
ZakljucanStatus: zakljucanStatus,
|
||||
CenaDijagnostikePredlog: podesavanja["servis_cena_dijagnostike"],
|
||||
KorisceneUsluge: korisceneUsluge,
|
||||
SviStatusi: model.SviStatusi,
|
||||
}
|
||||
|
||||
h.renderujTemplate(w, "servis_detalji", podaci)
|
||||
@@ -1806,6 +1822,127 @@ func (h *Handler) SacuvajNapomenuKlijentu(w http.ResponseWriter, r *http.Request
|
||||
http.Redirect(w, r, "/servis/"+strconv.FormatInt(id, 10), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// SacuvajNalazDijagnostike obrađuje POST /servis/{id}/nalaz-dijagnostike i čuva
|
||||
// dijagnozu koju je serviser utvrdio (rezultat dijagnostike, hrani predračun)
|
||||
func (h *Handler) SacuvajNalazDijagnostike(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "servis.izmeni"); !ok {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
tekst := strings.TrimSpace(r.FormValue("nalaz_dijagnostike"))
|
||||
if err := h.ServisRepo.AzurirajNalazDijagnostike(r.Context(), id, tekst); err != nil {
|
||||
slog.Error("greška pri ažuriranju nalaza dijagnostike", "id", id, "error", err)
|
||||
http.Error(w, "Greška pri ažuriranju nalaza", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/servis/"+strconv.FormatInt(id, 10), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// SacuvajUradjeno obrađuje POST /servis/{id}/uradjeno i čuva tekst „šta je urađeno"
|
||||
// (serviser upisuje izvršene radove tokom popravke)
|
||||
func (h *Handler) SacuvajUradjeno(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "servis.izmeni"); !ok {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
tekst := strings.TrimSpace(r.FormValue("uradjeno"))
|
||||
if err := h.ServisRepo.AzurirajUradjeno(r.Context(), id, tekst); err != nil {
|
||||
slog.Error("greška pri ažuriranju urađenog", "id", id, "error", err)
|
||||
http.Error(w, "Greška pri čuvanju", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/servis/"+strconv.FormatInt(id, 10), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// AzurirajCenaDijagnostike čuva cenu dijagnostike (taksa kad klijent ne prihvati popravku);
|
||||
// prazno polje ili neispravna vrednost se tretira kao 0 (ne naplaćuje se)
|
||||
func (h *Handler) AzurirajCenaDijagnostike(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "servis.izmeni"); !ok {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
cena := 0.0
|
||||
if s := strings.TrimSpace(r.FormValue("cena_dijagnostike")); s != "" {
|
||||
v, e := strconv.ParseFloat(s, 64)
|
||||
if e != nil || v < 0 {
|
||||
http.Error(w, "Cena dijagnostike mora biti broj veći ili jednak nuli.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
cena = v
|
||||
}
|
||||
if err := h.ServisRepo.AzurirajCenaDijagnostike(r.Context(), id, cena); err != nil {
|
||||
slog.Error("greška pri ažuriranju cene dijagnostike", "id", id, "error", err)
|
||||
http.Error(w, "Greška pri ažuriranju cene dijagnostike", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/servis/"+strconv.FormatInt(id, 10), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// OdbijPopravku obrađuje slučaj kad klijent posle dijagnostike ne prihvati popravku:
|
||||
// nalog prelazi u „Završeno" i naplaćuje se samo cena dijagnostike. Cena se uzima iz
|
||||
// forme, a ako je prazna koristi se podrazumevana iz podešavanja (Podešavanja → Servis).
|
||||
func (h *Handler) OdbijPopravku(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "servis.izmeni"); !ok {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
cenaStr := strings.TrimSpace(r.FormValue("cena_dijagnostike"))
|
||||
if cenaStr == "" {
|
||||
// nema unete cene na nalogu → uzmi podrazumevanu iz podešavanja
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
cenaStr = strings.TrimSpace(podesavanja["servis_cena_dijagnostike"])
|
||||
}
|
||||
cena := 0.0
|
||||
if cenaStr != "" {
|
||||
v, e := strconv.ParseFloat(strings.ReplaceAll(cenaStr, ",", "."), 64)
|
||||
if e != nil || v < 0 {
|
||||
http.Error(w, "Cena dijagnostike mora biti broj veći ili jednak nuli.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
cena = v
|
||||
}
|
||||
|
||||
if err := h.ServisRepo.OdbijPopravku(r.Context(), id, cena); err != nil {
|
||||
slog.Error("greška pri odbijanju popravke", "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)
|
||||
}
|
||||
|
||||
// PodaciJavnogStatusa su podaci za javnu status stranicu servisnog naloga
|
||||
type PodaciJavnogStatusa struct {
|
||||
Nalog model.ServisniNalog
|
||||
|
||||
@@ -50,6 +50,10 @@ type ServisniNalog struct {
|
||||
PinUredjaja string
|
||||
Pribor string
|
||||
NapomenaKlijentu string
|
||||
NalazDijagnostike string // šta je serviser utvrdio pregledom (dijagnoza + predlog popravke)
|
||||
Uradjeno string // šta je serviser stvarno uradio tokom popravke
|
||||
CenaDijagnostike float64 // taksa kad klijent ne prihvati popravku; 0 = ne naplaćuje se
|
||||
PopravkaOdbijena bool // klijent odbio popravku posle dijagnostike → naplaćuje se samo dijagnostika
|
||||
JavniToken string
|
||||
}
|
||||
|
||||
@@ -159,6 +163,14 @@ func (n ServisniNalog) AvansStr() string {
|
||||
return fmt.Sprintf("%.2f", *n.Avans)
|
||||
}
|
||||
|
||||
// CenaDijagnostikeStr vraća cenu dijagnostike za <input>, ili prazan string kad je 0
|
||||
func (n ServisniNalog) CenaDijagnostikeStr() string {
|
||||
if n.CenaDijagnostike == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%.2f", n.CenaDijagnostike)
|
||||
}
|
||||
|
||||
// PreostaloZaNaplatu vraća razliku konacna_cena − avans, minimum 0.
|
||||
// Vraća nil ako konačna cena nije uneta.
|
||||
func (n ServisniNalog) PreostaloZaNaplatu() *float64 {
|
||||
|
||||
Reference in New Issue
Block a user