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:
2026-06-23 22:21:12 +02:00
parent a8d5b0e9b4
commit f745f043c5
17 changed files with 494 additions and 106 deletions
+4
View File
@@ -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)
}
+59 -7
View File
@@ -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
+16
View File
@@ -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 (190)
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
View File
@@ -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
+12
View File
@@ -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 {