Servis: servisni_log tabela — praćenje datuma događaja za eskalacioni list
This commit is contained in:
@@ -386,6 +386,12 @@ type FiskalRepository interface {
|
|||||||
OznačiKaoStorniran(ctx context.Context, id int64) error
|
OznačiKaoStorniran(ctx context.Context, id int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServisniLogRepository definiše operacije nad log-om događaja servisnog naloga.
|
||||||
|
type ServisniLogRepository interface {
|
||||||
|
Kreiraj(ctx context.Context, nalogID int64, dogadjaj string, korisnikID *int64) error
|
||||||
|
DohvatiZaNalog(ctx context.Context, nalogID int64) ([]model.ServisniLog, error)
|
||||||
|
}
|
||||||
|
|
||||||
// KpoRepository definiše operacije nad knjigom o ostvarenom prometu.
|
// KpoRepository definiše operacije nad knjigom o ostvarenom prometu.
|
||||||
type KpoRepository interface {
|
type KpoRepository interface {
|
||||||
Lista(ctx context.Context, od, do time.Time) ([]model.KpoZapis, error)
|
Lista(ctx context.Context, od, do time.Time) ([]model.KpoZapis, error)
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"ntech/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServisniLogRepo je SQLite implementacija db.ServisniLogRepository
|
||||||
|
type ServisniLogRepo struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoviServisniLogRepo kreira repozitorijum log-a servisnih naloga
|
||||||
|
func NoviServisniLogRepo(baza *sql.DB) *ServisniLogRepo {
|
||||||
|
return &ServisniLogRepo{db: baza}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kreiraj upisuje novi događaj u log servisnog naloga.
|
||||||
|
func (r *ServisniLogRepo) Kreiraj(ctx context.Context, nalogID int64, dogadjaj string, korisnikID *int64) error {
|
||||||
|
_, err := r.db.ExecContext(ctx,
|
||||||
|
`INSERT INTO servisni_log (nalog_id, dogadjaj, korisnik_id) VALUES (?, ?, ?)`,
|
||||||
|
nalogID, dogadjaj, korisnikID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DohvatiZaNalog vraća sve log zapise za dati nalog, od najstarijeg ka najnovijem.
|
||||||
|
func (r *ServisniLogRepo) DohvatiZaNalog(ctx context.Context, nalogID int64) ([]model.ServisniLog, error) {
|
||||||
|
rows, err := r.db.QueryContext(ctx,
|
||||||
|
`SELECT id, nalog_id, dogadjaj, COALESCE(napomena,''), korisnik_id, datum
|
||||||
|
FROM servisni_log WHERE nalog_id = ? ORDER BY datum ASC`,
|
||||||
|
nalogID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var lista []model.ServisniLog
|
||||||
|
for rows.Next() {
|
||||||
|
var l model.ServisniLog
|
||||||
|
if err := rows.Scan(&l.ID, &l.NalogID, &l.Dogadjaj, &l.Napomena, &l.KorisnikID, &l.Datum); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lista = append(lista, l)
|
||||||
|
}
|
||||||
|
return lista, rows.Err()
|
||||||
|
}
|
||||||
@@ -47,6 +47,7 @@ type Handler struct {
|
|||||||
NivelacijaRepo db.NivelacijaRepository
|
NivelacijaRepo db.NivelacijaRepository
|
||||||
FiskalRepo db.FiskalRepository
|
FiskalRepo db.FiskalRepository
|
||||||
KpoRepo db.KpoRepository
|
KpoRepo db.KpoRepository
|
||||||
|
ServisniLogRepo db.ServisniLogRepository
|
||||||
Verzija string
|
Verzija string
|
||||||
JelDemo bool
|
JelDemo bool
|
||||||
AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju)
|
AssetV string // verzija statičkih fajlova za cache-busting (postavlja se pri pokretanju)
|
||||||
@@ -117,6 +118,7 @@ func Novi(baza *sql.DB, totpKljuc []byte) *Handler {
|
|||||||
NivelacijaRepo: sqlite.NoviNivelacijaRepo(baza),
|
NivelacijaRepo: sqlite.NoviNivelacijaRepo(baza),
|
||||||
FiskalRepo: sqlite.NoviFiskalRepo(baza),
|
FiskalRepo: sqlite.NoviFiskalRepo(baza),
|
||||||
KpoRepo: sqlite.NoviKpoRepo(baza),
|
KpoRepo: sqlite.NoviKpoRepo(baza),
|
||||||
|
ServisniLogRepo: sqlite.NoviServisniLogRepo(baza),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +154,7 @@ func (h *Handler) reinicijalizujRepozitorijume(novaDB *sql.DB) {
|
|||||||
h.NivelacijaRepo = sqlite.NoviNivelacijaRepo(novaDB)
|
h.NivelacijaRepo = sqlite.NoviNivelacijaRepo(novaDB)
|
||||||
h.FiskalRepo = sqlite.NoviFiskalRepo(novaDB)
|
h.FiskalRepo = sqlite.NoviFiskalRepo(novaDB)
|
||||||
h.KpoRepo = sqlite.NoviKpoRepo(novaDB)
|
h.KpoRepo = sqlite.NoviKpoRepo(novaDB)
|
||||||
|
h.ServisniLogRepo = sqlite.NoviServisniLogRepo(novaDB)
|
||||||
}
|
}
|
||||||
|
|
||||||
// modulUkljucen vraća da li je zakonski modul (npr. „pdv") uključen za firmu prema profilu.
|
// modulUkljucen vraća da li je zakonski modul (npr. „pdv") uključen za firmu prema profilu.
|
||||||
|
|||||||
@@ -1702,6 +1702,8 @@ type PodaciEskalacionogLista struct {
|
|||||||
PIB string
|
PIB string
|
||||||
MaticniBroj string
|
MaticniBroj string
|
||||||
Barkod string
|
Barkod string
|
||||||
|
// LogDogadjaji mapa dogadjaj→vreme, npr. "status:U dijagnostici"→datum
|
||||||
|
LogDogadjaji map[string]time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// StampaEskalacionogLista renderuje interni list praćenja rokova.
|
// StampaEskalacionogLista renderuje interni list praćenja rokova.
|
||||||
@@ -1745,6 +1747,15 @@ func (h *Handler) StampaEskalacionogLista(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logZapisi, _ := h.ServisniLogRepo.DohvatiZaNalog(r.Context(), id)
|
||||||
|
logDogadjaji := make(map[string]time.Time, len(logZapisi))
|
||||||
|
for _, l := range logZapisi {
|
||||||
|
// čuvamo samo prvi zapis za svaki tip događaja
|
||||||
|
if _, postoji := logDogadjaji[l.Dogadjaj]; !postoji {
|
||||||
|
logDogadjaji[l.Dogadjaj] = l.Datum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h.renderujStandalone(w, "servis_eskalacioni_list", PodaciEskalacionogLista{
|
h.renderujStandalone(w, "servis_eskalacioni_list", PodaciEskalacionogLista{
|
||||||
Nalog: *nalog,
|
Nalog: *nalog,
|
||||||
KlijentNaziv: klijentNaziv,
|
KlijentNaziv: klijentNaziv,
|
||||||
@@ -1759,6 +1770,7 @@ func (h *Handler) StampaEskalacionogLista(w http.ResponseWriter, r *http.Request
|
|||||||
PIB: podesavanja["pib"],
|
PIB: podesavanja["pib"],
|
||||||
MaticniBroj: podesavanja["maticni_broj"],
|
MaticniBroj: podesavanja["maticni_broj"],
|
||||||
Barkod: barkodNaloga(nalog.BrojNaloga),
|
Barkod: barkodNaloga(nalog.BrojNaloga),
|
||||||
|
LogDogadjaji: logDogadjaji,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2123,6 +2135,13 @@ func (h *Handler) PromeniStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "Greška pri promeni statusa", http.StatusInternalServerError)
|
http.Error(w, "Greška pri promeni statusa", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// log događaja — best-effort, greška se ignoriše
|
||||||
|
if k := middleware.KorisnikIzKonteksta(r.Context()); k != nil {
|
||||||
|
kid := k.ID
|
||||||
|
_ = h.ServisniLogRepo.Kreiraj(r.Context(), id, "status:"+noviStatus, &kid)
|
||||||
|
} else {
|
||||||
|
_ = h.ServisniLogRepo.Kreiraj(r.Context(), id, "status:"+noviStatus, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// pri prelasku u Preuzeto — auto-izračunaj cenu_konacnu ako nije uneta, pa sačuvaj naplatu i fiskalizuj
|
// pri prelasku u Preuzeto — auto-izračunaj cenu_konacnu ako nije uneta, pa sačuvaj naplatu i fiskalizuj
|
||||||
if noviStatus == model.StatusPreuzeto {
|
if noviStatus == model.StatusPreuzeto {
|
||||||
@@ -2934,6 +2953,7 @@ func (h *Handler) ServisJavniPrihvati(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err := h.ServisRepo.SacuvajOdlukuKlijenta(r.Context(), nalog.ID, "prihvaceno", komentar); err != nil {
|
if err := h.ServisRepo.SacuvajOdlukuKlijenta(r.Context(), nalog.ID, "prihvaceno", komentar); err != nil {
|
||||||
slog.Error("greška pri čuvanju odluke klijenta", "error", err, "nalog_id", nalog.ID)
|
slog.Error("greška pri čuvanju odluke klijenta", "error", err, "nalog_id", nalog.ID)
|
||||||
}
|
}
|
||||||
|
_ = h.ServisniLogRepo.Kreiraj(r.Context(), nalog.ID, "odluka:prihvaceno", nil)
|
||||||
|
|
||||||
http.Redirect(w, r, "/status/"+token, http.StatusSeeOther)
|
http.Redirect(w, r, "/status/"+token, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
@@ -2990,6 +3010,7 @@ func (h *Handler) ServisJavniOdlukaOdabrano(w http.ResponseWriter, r *http.Reque
|
|||||||
if err := h.ServisRepo.SacuvajOdlukuKlijenta(r.Context(), nalog.ID, odluka, komentar); err != nil {
|
if err := h.ServisRepo.SacuvajOdlukuKlijenta(r.Context(), nalog.ID, odluka, komentar); err != nil {
|
||||||
slog.Error("greška pri čuvanju odluke klijenta", "error", err, "nalog_id", nalog.ID)
|
slog.Error("greška pri čuvanju odluke klijenta", "error", err, "nalog_id", nalog.ID)
|
||||||
}
|
}
|
||||||
|
_ = h.ServisniLogRepo.Kreiraj(r.Context(), nalog.ID, "odluka:"+odluka, nil)
|
||||||
|
|
||||||
http.Redirect(w, r, "/status/"+token, http.StatusSeeOther)
|
http.Redirect(w, r, "/status/"+token, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
@@ -3101,6 +3122,7 @@ func (h *Handler) ServisJavniOdbij(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err := h.ServisRepo.SacuvajOdlukuKlijenta(r.Context(), nalog.ID, "odbijeno", komentar); err != nil {
|
if err := h.ServisRepo.SacuvajOdlukuKlijenta(r.Context(), nalog.ID, "odbijeno", komentar); err != nil {
|
||||||
slog.Error("greška pri čuvanju odluke klijenta", "error", err, "nalog_id", nalog.ID)
|
slog.Error("greška pri čuvanju odluke klijenta", "error", err, "nalog_id", nalog.ID)
|
||||||
}
|
}
|
||||||
|
_ = h.ServisniLogRepo.Kreiraj(r.Context(), nalog.ID, "odluka:odbijeno", nil)
|
||||||
|
|
||||||
http.Redirect(w, r, "/status/"+token, http.StatusSeeOther)
|
http.Redirect(w, r, "/status/"+token, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,16 @@ type ServisniNalog struct {
|
|||||||
Naplaceno float64 // iznos koji je naplaćen pri preuzimanju
|
Naplaceno float64 // iznos koji je naplaćen pri preuzimanju
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServisniLog je jedan zapis u istoriji događaja servisnog naloga.
|
||||||
|
type ServisniLog struct {
|
||||||
|
ID int64
|
||||||
|
NalogID int64
|
||||||
|
Dogadjaj string // npr. "status:U dijagnostici", "odluka:prihvaceno"
|
||||||
|
Napomena string
|
||||||
|
KorisnikID *int64
|
||||||
|
Datum time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// ServisniDeo predstavlja jedan artikal ugrađen u servisni nalog
|
// ServisniDeo predstavlja jedan artikal ugrađen u servisni nalog
|
||||||
type ServisniDeo struct {
|
type ServisniDeo struct {
|
||||||
ID int64
|
ID int64
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS servisni_log (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
nalog_id INTEGER NOT NULL REFERENCES servisni_nalozi(id) ON DELETE CASCADE,
|
||||||
|
dogadjaj TEXT NOT NULL, -- npr. "status:U dijagnostici", "odluka:prihvaceno", "poziv_klijenta"
|
||||||
|
napomena TEXT,
|
||||||
|
korisnik_id INTEGER REFERENCES korisnici(id),
|
||||||
|
datum DATETIME NOT NULL DEFAULT (datetime('now','localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_servisni_log_nalog ON servisni_log(nalog_id);
|
||||||
@@ -126,11 +126,19 @@
|
|||||||
<td><strong>{{.Nalog.DatumPrijema.Format "02.01.2006."}}</strong> <span class="badge-auto">auto</span></td>
|
<td><strong>{{.Nalog.DatumPrijema.Format "02.01.2006."}}</strong> <span class="badge-auto">auto</span></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{{with index .LogDogadjaji "status:U dijagnostici"}}
|
||||||
|
<tr class="auto">
|
||||||
|
<td>Početak dijagnostike</td>
|
||||||
|
<td><strong>{{.Format "02.01.2006."}}</strong> <span class="badge-auto">auto</span></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{{else}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Početak dijagnostike</td>
|
<td>Početak dijagnostike</td>
|
||||||
<td><span class="linija-unos"></span></td>
|
<td><span class="linija-unos"></span></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{{end}}
|
||||||
{{if .Nalog.PredvidjenDatum}}
|
{{if .Nalog.PredvidjenDatum}}
|
||||||
<tr class="auto">
|
<tr class="auto">
|
||||||
<td>Predviđeni datum završetka</td>
|
<td>Predviđeni datum završetka</td>
|
||||||
@@ -190,11 +198,19 @@
|
|||||||
<td><span class="linija-unos"></span></td>
|
<td><span class="linija-unos"></span></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{{with index .LogDogadjaji "status:Preuzeto"}}
|
||||||
|
<tr class="auto">
|
||||||
|
<td>Preuzimanje uređaja</td>
|
||||||
|
<td><strong>{{.Format "02.01.2006."}}</strong> <span class="badge-auto">auto</span></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{{else}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Preuzimanje uređaja</td>
|
<td>Preuzimanje uređaja</td>
|
||||||
<td><span class="linija-unos"></span></td>
|
<td><span class="linija-unos"></span></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{{end}}
|
||||||
{{if .RokPodizanja}}
|
{{if .RokPodizanja}}
|
||||||
<tr class="auto rok-upozorenje">
|
<tr class="auto rok-upozorenje">
|
||||||
<td><strong>Rok za podizanje</strong></td>
|
<td><strong>Rok za podizanje</strong></td>
|
||||||
|
|||||||
Reference in New Issue
Block a user