From 20b161a89d472c952228cc1cee53cce0df622242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Mon, 22 Jun 2026 17:20:45 +0200 Subject: [PATCH] =?UTF-8?q?Servis:=20garancija=20u=20formi=20izmene,=20kar?= =?UTF-8?q?tica=20Ukupno,=20povra=C4=87aj=20delova=20pri=20brisanju?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Forma izmene: polje Garancija do sa kvačicom Bez garancije, pored Avansa; veći razmak između kartica - Detalji: kartica Ukupno (usluge + ugrađeni delovi) pre zone za brisanje - Brisanje naloga vraća ugrađene delove na stanje u magacin (transakcija), čisti potraživane delove --- internal/db/repository.go | 2 +- internal/db/sqlite/servis.go | 73 +++++++++++++++++++++- internal/handler/servis.go | 14 +++-- web/templates/stranice/servis_detalji.html | 21 +++++++ web/templates/stranice/servis_forma.html | 34 +++++++--- 5 files changed, 127 insertions(+), 17 deletions(-) diff --git a/internal/db/repository.go b/internal/db/repository.go index 90d81ae..7f1c31a 100644 --- a/internal/db/repository.go +++ b/internal/db/repository.go @@ -192,7 +192,7 @@ type ServisRepository interface { Izmeni(ctx context.Context, n *model.ServisniNalog) error AzurirajStatus(ctx context.Context, id int64, status string) error AzurirajGaranciju(ctx context.Context, id int64, garancijaDo *time.Time) error - Obrisi(ctx context.Context, id int64) error + Obrisi(ctx context.Context, id int64, korisnikID *int64) error SledeciBroj(ctx context.Context) (string, error) } diff --git a/internal/db/sqlite/servis.go b/internal/db/sqlite/servis.go index cc74e6c..85a210c 100644 --- a/internal/db/sqlite/servis.go +++ b/internal/db/sqlite/servis.go @@ -230,10 +230,77 @@ func (r *ServisRepo) AzurirajGaranciju(ctx context.Context, id int64, garancijaD } // Obrisi briše servisni nalog po ID-u -func (r *ServisRepo) Obrisi(ctx context.Context, id int64) error { - _, err := r.db.ExecContext(ctx, "DELETE FROM servisni_nalozi WHERE id = ?", id) +// 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 { + tx, err := r.db.BeginTx(ctx, nil) if err != nil { - return fmt.Errorf("ntech: ServisRepo.Obrisi: %w", err) + return fmt.Errorf("ntech: ServisRepo.Obrisi: begin tx: %w", err) + } + defer tx.Rollback() + + // pokupi ugrađene delove pre brisanja (CASCADE bi ih obrisao bez povraćaja) + redovi, err := tx.QueryContext(ctx, + "SELECT artikal_id, kolicina FROM servisni_delovi WHERE nalog_id = ?", id) + if err != nil { + return fmt.Errorf("ntech: ServisRepo.Obrisi: dohvati delove: %w", err) + } + type povrat struct { + artikalID int64 + kolicina int + } + var delovi []povrat + for redovi.Next() { + var p povrat + if err := redovi.Scan(&p.artikalID, &p.kolicina); err != nil { + redovi.Close() + return fmt.Errorf("ntech: ServisRepo.Obrisi: scan dela: %w", err) + } + delovi = append(delovi, p) + } + redovi.Close() + + for _, p := range delovi { + // usluge i troškovi nemaju stanje na lageru — vraćamo samo proizvodima + var stanjePre int + var tip string + err := tx.QueryRowContext(ctx, + "SELECT kolicina, tip FROM artikli WHERE id = ?", p.artikalID, + ).Scan(&stanjePre, &tip) + if err != nil { + return fmt.Errorf("ntech: ServisRepo.Obrisi: dohvati stanje: %w", err) + } + if !(tip == model.TipProizvod || tip == "") { + continue + } + + stanjePosle := stanjePre + p.kolicina + _, err = tx.ExecContext(ctx, + "UPDATE artikli SET kolicina = ? WHERE id = ?", stanjePosle, p.artikalID, + ) + if err != nil { + return fmt.Errorf("ntech: ServisRepo.Obrisi: vrati stanje: %w", err) + } + + err = zabeleziMagacinPromenu(ctx, tx, p.artikalID, model.PromenaPovracaj, + p.kolicina, stanjePre, stanjePosle, id, korisnikID, "brisanje servisnog naloga") + if err != nil { + return fmt.Errorf("ntech: ServisRepo.Obrisi: magacin: %w", err) + } + } + + // potraživani delovi nemaju ON DELETE CASCADE — ručno ih čistimo da brisanje ne padne na FK + if _, err := tx.ExecContext(ctx, + "DELETE FROM servisni_potrazivani_delovi WHERE nalog_id = ?", id); err != nil { + return fmt.Errorf("ntech: ServisRepo.Obrisi: potraživani: %w", err) + } + + // CASCADE briše servisni_delovi i servisni_radovi + if _, err := tx.ExecContext(ctx, "DELETE FROM servisni_nalozi WHERE id = ?", id); err != nil { + return fmt.Errorf("ntech: ServisRepo.Obrisi: delete: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("ntech: ServisRepo.Obrisi: commit: %w", err) } return nil diff --git a/internal/handler/servis.go b/internal/handler/servis.go index 18b6008..785f459 100644 --- a/internal/handler/servis.go +++ b/internal/handler/servis.go @@ -327,16 +327,16 @@ func (h *Handler) SacuvajIzmenaNaloga(w http.ResponseWriter, r *http.Request) { nalog.ID = id - // forma izmene više ne šalje status, datume, cene ni klijenta — očuvaj postojeće - // vrednosti (status se menja u detaljima, cena rada se računa iz radova, garancija - // i datum završetka se postavljaju kasnije). Inače bi prazna polja pregazila bazu. + // forma izmene više ne šalje status, datume (osim garancije), cene ni klijenta — + // očuvaj postojeće vrednosti (status se menja u detaljima, cena rada se računa iz + // radova, datum završetka se postavlja kasnije). Garancija se sada uređuje u formi, + // pa je čuvamo iz parseFormuNaloga. Inače bi prazna polja pregazila bazu. if stari, e := h.ServisRepo.DohvatiID(r.Context(), id); e == nil && stari != nil { nalog.Status = stari.Status nalog.KlijentID = stari.KlijentID nalog.CenaOd = stari.CenaOd nalog.CenaDo = stari.CenaDo nalog.CenaKonacna = stari.CenaKonacna - nalog.GarancijaDo = stari.GarancijaDo nalog.DatumZavrsetka = stari.DatumZavrsetka } @@ -385,7 +385,8 @@ func (h *Handler) SacuvajIzmenaNaloga(w http.ResponseWriter, r *http.Request) { // ObrisiNalog prima POST zahtev i briše servisni nalog po ID-u func (h *Handler) ObrisiNalog(w http.ResponseWriter, r *http.Request) { - if _, ok := h.zahtevajDozvolu(w, r, "servis.obrisi"); !ok { + k, ok := h.zahtevajDozvolu(w, r, "servis.obrisi") + if !ok { return } id, err := parseID(chi.URLParam(r, "id")) @@ -394,7 +395,8 @@ func (h *Handler) ObrisiNalog(w http.ResponseWriter, r *http.Request) { return } - if err := h.ServisRepo.Obrisi(r.Context(), id); err != nil { + if err := h.ServisRepo.Obrisi(r.Context(), id, &k.ID); err != nil { + slog.Error("greška pri brisanju naloga", "id", id, "error", err) http.Error(w, "Greška pri brisanju naloga", http.StatusInternalServerError) return } diff --git a/web/templates/stranice/servis_detalji.html b/web/templates/stranice/servis_detalji.html index deca651..0923e85 100644 --- a/web/templates/stranice/servis_detalji.html +++ b/web/templates/stranice/servis_detalji.html @@ -333,6 +333,27 @@ + +
+
+ Ukupno +
+
+
+ Usluge (rad) + {{dinari .UkupnoRadovi}} din +
+
+ Ugrađeni delovi + {{dinari .UkupnoDelovi}} din +
+
+ Ukupno + {{dinari .UkupnoSve}} din +
+
+
+
diff --git a/web/templates/stranice/servis_forma.html b/web/templates/stranice/servis_forma.html index 3601529..04de7ad 100644 --- a/web/templates/stranice/servis_forma.html +++ b/web/templates/stranice/servis_forma.html @@ -29,7 +29,7 @@ -
+
{{if .Izmena}}Izmeni nalog{{else}}Novi nalog{{end}} @@ -91,7 +91,7 @@ {{if .Izmena}} -
+
Tehničar
@@ -104,7 +104,7 @@
{{else}} -
+
Klijent
@@ -259,18 +259,38 @@
{{end}} - -
-
Avans i napomena
+ +
+
{{if .Izmena}}Avans, garancija i napomena{{else}}Avans i napomena{{end}}
-
+
+ {{if .Izmena}} + +
+ +
+ + +
+ + +
+ {{end}}
+