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 @@ + +