From 9acfb2919cf7a5b5c9a53d5337357ec02cb696f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Mon, 22 Jun 2026 16:19:19 +0200 Subject: [PATCH] =?UTF-8?q?Servis:=20self-heal=20pri=20otvaranju=20detalja?= =?UTF-8?q?=20=E2=80=94=20povuci=20dostupno=20stanje=20u=20potra=C5=BEivan?= =?UTF-8?q?e=20Pri=20otvaranju=20detalja=20naloga,=20ako=20za=20potra?= =?UTF-8?q?=C5=BEivani=20artikal=20ima=20stanja=20na=20magacinu,=20automat?= =?UTF-8?q?ski=20se=20povla=C4=8Di=20(delimi=C4=8Dno=20ili=20u=20celosti)?= =?UTF-8?q?=20i=20skida=20sa=20lagera.=20Hvata=20sve=20na=C4=8Dine=20na=20?= =?UTF-8?q?koje=20stanje=20poraste=20(i=20brisanje=20dela,=20povra=C4=87aj?= =?UTF-8?q?=20prodaje),=20ne=20samo=20nabavku/izmenu/popis.=20Dodat=20test?= =?UTF-8?q?=20za=20delimi=C4=8Dno=20pokrivanje.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../servisni_potrazivani_delovi_test.go | 50 +++++++++++++++++++ internal/handler/servis.go | 27 ++++++++++ 2 files changed, 77 insertions(+) diff --git a/internal/db/sqlite/servisni_potrazivani_delovi_test.go b/internal/db/sqlite/servisni_potrazivani_delovi_test.go index 4f1ff41..7add900 100644 --- a/internal/db/sqlite/servisni_potrazivani_delovi_test.go +++ b/internal/db/sqlite/servisni_potrazivani_delovi_test.go @@ -74,6 +74,56 @@ func TestPotrazivaniTok(t *testing.T) { } } +// TestPotrazivaniDelimicnoPokrivanje: stanje (1) manje od potraživanog (3) — +// povuče se samo dostupno, ostatak ostaje na čekanju, nalog ostaje zaključan. +func TestPotrazivaniDelimicnoPokrivanje(t *testing.T) { + db := testDB(t) + ctx := context.Background() + + if _, err := db.ExecContext(ctx, "INSERT INTO artikli (id, naziv, kolicina) VALUES (1, 'Test CPU', 0)"); err != nil { + t.Fatalf("insert artikal: %v", err) + } + if _, err := db.ExecContext(ctx, + "INSERT INTO servisni_nalozi (id, broj_naloga, uredjaj, opis_kvara, status) VALUES (1, 'SN-1', 'PC', 'kvar', 'Čeka delove')", + ); err != nil { + t.Fatalf("insert nalog: %v", err) + } + // ugrađeno 2, potraživano 3 (kao da je traženo 5 a bilo 2 na stanju) + if _, err := db.ExecContext(ctx, "INSERT INTO servisni_delovi (nalog_id, artikal_id, kolicina, cena_komada) VALUES (1, 1, 2, 100)"); err != nil { + t.Fatalf("insert deo: %v", err) + } + if _, err := db.ExecContext(ctx, "INSERT INTO servisni_potrazivani_delovi (nalog_id, artikal_id, kolicina, cena_komada) VALUES (1, 1, 3, 100)"); err != nil { + t.Fatalf("insert potraživani: %v", err) + } + + // stigao samo 1 komad + if _, err := db.ExecContext(ctx, "UPDATE artikli SET kolicina = 1 WHERE id=1"); err != nil { + t.Fatalf("dopuna: %v", err) + } + + potrRepo := NoviServisniPotrazivaniDeloviRepo(db) + otkljucani, err := potrRepo.ProveriIPocistiZaArtikal(ctx, 1) + if err != nil { + t.Fatalf("ProveriIPocistiZaArtikal: %v", err) + } + // nalog se NE otključava jer još fali 2 + if len(otkljucani) != 0 { + t.Fatalf("nalog ne sme da se otključa, dobijeno %v", otkljucani) + } + // ugrađeno: 2 + 1 = 3 + if k := skalarInt(t, db, "SELECT kolicina FROM servisni_delovi WHERE nalog_id=1 AND artikal_id=1"); k != 3 { + t.Fatalf("ugrađeni deo očekivano 3, dobijeno %d", k) + } + // potraživano: 3 − 1 = 2 + if k := skalarInt(t, db, "SELECT kolicina FROM servisni_potrazivani_delovi WHERE nalog_id=1 AND artikal_id=1"); k != 2 { + t.Fatalf("potraživani očekivano 2, dobijeno %d", k) + } + // stanje magacina: 1 − 1 = 0 + if k := skalarInt(t, db, "SELECT kolicina FROM artikli WHERE id=1"); k != 0 { + t.Fatalf("stanje očekivano 0, dobijeno %d", k) + } +} + // skalarInt vraća jednu celobrojnu vrednost iz upita (za proveru stanja u testu) func skalarInt(t *testing.T, db *sql.DB, upit string) int { t.Helper() diff --git a/internal/handler/servis.go b/internal/handler/servis.go index 19214ff..40a3a8d 100644 --- a/internal/handler/servis.go +++ b/internal/handler/servis.go @@ -403,6 +403,33 @@ func (h *Handler) DetaljiNaloga(w http.ResponseWriter, r *http.Request) { return } + // self-heal: ako u međuvremenu ima stanja na magacinu za potraživane artikle, + // povuci ih (delimično ili u celosti) i otključaj nalog ako je sve pokriveno. + // Hvata sve načine na koje stanje poraste, ne samo nabavku/izmenu/popis. + if potrazivani, e := h.ServisniPotrazivaniDeloviRepo.DohvatiZaNalog(r.Context(), id); e == nil && len(potrazivani) > 0 { + vidjeni := map[int64]bool{} + for _, p := range potrazivani { + if vidjeni[p.ArtikalID] { + continue + } + vidjeni[p.ArtikalID] = true + otkljucani, err := h.ServisniPotrazivaniDeloviRepo.ProveriIPocistiZaArtikal(r.Context(), p.ArtikalID) + if err != nil { + slog.Error("self-heal potraživanih delova nije uspeo", "artikal_id", p.ArtikalID, "error", err) + continue + } + for _, nalogID := range otkljucani { + if err := h.ServisRepo.AzurirajStatus(r.Context(), nalogID, model.StatusPrimljeno); err != nil { + slog.Error("self-heal reset statusa naloga nije uspeo", "nalog_id", nalogID, "error", err) + } + } + } + // status naloga se možda promenio — ponovo ga učitaj za prikaz + if osvezen, e := h.ServisRepo.DohvatiID(r.Context(), id); e == nil && osvezen != nil { + nalog = osvezen + } + } + podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) if err != nil { http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)