Servis: revers i predracun (iz stavki radova+delova), uslovi servisa u podesavanjima, status U dijagnostici
- Revers (servis_revers.html) - dokument prijema bez cena, sa QR, oscecenjima i uslovima servisa - Predracun (servis_predracun.html) - procena iz stavki radova i delova, klauzula, dugme uvek dostupno - Podesavanja > Servis: kartica Uslovi servisa (servis_uslovi + servis_klauzula_predracuna), auto-snimanje, bez dugmeta Sacuvaj - Genericki data-autosave obrazac u base.html - Status U dijagnostici: konstanta + kanban kolona + dugme Pocni dijagnostiku (vidljivo u Primljeno)
This commit is contained in:
@@ -43,7 +43,7 @@ var saSidebar = []string{
|
||||
|
||||
// standalone su šabloni bez base layouta
|
||||
var standaloneIme = []string{
|
||||
"prijava", "setup", "totp_provera", "prodaja_stampa", "servis_radni_nalog", "servis_otpremnica", "servis_nalepnica", "servis_status_javni",
|
||||
"prijava", "setup", "totp_provera", "prodaja_stampa", "servis_radni_nalog", "servis_otpremnica", "servis_revers", "servis_predracun", "servis_nalepnica", "servis_status_javni",
|
||||
}
|
||||
|
||||
// sablonskeFunkcije su pomoćne funkcije dostupne u svim šablonima.
|
||||
|
||||
@@ -20,6 +20,19 @@ import (
|
||||
"ntech/internal/model"
|
||||
)
|
||||
|
||||
// podrazumevaniUsloviServisa je podrazumevani tekst uslova servisa koji se
|
||||
// štampa na reversu; korisnik ga može izmeniti u Podešavanja → Servis.
|
||||
const podrazumevaniUsloviServisa = `1. Servis ne odgovara za podatke na uređaju. Klijent je dužan da sam napravi rezervnu kopiju podataka pre predaje.
|
||||
2. Uređaj se preuzima uz ovaj revers i ličnu kartu. Bez reversa uređaj se ne izdaje.
|
||||
3. Rok za podizanje uređaja je 30 dana od obaveštenja o završetku. Po isteku roka servis ne odgovara za uređaj.
|
||||
4. Ako klijent odustane od popravke, naplaćuje se dijagnostika prema cenovniku.
|
||||
5. Garancija na izvršeni servis važi za zamenjene delove i obavljeni rad, ne i za nove kvarove.
|
||||
6. Potpisom klijent potvrđuje da je saglasan sa navedenim uslovima.`
|
||||
|
||||
// podrazumevanaKlauzulaPredracuna je podrazumevana napomena koja se štampa na
|
||||
// dnu predračuna; korisnik je može izmeniti u Podešavanja → Servis.
|
||||
const podrazumevanaKlauzulaPredracuna = `Procena je data na osnovu zahteva klijenta i nije fiskalni dokument. Ako se tokom rada utvrdi dodatni kvar, servis kontaktira klijenta radi saglasnosti pre nastavka radova i izmene cene.`
|
||||
|
||||
// PodaciPodesavanja su podaci za stranicu podešavanja
|
||||
type PodaciPodesavanja struct {
|
||||
model.PodaciStranice
|
||||
@@ -52,6 +65,8 @@ type PodaciPodesavanja struct {
|
||||
ServisGarancijaDana string
|
||||
PredracunRokDana string
|
||||
PredvidjenRokDana string
|
||||
ServisUslovi string
|
||||
ServisKlauzulaPredracuna string
|
||||
LoginPozadina string
|
||||
LoginPozadinaOpacity string
|
||||
LoginPozadinaBlurPozadine string
|
||||
@@ -407,6 +422,25 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// uslovi servisa (slobodan tekst, štampa se na reversu); čuva se uvek,
|
||||
// pa korisnik može i da skrati ili isprazni tekst
|
||||
if _, ima := r.Form["servis_uslovi"]; ima {
|
||||
uslovi := strings.TrimSpace(r.FormValue("servis_uslovi"))
|
||||
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_uslovi", uslovi); err != nil {
|
||||
http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// klauzula na predračunu (slobodan tekst); čuva se uvek
|
||||
if _, ima := r.Form["servis_klauzula_predracuna"]; ima {
|
||||
klauzula := strings.TrimSpace(r.FormValue("servis_klauzula_predracuna"))
|
||||
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_klauzula_predracuna", klauzula); err != nil {
|
||||
http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(w, r, sledeci+"?sacuvano=1", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
@@ -782,6 +816,8 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
|
||||
ServisGarancijaDana: vrednostIliDefault(podesavanja, "servis_garancija_dana", "60"),
|
||||
PredracunRokDana: vrednostIliDefault(podesavanja, "predracun_rok_dana", "7"),
|
||||
PredvidjenRokDana: vrednostIliDefault(podesavanja, "predvidjen_rok_dana", "15"),
|
||||
ServisUslovi: vrednostIliDefault(podesavanja, "servis_uslovi", podrazumevaniUsloviServisa),
|
||||
ServisKlauzulaPredracuna: vrednostIliDefault(podesavanja, "servis_klauzula_predracuna", podrazumevanaKlauzulaPredracuna),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1284,6 +1284,209 @@ func (h *Handler) StampaOtpremnice(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// PodaciReversa su podaci za revers — potvrdu o prijemu uređaja na servis.
|
||||
// Dokument se izdaje pri prijemu, pa nema cena.
|
||||
type PodaciReversa struct {
|
||||
Nalog model.ServisniNalog
|
||||
Klijent *model.Klijent
|
||||
KlijentNaziv string
|
||||
KlijentOznakaIdent string // labela identifikacije ("JMBG" / "Br. lične karte")
|
||||
KlijentBrojIdent string // vrednost identifikacionog broja
|
||||
TehnicarNaziv string
|
||||
QRKod string // base64 QR ka javnoj status strani naloga
|
||||
Uslovi string // uslovi servisa iz podešavanja (servis_uslovi)
|
||||
NazivFirme string
|
||||
Podnazlov string
|
||||
Adresa string
|
||||
Telefon string
|
||||
PIB string
|
||||
}
|
||||
|
||||
// StampaReversa renderuje revers (potvrdu o prijemu uređaja na servis)
|
||||
func (h *Handler) StampaReversa(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
|
||||
}
|
||||
|
||||
nalog, err := h.ServisRepo.DohvatiID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Nalog nije pronađen", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var klijent *model.Klijent
|
||||
klijentNaziv := ""
|
||||
klijentOznaka := ""
|
||||
klijentBroj := ""
|
||||
if nalog.KlijentID != nil {
|
||||
k, err := h.KlijentiRepo.DohvatiID(r.Context(), *nalog.KlijentID)
|
||||
if err == nil {
|
||||
klijent = k
|
||||
klijentNaziv = k.PunoIme()
|
||||
if k.JMBG != "" {
|
||||
klijentOznaka = k.OznakaIdentifikacije()
|
||||
klijentBroj = k.JMBG
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tehnicarNaziv := ""
|
||||
if nalog.TehnicarID != nil {
|
||||
tehnicar, err := h.KorisniciRepo.DohvatiPoID(r.Context(), *nalog.TehnicarID)
|
||||
if err == nil {
|
||||
tehnicarNaziv = tehnicar.KorisnickoIme
|
||||
}
|
||||
}
|
||||
|
||||
nalogURL := qrNalogURL(r, nalog.JavniToken)
|
||||
var qrKod string
|
||||
if png, err := qrcode.Encode(nalogURL, qrcode.Medium, 160); err == nil {
|
||||
qrKod = base64.StdEncoding.EncodeToString(png)
|
||||
}
|
||||
|
||||
h.renderujStandalone(w, "servis_revers", PodaciReversa{
|
||||
Nalog: *nalog,
|
||||
Klijent: klijent,
|
||||
KlijentNaziv: klijentNaziv,
|
||||
KlijentOznakaIdent: klijentOznaka,
|
||||
KlijentBrojIdent: klijentBroj,
|
||||
TehnicarNaziv: tehnicarNaziv,
|
||||
QRKod: qrKod,
|
||||
Uslovi: vrednostIliDefault(podesavanja, "servis_uslovi", podrazumevaniUsloviServisa),
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
Adresa: podesavanja["adresa"],
|
||||
Telefon: podesavanja["telefon"],
|
||||
PIB: podesavanja["pib"],
|
||||
})
|
||||
}
|
||||
|
||||
// PodaciPredracuna su podaci za predračun — procenu cene za tražene radove i delove,
|
||||
// koju klijent odobrava pre popravke. Gradi se iz stavki radova i delova na nalogu.
|
||||
type PodaciPredracuna struct {
|
||||
Nalog model.ServisniNalog
|
||||
Radovi []model.ServisniRad
|
||||
UkupnoRad float64
|
||||
ServisniDelovi []model.ServisniDeoSaArtiklom
|
||||
UkupnoDelovi float64
|
||||
UkupnoSve float64
|
||||
DatumIzdavanja time.Time
|
||||
VaziDo time.Time
|
||||
Klauzula string
|
||||
QRKod string
|
||||
Klijent *model.Klijent
|
||||
KlijentNaziv string
|
||||
TehnicarNaziv string
|
||||
NazivFirme string
|
||||
Podnazlov string
|
||||
Adresa string
|
||||
Telefon string
|
||||
PIB string
|
||||
}
|
||||
|
||||
// StampaPredracuna renderuje predračun (procenu cene) na osnovu traženih radova i delova
|
||||
func (h *Handler) StampaPredracuna(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
|
||||
}
|
||||
|
||||
nalog, err := h.ServisRepo.DohvatiID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Nalog nije pronađen", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
radovi, err := h.ServisniRadoviRepo.DohvatiZaNalog(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju radova", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
delovi, err := h.deloviSaPotrazivanima(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju delova", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var klijent *model.Klijent
|
||||
klijentNaziv := ""
|
||||
if nalog.KlijentID != nil {
|
||||
k, err := h.KlijentiRepo.DohvatiID(r.Context(), *nalog.KlijentID)
|
||||
if err == nil {
|
||||
klijent = k
|
||||
klijentNaziv = k.PunoIme()
|
||||
}
|
||||
}
|
||||
|
||||
tehnicarNaziv := ""
|
||||
if nalog.TehnicarID != nil {
|
||||
tehnicar, err := h.KorisniciRepo.DohvatiPoID(r.Context(), *nalog.TehnicarID)
|
||||
if err == nil {
|
||||
tehnicarNaziv = tehnicar.KorisnickoIme
|
||||
}
|
||||
}
|
||||
|
||||
var ukupnoRad float64
|
||||
for _, rd := range radovi {
|
||||
ukupnoRad += rd.Ukupno()
|
||||
}
|
||||
var ukupnoDelovi float64
|
||||
for _, d := range delovi {
|
||||
ukupnoDelovi += d.Ukupno()
|
||||
}
|
||||
|
||||
// rok važenja iz podešavanja (default 7 dana)
|
||||
rok := 7
|
||||
if v, err := strconv.Atoi(podesavanja["predracun_rok_dana"]); err == nil && v > 0 {
|
||||
rok = v
|
||||
}
|
||||
datumIzdavanja := time.Now()
|
||||
vaziDo := datumIzdavanja.AddDate(0, 0, rok)
|
||||
|
||||
nalogURL := qrNalogURL(r, nalog.JavniToken)
|
||||
var qrKod string
|
||||
if png, err := qrcode.Encode(nalogURL, qrcode.Medium, 160); err == nil {
|
||||
qrKod = base64.StdEncoding.EncodeToString(png)
|
||||
}
|
||||
|
||||
h.renderujStandalone(w, "servis_predracun", PodaciPredracuna{
|
||||
Nalog: *nalog,
|
||||
Radovi: radovi,
|
||||
UkupnoRad: ukupnoRad,
|
||||
ServisniDelovi: delovi,
|
||||
UkupnoDelovi: ukupnoDelovi,
|
||||
UkupnoSve: ukupnoRad + ukupnoDelovi,
|
||||
DatumIzdavanja: datumIzdavanja,
|
||||
VaziDo: vaziDo,
|
||||
Klauzula: vrednostIliDefault(podesavanja, "servis_klauzula_predracuna", podrazumevanaKlauzulaPredracuna),
|
||||
QRKod: qrKod,
|
||||
Klijent: klijent,
|
||||
KlijentNaziv: klijentNaziv,
|
||||
TehnicarNaziv: tehnicarNaziv,
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
Adresa: podesavanja["adresa"],
|
||||
Telefon: podesavanja["telefon"],
|
||||
PIB: podesavanja["pib"],
|
||||
})
|
||||
}
|
||||
|
||||
// PodaciNalepnice su podaci za malu nalepnicu (70×40 mm) koja se lepi na uređaj
|
||||
type PodaciNalepnice struct {
|
||||
Nalog model.ServisniNalog
|
||||
|
||||
@@ -7,16 +7,18 @@ import (
|
||||
|
||||
// Statusi servisnog naloga
|
||||
const (
|
||||
StatusPrimljeno = "Primljeno"
|
||||
StatusCekaDelove = "Čeka delove"
|
||||
StatusUPopravci = "U popravci"
|
||||
StatusZavrseno = "Završeno"
|
||||
StatusPreuzeto = "Preuzeto"
|
||||
StatusPrimljeno = "Primljeno"
|
||||
StatusUDijagnostici = "U dijagnostici"
|
||||
StatusCekaDelove = "Čeka delove"
|
||||
StatusUPopravci = "U popravci"
|
||||
StatusZavrseno = "Završeno"
|
||||
StatusPreuzeto = "Preuzeto"
|
||||
)
|
||||
|
||||
// SviStatusi je uređena lista statusa za prikaz u dropdownu
|
||||
var SviStatusi = []string{
|
||||
StatusPrimljeno,
|
||||
StatusUDijagnostici,
|
||||
StatusCekaDelove,
|
||||
StatusUPopravci,
|
||||
StatusZavrseno,
|
||||
|
||||
Reference in New Issue
Block a user