Servis: garantni list, radovi na otpremnici, selektivno prihvatanje predloga po stavkama
This commit is contained in:
@@ -232,6 +232,9 @@ type ServisniDeloviRepository interface {
|
||||
PrihvatiPredlozene(ctx context.Context, nalogID int64) error
|
||||
// ObrisiPredlozene briše sve predložene delove sa naloga (klijent odbio predlog)
|
||||
ObrisiPredlozene(ctx context.Context, nalogID int64) error
|
||||
// PrihvatiOdabranePoArtiklu selektivno prihvata predlozene delove po artikal_id;
|
||||
// prihvaćeni idu kroz normalnu ugradnju (skidaju sa lagera), ostali se odbacuju.
|
||||
PrihvatiOdabranePoArtiklu(ctx context.Context, nalogID int64, artikalIDs []int64) error
|
||||
}
|
||||
|
||||
// ServisniPotrazivaniDeloviRepository definiše operacije nad delovima koji nedostaju
|
||||
@@ -255,6 +258,9 @@ type ServisniRadoviRepository interface {
|
||||
PrihvatiPredlozene(ctx context.Context, nalogID int64) error
|
||||
// ObrisiPredlozene briše sve predložene radove sa naloga (klijent odbio predlog)
|
||||
ObrisiPredlozene(ctx context.Context, nalogID int64) error
|
||||
// PrihvatiOdabrane selektivno prihvata predlozene radove po ID-u;
|
||||
// prihvaćeni dobijaju predlozeno=0, ostali predlozeni se brišu.
|
||||
PrihvatiOdabrane(ctx context.Context, nalogID int64, ids []int64) error
|
||||
}
|
||||
|
||||
// MagacinskePromeneRepository definiše operacije nad revizijskim tragom magacina
|
||||
|
||||
@@ -315,6 +315,74 @@ func (r *ServisniDeloviRepo) ObrisiPredlozene(ctx context.Context, nalogID int64
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrihvatiOdabranePoArtiklu prihvata selektivno predložene delove: artikalIDs su prihvaćeni
|
||||
// (idu kroz normalnu ugradnju/lager), svi ostali predlozeni se brišu bez povraćaja lagera.
|
||||
func (r *ServisniDeloviRepo) PrihvatiOdabranePoArtiklu(ctx context.Context, nalogID int64, artikalIDs []int64) error {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiOdabranePoArtiklu: begin tx: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// Dohvati sve predložene potraživane
|
||||
redovi, err := tx.QueryContext(ctx,
|
||||
"SELECT id, artikal_id, kolicina, cena_komada FROM servisni_potrazivani_delovi WHERE nalog_id = ? AND predlozeno = 1",
|
||||
nalogID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiOdabranePoArtiklu: dohvati: %w", err)
|
||||
}
|
||||
type stavka struct {
|
||||
id int64
|
||||
artikalID int64
|
||||
kolicina int
|
||||
cena float64
|
||||
}
|
||||
var sve []stavka
|
||||
for redovi.Next() {
|
||||
var s stavka
|
||||
if err := redovi.Scan(&s.id, &s.artikalID, &s.kolicina, &s.cena); err != nil {
|
||||
redovi.Close()
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiOdabranePoArtiklu: scan: %w", err)
|
||||
}
|
||||
sve = append(sve, s)
|
||||
}
|
||||
redovi.Close()
|
||||
if err := redovi.Err(); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiOdabranePoArtiklu: rows: %w", err)
|
||||
}
|
||||
|
||||
prihvaceni := make(map[int64]bool, len(artikalIDs))
|
||||
for _, id := range artikalIDs {
|
||||
prihvaceni[id] = true
|
||||
}
|
||||
|
||||
for _, s := range sve {
|
||||
if prihvaceni[s.artikalID] {
|
||||
if _, _, err := ugradiIliPotrazujTx(ctx, tx, nalogID, s.artikalID, s.kolicina, s.cena, nil, false); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiOdabranePoArtiklu: ugradi: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Briši sve predlozene potraživane (prihvaćeni su prebačeni, odbijeni se odbacuju)
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
"DELETE FROM servisni_potrazivani_delovi WHERE nalog_id = ? AND predlozeno = 1", nalogID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiOdabranePoArtiklu: obrisi: %w", err)
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
"DELETE FROM servisni_delovi WHERE nalog_id = ? AND predlozeno = 1", nalogID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiOdabranePoArtiklu: obrisi delove: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiOdabranePoArtiklu: commit: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DohvatiArtikalID vraća artikal_id za dati servisni deo (pre brisanja)
|
||||
func (r *ServisniDeloviRepo) DohvatiArtikalID(ctx context.Context, deoID int64) (int64, error) {
|
||||
var artikalID int64
|
||||
|
||||
@@ -89,6 +89,54 @@ func (r *ServisniRadoviRepo) ObrisiPredlozene(ctx context.Context, nalogID int64
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrihvatiOdabrane prihvata selektivno predložene radove: ids su prihvaćeni (predlozeno→0),
|
||||
// ostali predlozeni se brišu.
|
||||
func (r *ServisniRadoviRepo) PrihvatiOdabrane(ctx context.Context, nalogID int64, ids []int64) error {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisniRadoviRepo.PrihvatiOdabrane: begin tx: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
prihvaceni := make(map[int64]bool, len(ids))
|
||||
for _, id := range ids {
|
||||
prihvaceni[id] = true
|
||||
}
|
||||
|
||||
// Dohvati sve predlozene radove
|
||||
redovi, err := tx.QueryContext(ctx, "SELECT id FROM servis_radovi WHERE nalog_id = ? AND predlozeno = 1", nalogID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisniRadoviRepo.PrihvatiOdabrane: dohvati: %w", err)
|
||||
}
|
||||
var sviIDs []int64
|
||||
for redovi.Next() {
|
||||
var id int64
|
||||
if err := redovi.Scan(&id); err != nil {
|
||||
redovi.Close()
|
||||
return fmt.Errorf("ntech: ServisniRadoviRepo.PrihvatiOdabrane: scan: %w", err)
|
||||
}
|
||||
sviIDs = append(sviIDs, id)
|
||||
}
|
||||
redovi.Close()
|
||||
|
||||
for _, id := range sviIDs {
|
||||
if prihvaceni[id] {
|
||||
if _, err := tx.ExecContext(ctx, "UPDATE servis_radovi SET predlozeno = 0 WHERE id = ?", id); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniRadoviRepo.PrihvatiOdabrane: update: %w", err)
|
||||
}
|
||||
} else {
|
||||
if _, err := tx.ExecContext(ctx, "DELETE FROM servis_radovi WHERE id = ?", id); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniRadoviRepo.PrihvatiOdabrane: delete: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniRadoviRepo.PrihvatiOdabrane: commit: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obrisi uklanja jednu stavku rada sa naloga
|
||||
func (r *ServisniRadoviRepo) Obrisi(ctx context.Context, id int64) error {
|
||||
if _, err := r.db.ExecContext(ctx, "DELETE FROM servis_radovi WHERE id = ?", id); err != nil {
|
||||
|
||||
@@ -1347,6 +1347,7 @@ func (h *Handler) StampaRadnogNaloga(w http.ResponseWriter, r *http.Request) {
|
||||
// PodaciOtpremnice su podaci za otpremnicu pri preuzimanju uređaja
|
||||
type PodaciOtpremnice struct {
|
||||
Nalog model.ServisniNalog
|
||||
Radovi []model.ServisniRad
|
||||
ServisniDelovi []model.ServisniDeoSaArtiklom
|
||||
UkupnoDelovi float64
|
||||
PreostaloSve float64
|
||||
@@ -1379,6 +1380,12 @@ func (h *Handler) StampaOtpremnice(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
@@ -1452,6 +1459,7 @@ func (h *Handler) StampaOtpremnice(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
h.renderujStandalone(w, "servis_otpremnica", PodaciOtpremnice{
|
||||
Nalog: *nalog,
|
||||
Radovi: radovi,
|
||||
ServisniDelovi: delovi,
|
||||
UkupnoDelovi: ukupnoDelovi,
|
||||
PreostaloSve: preostaloSve,
|
||||
@@ -2359,6 +2367,109 @@ func (h *Handler) ServisJavniPrihvati(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/status/"+token, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// ServisJavniOdlukaOdabrano obrađuje selektivno odobrenje predloga sa javne status stranice.
|
||||
// Klijent čekira pojedinačne stavke; prihvaćene idu u ugrađene, ostale se brišu.
|
||||
func (h *Handler) ServisJavniOdlukaOdabrano(w http.ResponseWriter, r *http.Request) {
|
||||
token := chi.URLParam(r, "token")
|
||||
if token == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
nalog, err := h.ServisRepo.DohvatiJavniToken(r.Context(), token)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if nalog.Status == model.StatusPreuzeto {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if nalog.OdlukaKlijenta != "" {
|
||||
http.Redirect(w, r, "/status/"+token, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "Neispravan zahtev.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
radIDs := parsInt64s(r.Form["rad_id"])
|
||||
artikalIDs := parsInt64s(r.Form["artikal_id"])
|
||||
|
||||
if err := h.ServisniRadoviRepo.PrihvatiOdabrane(r.Context(), nalog.ID, radIDs); err != nil {
|
||||
slog.Error("greška pri selektivnom prihvatanju radova", "error", err, "nalog_id", nalog.ID)
|
||||
http.Error(w, "Greška pri obradi predloga.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := h.ServisniDeloviRepo.PrihvatiOdabranePoArtiklu(r.Context(), nalog.ID, artikalIDs); err != nil {
|
||||
slog.Error("greška pri selektivnom prihvatanju delova", "error", err, "nalog_id", nalog.ID)
|
||||
http.Error(w, "Greška pri obradi predloga.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
komentar := strings.TrimSpace(r.FormValue("komentar"))
|
||||
odluka := "prihvaceno"
|
||||
if len(radIDs) == 0 && len(artikalIDs) == 0 {
|
||||
odluka = "odbijeno"
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/status/"+token, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// PrihvatiOdabraniPredlog je interni handler za selektivno prihvatanje predloga
|
||||
// (serviser štiklira stavke u detaljima naloga i klika "Prihvati odabrano").
|
||||
func (h *Handler) PrihvatiOdabraniPredlog(w http.ResponseWriter, r *http.Request) {
|
||||
_, ok := h.zahtevajDozvolu(w, r, "servis.izmeni")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
nalogID, err := parseID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "Neispravan zahtev.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
radIDs := parsInt64s(r.Form["rad_id"])
|
||||
artikalIDs := parsInt64s(r.Form["artikal_id"])
|
||||
|
||||
if err := h.ServisniRadoviRepo.PrihvatiOdabrane(r.Context(), nalogID, radIDs); err != nil {
|
||||
slog.Error("greška pri selektivnom prihvatanju radova", "error", err, "nalog_id", nalogID)
|
||||
http.Error(w, "Greška pri obradi predloga.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := h.ServisniDeloviRepo.PrihvatiOdabranePoArtiklu(r.Context(), nalogID, artikalIDs); err != nil {
|
||||
slog.Error("greška pri selektivnom prihvatanju delova", "error", err, "nalog_id", nalogID)
|
||||
http.Error(w, "Greška pri obradi predloga.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/servis/"+strconv.FormatInt(nalogID, 10), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// parsInt64s konvertuje listu string vrednosti u []int64, preskačući neispravne.
|
||||
func parsInt64s(vals []string) []int64 {
|
||||
result := make([]int64, 0, len(vals))
|
||||
for _, v := range vals {
|
||||
if id, err := strconv.ParseInt(v, 10, 64); err == nil && id > 0 {
|
||||
result = append(result, id)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ObrisiKomentarKlijenta briše poruku klijenta sa naloga
|
||||
func (h *Handler) ObrisiKomentarKlijenta(w http.ResponseWriter, r *http.Request) {
|
||||
_, ok := h.zahtevajDozvolu(w, r, "servis.izmeni")
|
||||
|
||||
Reference in New Issue
Block a user