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:
@@ -357,6 +357,8 @@ 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}", h.DetaljiNaloga)
|
||||||
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}/radni-nalog", h.StampaRadnogNaloga)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}/radni-nalog", h.StampaRadnogNaloga)
|
||||||
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}/otpremnica", h.StampaOtpremnice)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}/otpremnica", h.StampaOtpremnice)
|
||||||
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}/revers", h.StampaReversa)
|
||||||
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}/predracun", h.StampaPredracuna)
|
||||||
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}/nalepnica", h.StampaNalepnice)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "servis.pregled")).Get("/servis/{id}/nalepnica", h.StampaNalepnice)
|
||||||
r.With(doz("servis.izmeni")).Post("/servis/{id}/status", h.PromeniStatus)
|
r.With(doz("servis.izmeni")).Post("/servis/{id}/status", h.PromeniStatus)
|
||||||
r.With(doz("servis.izmeni")).Post("/servis/{id}/garancija", h.AzurirajGaranciju)
|
r.With(doz("servis.izmeni")).Post("/servis/{id}/garancija", h.AzurirajGaranciju)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ var saSidebar = []string{
|
|||||||
|
|
||||||
// standalone su šabloni bez base layouta
|
// standalone su šabloni bez base layouta
|
||||||
var standaloneIme = []string{
|
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.
|
// sablonskeFunkcije su pomoćne funkcije dostupne u svim šablonima.
|
||||||
|
|||||||
@@ -20,6 +20,19 @@ import (
|
|||||||
"ntech/internal/model"
|
"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
|
// PodaciPodesavanja su podaci za stranicu podešavanja
|
||||||
type PodaciPodesavanja struct {
|
type PodaciPodesavanja struct {
|
||||||
model.PodaciStranice
|
model.PodaciStranice
|
||||||
@@ -52,6 +65,8 @@ type PodaciPodesavanja struct {
|
|||||||
ServisGarancijaDana string
|
ServisGarancijaDana string
|
||||||
PredracunRokDana string
|
PredracunRokDana string
|
||||||
PredvidjenRokDana string
|
PredvidjenRokDana string
|
||||||
|
ServisUslovi string
|
||||||
|
ServisKlauzulaPredracuna string
|
||||||
LoginPozadina string
|
LoginPozadina string
|
||||||
LoginPozadinaOpacity string
|
LoginPozadinaOpacity string
|
||||||
LoginPozadinaBlurPozadine 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)
|
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"),
|
ServisGarancijaDana: vrednostIliDefault(podesavanja, "servis_garancija_dana", "60"),
|
||||||
PredracunRokDana: vrednostIliDefault(podesavanja, "predracun_rok_dana", "7"),
|
PredracunRokDana: vrednostIliDefault(podesavanja, "predracun_rok_dana", "7"),
|
||||||
PredvidjenRokDana: vrednostIliDefault(podesavanja, "predvidjen_rok_dana", "15"),
|
PredvidjenRokDana: vrednostIliDefault(podesavanja, "predvidjen_rok_dana", "15"),
|
||||||
|
ServisUslovi: vrednostIliDefault(podesavanja, "servis_uslovi", podrazumevaniUsloviServisa),
|
||||||
|
ServisKlauzulaPredracuna: vrednostIliDefault(podesavanja, "servis_klauzula_predracuna", podrazumevanaKlauzulaPredracuna),
|
||||||
}, nil
|
}, 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
|
// PodaciNalepnice su podaci za malu nalepnicu (70×40 mm) koja se lepi na uređaj
|
||||||
type PodaciNalepnice struct {
|
type PodaciNalepnice struct {
|
||||||
Nalog model.ServisniNalog
|
Nalog model.ServisniNalog
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
// Statusi servisnog naloga
|
// Statusi servisnog naloga
|
||||||
const (
|
const (
|
||||||
StatusPrimljeno = "Primljeno"
|
StatusPrimljeno = "Primljeno"
|
||||||
|
StatusUDijagnostici = "U dijagnostici"
|
||||||
StatusCekaDelove = "Čeka delove"
|
StatusCekaDelove = "Čeka delove"
|
||||||
StatusUPopravci = "U popravci"
|
StatusUPopravci = "U popravci"
|
||||||
StatusZavrseno = "Završeno"
|
StatusZavrseno = "Završeno"
|
||||||
@@ -17,6 +18,7 @@ const (
|
|||||||
// SviStatusi je uređena lista statusa za prikaz u dropdownu
|
// SviStatusi je uređena lista statusa za prikaz u dropdownu
|
||||||
var SviStatusi = []string{
|
var SviStatusi = []string{
|
||||||
StatusPrimljeno,
|
StatusPrimljeno,
|
||||||
|
StatusUDijagnostici,
|
||||||
StatusCekaDelove,
|
StatusCekaDelove,
|
||||||
StatusUPopravci,
|
StatusUPopravci,
|
||||||
StatusZavrseno,
|
StatusZavrseno,
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
<div class="poruka-uspeh">Podešavanja su uspešno sačuvana.</div>
|
<div class="poruka-uspeh">Podešavanja su uspešno sačuvana.</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<form method="POST" action="/podesavanja/sacuvaj">
|
<form method="POST" action="/podesavanja/sacuvaj" data-autosave>
|
||||||
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||||
<input type="hidden" name="_next" value="/admin/podesavanja/servis">
|
<input type="hidden" name="_next" value="/admin/podesavanja/servis">
|
||||||
|
|
||||||
<div class="kartica animiraj">
|
<div class="kartica animiraj" style="margin-bottom:16px;">
|
||||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
|
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
|
||||||
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Servis</span>
|
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Servis</span>
|
||||||
@@ -59,9 +59,35 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="display:flex;justify-content:flex-end;margin-top:20px;">
|
<div class="kartica animiraj" style="margin-bottom:16px;">
|
||||||
<button type="submit" class="btn-primarno">Sačuvaj</button>
|
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></svg>
|
||||||
|
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Uslovi servisa</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="servis_uslovi" class="polje-labela">
|
||||||
|
Uslovi koji se štampaju na reversu
|
||||||
|
</label>
|
||||||
|
<textarea id="servis_uslovi" name="servis_uslovi" rows="9"
|
||||||
|
style="width:100%;resize:vertical;line-height:1.6;">{{.ServisUslovi}}</textarea>
|
||||||
|
<div class="pomocni-tekst" style="margin-top:6px;">
|
||||||
|
Ovaj tekst se štampa na reversu (potvrdi o prijemu uređaja na servis). Slobodno ga
|
||||||
|
izmeni prema svojim pravilima — svaka stavka u novom redu. Snima se automatski.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:16px;">
|
||||||
|
<label for="servis_klauzula_predracuna" class="polje-labela">
|
||||||
|
Klauzula na predračunu
|
||||||
|
</label>
|
||||||
|
<textarea id="servis_klauzula_predracuna" name="servis_klauzula_predracuna" rows="3"
|
||||||
|
style="width:100%;resize:vertical;line-height:1.6;">{{.ServisKlauzulaPredracuna}}</textarea>
|
||||||
|
<div class="pomocni-tekst" style="margin-top:6px;">
|
||||||
|
Napomena na dnu predračuna (procena cene). Snima se automatski.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
{{define "status-badge"}}
|
{{define "status-badge"}}
|
||||||
{{if eq . "Primljeno"}}<span class="status-badge status-primljeno">{{.}}</span>
|
{{if eq . "Primljeno"}}<span class="status-badge status-primljeno">{{.}}</span>
|
||||||
|
{{else if eq . "U dijagnostici"}}<span class="status-badge status-dijagnostika">{{.}}</span>
|
||||||
{{else if eq . "Čeka delove"}}<span class="status-badge status-ceka">{{.}}</span>
|
{{else if eq . "Čeka delove"}}<span class="status-badge status-ceka">{{.}}</span>
|
||||||
{{else if eq . "U popravci"}}<span class="status-badge status-popravka">{{.}}</span>
|
{{else if eq . "U popravci"}}<span class="status-badge status-popravka">{{.}}</span>
|
||||||
{{else if eq . "Završeno"}}<span class="status-badge status-zavrseno">{{.}}</span>
|
{{else if eq . "Završeno"}}<span class="status-badge status-zavrseno">{{.}}</span>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
{{define "status-badge-detalji"}}
|
{{define "status-badge-detalji"}}
|
||||||
{{if eq . "Primljeno"}}<span class="status-badge status-primljeno">{{.}}</span>
|
{{if eq . "Primljeno"}}<span class="status-badge status-primljeno">{{.}}</span>
|
||||||
|
{{else if eq . "U dijagnostici"}}<span class="status-badge status-dijagnostika">{{.}}</span>
|
||||||
{{else if eq . "Čeka delove"}}<span class="status-badge status-ceka">{{.}}</span>
|
{{else if eq . "Čeka delove"}}<span class="status-badge status-ceka">{{.}}</span>
|
||||||
{{else if eq . "U popravci"}}<span class="status-badge status-popravka">{{.}}</span>
|
{{else if eq . "U popravci"}}<span class="status-badge status-popravka">{{.}}</span>
|
||||||
{{else if eq . "Završeno"}}<span class="status-badge status-zavrseno">{{.}}</span>
|
{{else if eq . "Završeno"}}<span class="status-badge status-zavrseno">{{.}}</span>
|
||||||
@@ -44,9 +45,26 @@
|
|||||||
</span>
|
</span>
|
||||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||||
{{template "status-badge-detalji" .Nalog.Status}}
|
{{template "status-badge-detalji" .Nalog.Status}}
|
||||||
|
{{if eq .Nalog.Status "Primljeno"}}
|
||||||
|
<!-- serviser preuzima uređaj u rad → status prelazi u „U dijagnostici";
|
||||||
|
data-refresh osvežava samo zaglavlje (badge i ovo dugme se ponovo renderuju) -->
|
||||||
|
<form method="POST" action="/servis/{{.Nalog.ID}}/status" data-refresh="#nalog-zaglavlje" style="margin:0;">
|
||||||
|
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||||
|
<input type="hidden" name="status" value="U dijagnostici">
|
||||||
|
<button type="submit" class="btn-sekundarno" style="padding:3px 12px;font-size:12px;">
|
||||||
|
Počni dijagnostiku
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
||||||
|
<a href="/servis/{{.Nalog.ID}}/revers" target="_blank" class="btn-sekundarno">
|
||||||
|
Revers
|
||||||
|
</a>
|
||||||
|
<a href="/servis/{{.Nalog.ID}}/predracun" target="_blank" class="btn-sekundarno">
|
||||||
|
Predračun
|
||||||
|
</a>
|
||||||
<a href="/servis/{{.Nalog.ID}}/radni-nalog" target="_blank" class="btn-sekundarno">
|
<a href="/servis/{{.Nalog.ID}}/radni-nalog" target="_blank" class="btn-sekundarno">
|
||||||
Radni nalog
|
Radni nalog
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="sr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Predračun — {{.Nalog.BrojNaloga}}</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; font-size: 13px; color: #111; background: #fff; }
|
||||||
|
|
||||||
|
.strana { max-width: 780px; margin: 0 auto; padding: 32px 40px; }
|
||||||
|
|
||||||
|
.zaglavlje { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; padding-bottom: 16px; border-bottom: 2px solid #111; gap: 20px; }
|
||||||
|
.firma-naziv { font-size: 20px; font-weight: 700; }
|
||||||
|
.firma-info { font-size: 11px; color: #555; margin-top: 4px; line-height: 1.7; }
|
||||||
|
.dok-naslov { text-align: right; }
|
||||||
|
.dok-tip { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; color: #555; margin-bottom: 4px; }
|
||||||
|
.dok-broj { font-size: 22px; font-weight: 700; font-family: monospace; }
|
||||||
|
.dok-datum { font-size: 12px; color: #555; margin-top: 6px; }
|
||||||
|
|
||||||
|
.strane-blok { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
|
||||||
|
.strana-kartica { padding: 12px 14px; border: 0.5px solid #ddd; border-radius: 6px; }
|
||||||
|
.strana-tip { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.6px; color: #888; margin-bottom: 6px; }
|
||||||
|
.strana-naziv { font-size: 14px; font-weight: 600; }
|
||||||
|
.strana-info { font-size: 12px; color: #555; margin-top: 3px; line-height: 1.6; }
|
||||||
|
|
||||||
|
.odeljak { margin-bottom: 18px; }
|
||||||
|
.odeljak-naslov { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.8px; color: #555; margin-bottom: 8px; padding-bottom: 4px; border-bottom: 0.5px solid #ccc; }
|
||||||
|
.polje-tekst { font-size: 13px; line-height: 1.6; white-space: pre-wrap; }
|
||||||
|
.prazno { color: #aaa; font-style: italic; }
|
||||||
|
|
||||||
|
.tabela { width: 100%; border-collapse: collapse; margin-top: 6px; }
|
||||||
|
.tabela th { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; color: #555; padding: 6px 8px; border-bottom: 1.5px solid #111; text-align: left; }
|
||||||
|
.tabela th.desno { text-align: right; }
|
||||||
|
.tabela td { padding: 7px 8px; border-bottom: 0.5px solid #ddd; font-size: 13px; vertical-align: middle; }
|
||||||
|
.tabela td.desno { text-align: right; }
|
||||||
|
.tabela .ukupno-red td { font-weight: 600; border-top: 1.5px solid #111; border-bottom: none; background: #fafafa; }
|
||||||
|
|
||||||
|
.rekap { margin-top: 14px; margin-left: auto; width: 320px; }
|
||||||
|
.rekap-red { display: flex; justify-content: space-between; padding: 5px 0; font-size: 13px; }
|
||||||
|
.rekap-ukupno { display: flex; justify-content: space-between; align-items: center; padding: 10px 14px; border: 1.5px solid #111; border-radius: 6px; margin-top: 8px; }
|
||||||
|
.rekap-ukupno-labela { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; }
|
||||||
|
.rekap-ukupno-iznos { font-size: 20px; font-weight: 700; }
|
||||||
|
|
||||||
|
.klauzula-blok { margin-top: 18px; padding: 10px 14px; background: #fffbeb; border-left: 3px solid #f59e0b; font-size: 12px; color: #92400e; line-height: 1.6; white-space: pre-wrap; }
|
||||||
|
.vazi-do { margin-top: 12px; font-size: 12px; color: #555; }
|
||||||
|
|
||||||
|
.potpisi { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-top: 36px; padding-top: 16px; border-top: 0.5px solid #ccc; }
|
||||||
|
.potpis-linija { border-top: 1px solid #888; margin-top: 44px; padding-top: 6px; font-size: 11px; color: #555; text-align: center; }
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body { font-size: 12px; }
|
||||||
|
.strana { padding: 16px 20px; max-width: 100%; }
|
||||||
|
.dugme-stampa { display: none !important; }
|
||||||
|
@page { size: A4; margin: 12mm 14mm; }
|
||||||
|
}
|
||||||
|
.dugme-stampa { position: fixed; bottom: 24px; right: 24px; background: #111; color: #fff; border: none; padding: 12px 22px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
|
||||||
|
.dugme-stampa:hover { background: #333; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="strana">
|
||||||
|
|
||||||
|
<!-- zaglavlje -->
|
||||||
|
<div class="zaglavlje">
|
||||||
|
<div>
|
||||||
|
<div class="firma-naziv">{{if .NazivFirme}}{{.NazivFirme}}{{else}}— naziv firme —{{end}}</div>
|
||||||
|
<div class="firma-info">
|
||||||
|
{{if .Podnazlov}}{{.Podnazlov}}<br>{{end}}
|
||||||
|
{{if .Adresa}}{{.Adresa}}<br>{{end}}
|
||||||
|
{{if .Telefon}}Tel: {{telefon .Telefon}}<br>{{end}}
|
||||||
|
{{if .PIB}}PIB: {{.PIB}}{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;align-items:flex-start;gap:14px;">
|
||||||
|
<div class="dok-naslov">
|
||||||
|
<div class="dok-tip">Predračun (procena)</div>
|
||||||
|
<div class="dok-broj">{{.Nalog.BrojNaloga}}</div>
|
||||||
|
<div class="dok-datum">Datum: {{.DatumIzdavanja.Format "02.01.2006."}}</div>
|
||||||
|
<div class="dok-datum">Važi do: {{.VaziDo.Format "02.01.2006."}}</div>
|
||||||
|
</div>
|
||||||
|
{{if .QRKod}}
|
||||||
|
<img src="data:image/png;base64,{{.QRKod}}" width="76" height="76"
|
||||||
|
alt="QR {{.Nalog.BrojNaloga}}"
|
||||||
|
style="image-rendering:pixelated;border:1px solid #ddd;border-radius:4px;flex-shrink:0;">
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- izdao / za -->
|
||||||
|
<div class="strane-blok">
|
||||||
|
<div class="strana-kartica">
|
||||||
|
<div class="strana-tip">Izdao (servis)</div>
|
||||||
|
<div class="strana-naziv">{{if .NazivFirme}}{{.NazivFirme}}{{else}}—{{end}}</div>
|
||||||
|
<div class="strana-info">
|
||||||
|
{{if .Adresa}}{{.Adresa}}<br>{{end}}
|
||||||
|
{{if .Telefon}}{{telefon .Telefon}}{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="strana-kartica">
|
||||||
|
<div class="strana-tip">Za (klijent)</div>
|
||||||
|
{{if .KlijentNaziv}}
|
||||||
|
<div class="strana-naziv">{{.KlijentNaziv}}</div>
|
||||||
|
<div class="strana-info">
|
||||||
|
{{if .Klijent}}
|
||||||
|
{{if .Klijent.Telefon}}Tel: {{telefon .Klijent.Telefon}}<br>{{end}}
|
||||||
|
{{if .Klijent.Mesto}}{{.Klijent.Mesto}}{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="strana-naziv prazno">— klijent nije naveden —</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- uređaj i traženo -->
|
||||||
|
<div class="odeljak">
|
||||||
|
<div class="odeljak-naslov">Uređaj i traženi radovi</div>
|
||||||
|
<div style="margin-bottom:6px;"><strong>Uređaj:</strong> {{if .Nalog.Uredjaj}}{{.Nalog.Uredjaj}}{{else}}<span class="prazno">—</span>{{end}}{{if .Nalog.SerijskiBroj}} · S/N: {{.Nalog.SerijskiBroj}}{{end}}</div>
|
||||||
|
<div class="polje-tekst"><strong>Zahtev klijenta:</strong> {{if .Nalog.OpisKvara}}{{.Nalog.OpisKvara}}{{else}}<span class="prazno">—</span>{{end}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- radovi -->
|
||||||
|
{{if .Radovi}}
|
||||||
|
<div class="odeljak">
|
||||||
|
<div class="odeljak-naslov">Radovi</div>
|
||||||
|
<table class="tabela">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Usluga</th>
|
||||||
|
<th class="desno" style="width:70px;">Kol.</th>
|
||||||
|
<th class="desno" style="width:120px;">Cena</th>
|
||||||
|
<th class="desno" style="width:120px;">Ukupno</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .Radovi}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Naziv}}</td>
|
||||||
|
<td class="desno">{{.Kolicina}}</td>
|
||||||
|
<td class="desno">{{dinari .CenaKomada}} din</td>
|
||||||
|
<td class="desno">{{dinari .Ukupno}} din</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
<tr class="ukupno-red">
|
||||||
|
<td colspan="3">Ukupno radovi</td>
|
||||||
|
<td class="desno">{{dinari .UkupnoRad}} din</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<!-- delovi -->
|
||||||
|
{{if .ServisniDelovi}}
|
||||||
|
<div class="odeljak">
|
||||||
|
<div class="odeljak-naslov">Delovi i materijal</div>
|
||||||
|
<table class="tabela">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Artikal</th>
|
||||||
|
<th class="desno" style="width:70px;">Kol.</th>
|
||||||
|
<th class="desno" style="width:120px;">Cena/kom</th>
|
||||||
|
<th class="desno" style="width:120px;">Ukupno</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .ServisniDelovi}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.ArtikalNaziv}}</td>
|
||||||
|
<td class="desno">{{.Kolicina}}</td>
|
||||||
|
<td class="desno">{{dinari .CenaKomada}} din</td>
|
||||||
|
<td class="desno">{{dinari .Ukupno}} din</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
<tr class="ukupno-red">
|
||||||
|
<td colspan="3">Ukupno delovi</td>
|
||||||
|
<td class="desno">{{dinari .UkupnoDelovi}} din</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if and (not .Radovi) (not .ServisniDelovi)}}
|
||||||
|
<div class="odeljak">
|
||||||
|
<div class="polje-tekst prazno">Na nalog još nisu uneti radovi ni delovi — dodaj ih u detaljima naloga da bi se prikazali na predračunu.</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<!-- rekapitulacija -->
|
||||||
|
<div class="rekap">
|
||||||
|
{{if .Radovi}}<div class="rekap-red"><span>Radovi</span><span>{{dinari .UkupnoRad}} din</span></div>{{end}}
|
||||||
|
{{if .ServisniDelovi}}<div class="rekap-red"><span>Delovi i materijal</span><span>{{dinari .UkupnoDelovi}} din</span></div>{{end}}
|
||||||
|
<div class="rekap-ukupno">
|
||||||
|
<div class="rekap-ukupno-labela">Ukupno (procena):</div>
|
||||||
|
<div class="rekap-ukupno-iznos">{{dinari .UkupnoSve}} din</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- klauzula -->
|
||||||
|
{{if .Klauzula}}
|
||||||
|
<div class="klauzula-blok">{{.Klauzula}}</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="vazi-do">Predračun važi do <strong>{{.VaziDo.Format "02.01.2006."}}</strong>.</div>
|
||||||
|
|
||||||
|
<!-- potpisi -->
|
||||||
|
<div class="potpisi">
|
||||||
|
<div>
|
||||||
|
<div class="potpis-linija">Saglasan sa procenom (klijent)</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="potpis-linija">Izdao{{if .TehnicarNaziv}} — {{.TehnicarNaziv}}{{end}} (servis)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="dugme-stampa" onclick="window.print()">Štampaj</button>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="sr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Revers — {{.Nalog.BrojNaloga}}</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; font-size: 13px; color: #111; background: #fff; }
|
||||||
|
|
||||||
|
.strana { max-width: 780px; margin: 0 auto; padding: 32px 40px; }
|
||||||
|
|
||||||
|
/* zaglavlje */
|
||||||
|
.zaglavlje { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; padding-bottom: 16px; border-bottom: 2px solid #111; gap: 20px; }
|
||||||
|
.firma-naziv { font-size: 20px; font-weight: 700; }
|
||||||
|
.firma-info { font-size: 11px; color: #555; margin-top: 4px; line-height: 1.7; }
|
||||||
|
.dok-naslov { text-align: right; }
|
||||||
|
.dok-tip { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; color: #555; margin-bottom: 4px; }
|
||||||
|
.dok-broj { font-size: 22px; font-weight: 700; font-family: monospace; }
|
||||||
|
.dok-datum { font-size: 12px; color: #555; margin-top: 6px; }
|
||||||
|
|
||||||
|
/* strane */
|
||||||
|
.strane-blok { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
|
||||||
|
.strana-kartica { padding: 12px 14px; border: 0.5px solid #ddd; border-radius: 6px; }
|
||||||
|
.strana-tip { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.6px; color: #888; margin-bottom: 6px; }
|
||||||
|
.strana-naziv { font-size: 14px; font-weight: 600; }
|
||||||
|
.strana-info { font-size: 12px; color: #555; margin-top: 3px; line-height: 1.6; }
|
||||||
|
|
||||||
|
/* odeljci */
|
||||||
|
.odeljak { margin-bottom: 18px; }
|
||||||
|
.odeljak-naslov { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.8px; color: #555; margin-bottom: 8px; padding-bottom: 4px; border-bottom: 0.5px solid #ccc; }
|
||||||
|
|
||||||
|
/* podaci */
|
||||||
|
.podaci-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
||||||
|
.polje-labela { font-size: 10px; color: #777; text-transform: uppercase; letter-spacing: 0.4px; margin-bottom: 3px; }
|
||||||
|
.polje-vrednost { font-size: 13px; font-weight: 500; }
|
||||||
|
.polje-vrednost-mono { font-family: monospace; font-size: 13px; font-weight: 500; }
|
||||||
|
.polje-tekst { font-size: 13px; line-height: 1.6; white-space: pre-wrap; }
|
||||||
|
.prazno { color: #aaa; font-style: italic; }
|
||||||
|
|
||||||
|
/* PIN istaknut */
|
||||||
|
.pin-vrednost { font-family: monospace; font-size: 13px; font-weight: 700; background: #fef9c3; padding: 1px 6px; border-radius: 4px; }
|
||||||
|
|
||||||
|
/* napomena klijentu */
|
||||||
|
.napomena-blok { padding: 10px 14px; border: 0.5px solid #cbd5e1; background: #f8fafc; border-radius: 6px; font-size: 12px; line-height: 1.6; white-space: pre-wrap; }
|
||||||
|
|
||||||
|
/* uslovi servisa */
|
||||||
|
.uslovi-blok { font-size: 11px; color: #444; line-height: 1.7; white-space: pre-wrap; }
|
||||||
|
|
||||||
|
/* potpisi */
|
||||||
|
.potpisi { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-top: 36px; padding-top: 16px; border-top: 0.5px solid #ccc; }
|
||||||
|
.potpis-linija { border-top: 1px solid #888; margin-top: 44px; padding-top: 6px; font-size: 11px; color: #555; text-align: center; }
|
||||||
|
|
||||||
|
/* napomena na dnu */
|
||||||
|
.dno-info { margin-top: 20px; font-size: 11px; color: #888; text-align: center; }
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body { font-size: 12px; }
|
||||||
|
.strana { padding: 16px 20px; max-width: 100%; }
|
||||||
|
.dugme-stampa { display: none !important; }
|
||||||
|
@page { size: A4; margin: 12mm 14mm; }
|
||||||
|
}
|
||||||
|
.dugme-stampa { position: fixed; bottom: 24px; right: 24px; background: #111; color: #fff; border: none; padding: 12px 22px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
|
||||||
|
.dugme-stampa:hover { background: #333; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="strana">
|
||||||
|
|
||||||
|
<!-- zaglavlje -->
|
||||||
|
<div class="zaglavlje">
|
||||||
|
<div>
|
||||||
|
<div class="firma-naziv">{{if .NazivFirme}}{{.NazivFirme}}{{else}}— naziv firme —{{end}}</div>
|
||||||
|
<div class="firma-info">
|
||||||
|
{{if .Podnazlov}}{{.Podnazlov}}<br>{{end}}
|
||||||
|
{{if .Adresa}}{{.Adresa}}<br>{{end}}
|
||||||
|
{{if .Telefon}}Tel: {{telefon .Telefon}}<br>{{end}}
|
||||||
|
{{if .PIB}}PIB: {{.PIB}}{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;align-items:flex-start;gap:14px;">
|
||||||
|
<div class="dok-naslov">
|
||||||
|
<div class="dok-tip">Revers — potvrda o prijemu</div>
|
||||||
|
<div class="dok-broj">{{.Nalog.BrojNaloga}}</div>
|
||||||
|
<div class="dok-datum">Datum prijema: {{.Nalog.DatumPrijema.Format "02.01.2006."}}</div>
|
||||||
|
{{if .Nalog.PredvidjenDatum}}
|
||||||
|
<div class="dok-datum">Predviđen rok: {{.Nalog.PredvidjenDatum.Format "02.01.2006."}}</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{if .QRKod}}
|
||||||
|
<img src="data:image/png;base64,{{.QRKod}}" width="76" height="76"
|
||||||
|
alt="QR {{.Nalog.BrojNaloga}}"
|
||||||
|
style="image-rendering:pixelated;border:1px solid #ddd;border-radius:4px;flex-shrink:0;">
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- primalac (firma) i predao (klijent) -->
|
||||||
|
<div class="strane-blok">
|
||||||
|
<div class="strana-kartica">
|
||||||
|
<div class="strana-tip">Primio (servis)</div>
|
||||||
|
<div class="strana-naziv">{{if .NazivFirme}}{{.NazivFirme}}{{else}}—{{end}}</div>
|
||||||
|
<div class="strana-info">
|
||||||
|
{{if .Adresa}}{{.Adresa}}<br>{{end}}
|
||||||
|
{{if .Telefon}}{{telefon .Telefon}}{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="strana-kartica">
|
||||||
|
<div class="strana-tip">Predao (klijent)</div>
|
||||||
|
{{if .KlijentNaziv}}
|
||||||
|
<div class="strana-naziv">{{.KlijentNaziv}}</div>
|
||||||
|
<div class="strana-info">
|
||||||
|
{{if .Klijent}}
|
||||||
|
{{if .Klijent.Telefon}}Tel: {{telefon .Klijent.Telefon}}<br>{{end}}
|
||||||
|
{{if .Klijent.Mesto}}{{.Klijent.Mesto}}<br>{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{if .KlijentBrojIdent}}{{.KlijentOznakaIdent}}: {{.KlijentBrojIdent}}{{end}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="strana-naziv prazno">— klijent nije naveden —</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- uređaj -->
|
||||||
|
<div class="odeljak">
|
||||||
|
<div class="odeljak-naslov">Uređaj na servisu</div>
|
||||||
|
<div class="podaci-grid" style="margin-bottom:10px;">
|
||||||
|
<div>
|
||||||
|
<div class="polje-labela">Naziv uređaja</div>
|
||||||
|
<div class="polje-vrednost">{{if .Nalog.Uredjaj}}{{.Nalog.Uredjaj}}{{else}}<span class="prazno">—</span>{{end}}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="polje-labela">Serijski broj</div>
|
||||||
|
<div class="polje-vrednost-mono">{{if .Nalog.SerijskiBroj}}{{.Nalog.SerijskiBroj}}{{else}}<span class="prazno">—</span>{{end}}</div>
|
||||||
|
</div>
|
||||||
|
{{if .Nalog.PinUredjaja}}
|
||||||
|
<div>
|
||||||
|
<div class="polje-labela">PIN / lozinka</div>
|
||||||
|
<div><span class="pin-vrednost">{{.Nalog.PinUredjaja}}</span></div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{if .Nalog.Pribor}}
|
||||||
|
<div style="margin-top:8px;">
|
||||||
|
<div class="polje-labela">Pribor predat uz uređaj</div>
|
||||||
|
<div class="polje-tekst">{{.Nalog.Pribor}}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .Nalog.Ostecenja}}
|
||||||
|
<div style="margin-top:8px;">
|
||||||
|
<div class="polje-labela">Vidljiva oštećenja pri prijemu</div>
|
||||||
|
<div class="polje-tekst">{{.Nalog.Ostecenja}}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .Nalog.TrazeneNadogradnje}}
|
||||||
|
<div style="margin-top:8px;">
|
||||||
|
<div class="polje-labela">Tražene nadogradnje</div>
|
||||||
|
<div class="polje-tekst">{{.Nalog.TrazeneNadogradnje}}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- prijavljeni kvar -->
|
||||||
|
<div class="odeljak">
|
||||||
|
<div class="odeljak-naslov">Prijavljeni kvar (po izjavi klijenta)</div>
|
||||||
|
<div class="polje-tekst">{{if .Nalog.OpisKvara}}{{.Nalog.OpisKvara}}{{else}}<span class="prazno">—</span>{{end}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- napomena klijentu -->
|
||||||
|
{{if .Nalog.NapomenaKlijentu}}
|
||||||
|
<div class="odeljak">
|
||||||
|
<div class="odeljak-naslov">Napomena</div>
|
||||||
|
<div class="napomena-blok">{{.Nalog.NapomenaKlijentu}}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<!-- uslovi servisa -->
|
||||||
|
{{if .Uslovi}}
|
||||||
|
<div class="odeljak">
|
||||||
|
<div class="odeljak-naslov">Uslovi servisa</div>
|
||||||
|
<div class="uslovi-blok">{{.Uslovi}}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<!-- potpisi -->
|
||||||
|
<div class="potpisi">
|
||||||
|
<div>
|
||||||
|
<div class="potpis-linija">Predao (klijent)</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="potpis-linija">Primio{{if .TehnicarNaziv}} — {{.TehnicarNaziv}}{{end}} (servis)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dno-info">Revers se izdaje u dva primerka — po jedan za klijenta i za servis. Uređaj se preuzima uz ovaj revers.</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="dugme-stampa" onclick="window.print()">Štampaj</button>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -361,6 +361,17 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// auto-snimanje — forme sa data-autosave se snimaju na svaku promenu polja
|
||||||
|
// (bez dugmeta „Sačuvaj"); 'change' se okida na blur za text/number/textarea,
|
||||||
|
// a na izbor za select/checkbox. Submit prolazi kroz AJAX logiku ispod.
|
||||||
|
document.querySelectorAll('form[data-autosave]').forEach(function(f) {
|
||||||
|
if (f._autosave) return;
|
||||||
|
f._autosave = true;
|
||||||
|
f.addEventListener('change', function() {
|
||||||
|
if (f.requestSubmit) f.requestSubmit(); else f.submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// AJAX submit — forme koje serveru vraćaju ?sacuvano= ostaju na stranici (scroll se ne gubi)
|
// AJAX submit — forme koje serveru vraćaju ?sacuvano= ostaju na stranici (scroll se ne gubi)
|
||||||
document.querySelectorAll('form[method="POST"],form[method="post"]').forEach(function(f) {
|
document.querySelectorAll('form[method="POST"],form[method="post"]').forEach(function(f) {
|
||||||
if (f._ajaxSave) return;
|
if (f._ajaxSave) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user