Servis predlog delovi: lager integracija, brisanje predloženih delova, odluka klijenta (migracija 079)
This commit is contained in:
@@ -178,6 +178,7 @@ type KlijentRepository interface {
|
||||
ListaFilter(ctx context.Context, filter KlijentFilter) ([]model.Klijent, error)
|
||||
PrebrojiPoFilteru(ctx context.Context, filter KlijentFilter) (int, error)
|
||||
DohvatiID(ctx context.Context, id int64) (*model.Klijent, error)
|
||||
Pronadji(ctx context.Context, tip, ime, prezime, nazivFirme, jmbg, telefon, email, mesto string) (*model.Klijent, error)
|
||||
Kreiraj(ctx context.Context, k *model.Klijent) (int64, error)
|
||||
Izmeni(ctx context.Context, k *model.Klijent) error
|
||||
Obrisi(ctx context.Context, id int64) error
|
||||
@@ -201,6 +202,8 @@ type ServisRepository interface {
|
||||
AzurirajCenaDijagnostike(ctx context.Context, id int64, cena float64) error
|
||||
OdbijPopravku(ctx context.Context, id int64, cena float64) error
|
||||
AzurirajKomentarKlijenta(ctx context.Context, id int64, tekst string) error
|
||||
SacuvajOdlukuKlijenta(ctx context.Context, id int64, odluka string, odgovor string) error
|
||||
ObrisiOdlukuKlijenta(ctx context.Context, id int64) error
|
||||
Obrisi(ctx context.Context, id int64, korisnikID *int64) error
|
||||
SledeciBroj(ctx context.Context) (string, error)
|
||||
}
|
||||
@@ -236,6 +239,7 @@ type ServisniPotrazivaniDeloviRepository interface {
|
||||
DohvatiZaNalog(ctx context.Context, nalogID int64) ([]model.ServisniPotrazivaniDeo, error)
|
||||
Obrisi(ctx context.Context, id int64) error
|
||||
ObrisiZaArtikal(ctx context.Context, nalogID, artikalID int64) error
|
||||
ObrisiPredlozeneZaArtikal(ctx context.Context, nalogID, artikalID int64) error
|
||||
// ProveriIPocistiZaArtikal proverava potraživane redove za dati artikal nakon
|
||||
// povećanja stanja i briše/smanjuje redove koji se mogu pokriti (FIFO).
|
||||
// Vraća ID-eve naloga čiji je poslednji potraživani red obrisan.
|
||||
|
||||
@@ -3,6 +3,7 @@ package sqlite
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"ntech/internal/db"
|
||||
@@ -96,6 +97,115 @@ func (r *KlijentRepo) DohvatiID(ctx context.Context, id int64) (*model.Klijent,
|
||||
return &k, nil
|
||||
}
|
||||
|
||||
// Pronadji traži postojećeg klijenta po imenu i prezimenu (fizičko) ili nazivu firme (pravno).
|
||||
// Vraća nil, nil ako nije pronađen.
|
||||
// Pretraga ide od najočiglednijeg ka opštijem: JMBG/PIB → ime+prezime+mesto → telefon → email → samo ime+prezime.
|
||||
func (r *KlijentRepo) Pronadji(ctx context.Context, tip, ime, prezime, nazivFirme, jmbg, telefon, email, mesto string) (*model.Klijent, error) {
|
||||
var k model.Klijent
|
||||
var imeN, prezimeN, jmbgN, tipIdent, nazivFirmeN, pibN, telefonN, emailN, mestoN, napomena sql.NullString
|
||||
|
||||
skeniraj := func(row *sql.Row) error {
|
||||
return row.Scan(
|
||||
&k.ID, &k.Tip, &imeN, &prezimeN, &jmbgN, &tipIdent, &nazivFirmeN, &pibN, &telefonN, &emailN, &mestoN, &napomena, &k.DatumUnosa,
|
||||
)
|
||||
}
|
||||
|
||||
var row *sql.Row
|
||||
|
||||
if tip == "pravno" {
|
||||
// 1. PIB (najpreciznije za firmu)
|
||||
if jmbg != "" {
|
||||
row = r.db.QueryRowContext(ctx, `
|
||||
SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
|
||||
FROM klijenti WHERE tip = 'pravno' AND pib = ? LIMIT 1`, jmbg)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
}
|
||||
}
|
||||
// 2. Naziv firme + mesto
|
||||
if nazivFirme != "" && mesto != "" {
|
||||
row = r.db.QueryRowContext(ctx, `
|
||||
SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
|
||||
FROM klijenti WHERE tip = 'pravno' AND naziv_firme = ? AND mesto = ? LIMIT 1`, nazivFirme, mesto)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
}
|
||||
}
|
||||
// 3. Samo naziv firme
|
||||
row = r.db.QueryRowContext(ctx, `
|
||||
SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
|
||||
FROM klijenti WHERE tip = 'pravno' AND naziv_firme = ? LIMIT 1`, nazivFirme)
|
||||
if err := skeniraj(row); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.Pronadji: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Fizičko lice — više nivoa
|
||||
// 1. JMBG ili broj lične karte
|
||||
if jmbg != "" {
|
||||
row = r.db.QueryRowContext(ctx, `
|
||||
SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
|
||||
FROM klijenti WHERE tip != 'pravno' AND jmbg = ? LIMIT 1`, jmbg)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
}
|
||||
}
|
||||
// 2. Ime + prezime + mesto
|
||||
if ime != "" && prezime != "" && mesto != "" {
|
||||
row = r.db.QueryRowContext(ctx, `
|
||||
SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
|
||||
FROM klijenti WHERE tip != 'pravno' AND ime = ? AND prezime = ? AND mesto = ? LIMIT 1`, ime, prezime, mesto)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
}
|
||||
}
|
||||
// 3. Ime + prezime + telefon
|
||||
if ime != "" && prezime != "" && telefon != "" {
|
||||
row = r.db.QueryRowContext(ctx, `
|
||||
SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
|
||||
FROM klijenti WHERE tip != 'pravno' AND ime = ? AND prezime = ? AND telefon = ? LIMIT 1`, ime, prezime, telefon)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
}
|
||||
}
|
||||
// 4. Ime + prezime + email
|
||||
if ime != "" && prezime != "" && email != "" {
|
||||
row = r.db.QueryRowContext(ctx, `
|
||||
SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
|
||||
FROM klijenti WHERE tip != 'pravno' AND ime = ? AND prezime = ? AND email = ? LIMIT 1`, ime, prezime, email)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
}
|
||||
}
|
||||
// 5. Samo ime + prezime (poslednji fallback)
|
||||
row = r.db.QueryRowContext(ctx, `
|
||||
SELECT id, tip, ime, prezime, jmbg, tip_identifikacije, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
|
||||
FROM klijenti WHERE tip != 'pravno' AND ime = ? AND prezime = ? LIMIT 1`, ime, prezime)
|
||||
if err := skeniraj(row); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.Pronadji: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
popuni:
|
||||
k.Ime = imeN.String
|
||||
k.Prezime = prezimeN.String
|
||||
k.JMBG = jmbgN.String
|
||||
k.TipIdentifikacije = tipIdent.String
|
||||
k.NazivFirme = nazivFirmeN.String
|
||||
k.PIB = pibN.String
|
||||
k.Telefon = telefonN.String
|
||||
k.Email = emailN.String
|
||||
k.Mesto = mestoN.String
|
||||
k.Napomena = napomena.String
|
||||
|
||||
return &k, nil
|
||||
}
|
||||
|
||||
// Kreiraj dodaje novog klijenta u bazu
|
||||
func (r *KlijentRepo) Kreiraj(ctx context.Context, k *model.Klijent) (int64, error) {
|
||||
if k.Tip == "" {
|
||||
|
||||
@@ -58,10 +58,11 @@ func (r *ServisRepo) Lista(ctx context.Context, pretraga, status string) ([]mode
|
||||
sn.id, sn.klijent_id, sn.tehnicar_id, sn.broj_naloga, sn.uredjaj, sn.serijski_broj,
|
||||
sn.opis_kvara, sn.trazene_nadogradnje, sn.status, sn.cena_od, sn.cena_do, sn.cena_konacna,
|
||||
sn.avans, sn.napomena, sn.garancija_do, sn.garancija_dana, sn.datum_prijema, sn.datum_zavrsetka, sn.predvidjen_datum,
|
||||
sn.ostecenja, sn.pin_uredjaja, sn.pribor, sn.napomena_klijentu, sn.nalaz_dijagnostike, sn.uradjeno, sn.cena_dijagnostike, sn.popravka_odbijena, sn.javni_token, sn.komentar_klijenta,
|
||||
sn.ostecenja, sn.pin_uredjaja, sn.pribor, sn.napomena_klijentu, sn.nalaz_dijagnostike, sn.uradjeno, sn.cena_dijagnostike, sn.popravka_odbijena, sn.javni_token, sn.komentar_klijenta, sn.odluka_klijenta, sn.datum_odluke,
|
||||
COALESCE(kp.naziv, '') AS klijent_naziv,
|
||||
(EXISTS(SELECT 1 FROM servis_radovi sr WHERE sr.nalog_id = sn.id AND sr.predlozeno = 1)
|
||||
OR EXISTS(SELECT 1 FROM servisni_delovi sd WHERE sd.nalog_id = sn.id AND sd.predlozeno = 1)) AS ima_predlog
|
||||
OR EXISTS(SELECT 1 FROM servisni_delovi sd WHERE sd.nalog_id = sn.id AND sd.predlozeno = 1)
|
||||
OR EXISTS(SELECT 1 FROM servisni_potrazivani_delovi spd WHERE spd.nalog_id = sn.id AND spd.predlozeno = 1)) AS ima_predlog
|
||||
FROM servisni_nalozi sn
|
||||
LEFT JOIN klijent_prikaz kp ON kp.id = sn.klijent_id
|
||||
WHERE 1=1`
|
||||
@@ -107,7 +108,7 @@ func (r *ServisRepo) DohvatiID(ctx context.Context, id int64) (*model.ServisniNa
|
||||
id, klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj,
|
||||
opis_kvara, trazene_nadogradnje, status, cena_od, cena_do, cena_konacna,
|
||||
avans, napomena, garancija_do, garancija_dana, datum_prijema, datum_zavrsetka, predvidjen_datum,
|
||||
ostecenja, pin_uredjaja, pribor, napomena_klijentu, nalaz_dijagnostike, uradjeno, cena_dijagnostike, popravka_odbijena, javni_token, komentar_klijenta
|
||||
ostecenja, pin_uredjaja, pribor, napomena_klijentu, nalaz_dijagnostike, uradjeno, cena_dijagnostike, popravka_odbijena, javni_token, komentar_klijenta, odluka_klijenta, datum_odluke
|
||||
FROM servisni_nalozi WHERE id = ?`, id)
|
||||
|
||||
var n model.ServisniNalog
|
||||
@@ -159,7 +160,7 @@ func (r *ServisRepo) DohvatiJavniToken(ctx context.Context, token string) (*mode
|
||||
id, klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj,
|
||||
opis_kvara, trazene_nadogradnje, status, cena_od, cena_do, cena_konacna,
|
||||
avans, napomena, garancija_do, garancija_dana, datum_prijema, datum_zavrsetka, predvidjen_datum,
|
||||
ostecenja, pin_uredjaja, pribor, napomena_klijentu, nalaz_dijagnostike, uradjeno, cena_dijagnostike, popravka_odbijena, javni_token, komentar_klijenta
|
||||
ostecenja, pin_uredjaja, pribor, napomena_klijentu, nalaz_dijagnostike, uradjeno, cena_dijagnostike, popravka_odbijena, javni_token, komentar_klijenta, odluka_klijenta, datum_odluke
|
||||
FROM servisni_nalozi WHERE javni_token = ?`, token)
|
||||
|
||||
var n model.ServisniNalog
|
||||
@@ -349,6 +350,30 @@ func (r *ServisRepo) AzurirajKomentarKlijenta(ctx context.Context, id int64, tek
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObrisiOdlukuKlijenta poništava prethodnu odluku klijenta (npr. kad se doda novi predlog).
|
||||
func (r *ServisRepo) ObrisiOdlukuKlijenta(ctx context.Context, id int64) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
"UPDATE servisni_nalozi SET odluka_klijenta = NULL, komentar_klijenta = NULL, datum_odluke = NULL WHERE id = ?", id,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisRepo.ObrisiOdlukuKlijenta: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SacuvajOdlukuKlijenta beleži odluku klijenta (prihvaceno/odbijeno) i poruku.
|
||||
// Ako je prihvaćeno, poziva PrihvatiPredlozene za delove i radove.
|
||||
func (r *ServisRepo) SacuvajOdlukuKlijenta(ctx context.Context, id int64, odluka string, odgovor string) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
"UPDATE servisni_nalozi SET odluka_klijenta = ?, komentar_klijenta = ?, datum_odluke = CURRENT_TIMESTAMP WHERE id = ?",
|
||||
odluka, odgovor, id,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisRepo.SacuvajOdlukuKlijenta: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obrisi briše servisni nalog po ID-u
|
||||
// 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 {
|
||||
@@ -430,9 +455,9 @@ func (r *ServisRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) er
|
||||
// klijentNaziv je opcioni pokazivač, nil kada se čita bez JOIN-a
|
||||
func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *string, imaPredlog *bool) error {
|
||||
var klijentID, tehnicarID sql.NullInt64
|
||||
var serijskiBroj, napomena, ostecenja, pinUredjaja, pribor, napomenaKlijentu, nalazDijagnostike, uradjeno, javniToken, komentarKlijenta sql.NullString
|
||||
var serijskiBroj, napomena, ostecenja, pinUredjaja, pribor, napomenaKlijentu, nalazDijagnostike, uradjeno, javniToken, komentarKlijenta, odlukaKlijenta sql.NullString
|
||||
var cenaOd, cenaDo, cenaKonacna, avans sql.NullFloat64
|
||||
var garancijaDo, datumZavrsetka, predvidjenDatum sql.NullTime
|
||||
var garancijaDo, datumZavrsetka, predvidjenDatum, datumOdluke sql.NullTime
|
||||
var garancijaDana sql.NullInt64
|
||||
var popravkaOdbijena sql.NullInt64
|
||||
|
||||
@@ -440,7 +465,7 @@ func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *st
|
||||
&n.ID, &klijentID, &tehnicarID, &n.BrojNaloga, &n.Uredjaj, &serijskiBroj,
|
||||
&n.OpisKvara, &n.TrazeneNadogradnje, &n.Status, &cenaOd, &cenaDo, &cenaKonacna,
|
||||
&avans, &napomena, &garancijaDo, &garancijaDana, &n.DatumPrijema, &datumZavrsetka, &predvidjenDatum,
|
||||
&ostecenja, &pinUredjaja, &pribor, &napomenaKlijentu, &nalazDijagnostike, &uradjeno, &n.CenaDijagnostike, &popravkaOdbijena, &javniToken, &komentarKlijenta,
|
||||
&ostecenja, &pinUredjaja, &pribor, &napomenaKlijentu, &nalazDijagnostike, &uradjeno, &n.CenaDijagnostike, &popravkaOdbijena, &javniToken, &komentarKlijenta, &odlukaKlijenta, &datumOdluke,
|
||||
}
|
||||
|
||||
if klijentNaziv != nil {
|
||||
@@ -505,6 +530,11 @@ func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *st
|
||||
n.PopravkaOdbijena = popravkaOdbijena.Int64 != 0
|
||||
n.JavniToken = javniToken.String
|
||||
n.KomentarKlijenta = komentarKlijenta.String
|
||||
n.OdlukaKlijenta = odlukaKlijenta.String
|
||||
if datumOdluke.Valid {
|
||||
v := datumOdluke.Time
|
||||
n.DatumOdluke = &v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"ntech/internal/model"
|
||||
)
|
||||
@@ -23,7 +24,7 @@ func NoviServisniDeloviRepo(db *sql.DB) *ServisniDeloviRepo {
|
||||
func (r *ServisniDeloviRepo) DohvatiZaNalog(ctx context.Context, nalogID int64) ([]model.ServisniDeoSaArtiklom, error) {
|
||||
redovi, err := r.db.QueryContext(ctx, `
|
||||
SELECT sd.id, sd.nalog_id, sd.artikal_id, sd.kolicina, sd.cena_komada, sd.datum,
|
||||
sd.predlozeno, a.naziv
|
||||
sd.predlozeno, a.naziv, a.sifra
|
||||
FROM servisni_delovi sd
|
||||
JOIN artikli a ON a.id = sd.artikal_id
|
||||
WHERE sd.nalog_id = ?
|
||||
@@ -38,7 +39,7 @@ func (r *ServisniDeloviRepo) DohvatiZaNalog(ctx context.Context, nalogID int64)
|
||||
var d model.ServisniDeoSaArtiklom
|
||||
err := redovi.Scan(
|
||||
&d.ID, &d.NalogID, &d.ArtikalID, &d.Kolicina, &d.CenaKomada, &d.Datum,
|
||||
&d.Predlozeno, &d.ArtikalNaziv,
|
||||
&d.Predlozeno, &d.ArtikalNaziv, &d.ArtikalSifra,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: ServisniDeloviRepo.DohvatiZaNalog: scan: %w", err)
|
||||
@@ -53,25 +54,52 @@ func (r *ServisniDeloviRepo) DohvatiZaNalog(ctx context.Context, nalogID int64)
|
||||
// stanju, a višak beleži u potraživane delove. Stanje se čita i koristi unutar iste
|
||||
// transakcije pa lager NIKAD ne ide u minus (nema TOCTOU između čitanja i upisa).
|
||||
// Vraća koliko je ugrađeno i koliko nedostaje.
|
||||
//
|
||||
// Predloženi delovi (predlozeno=true) ne skidaju sa lagera — sve ide u potraživane
|
||||
// do odluke klijenta. Tek PrihvatiPredlozene skida sa lagera.
|
||||
func (r *ServisniDeloviRepo) UgradiIliPotrazuj(ctx context.Context, nalogID, artikalID int64, kolicina int, cenaKomada float64, korisnikID *int64, predlozeno bool) (ugradjeno, nedostaje int, err error) {
|
||||
predInt := 0
|
||||
if predlozeno {
|
||||
predInt = 1
|
||||
}
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: begin tx: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
ugradjeno, nedostaje, err = ugradiIliPotrazujTx(ctx, tx, nalogID, artikalID, kolicina, cenaKomada, korisnikID, predlozeno)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: commit: %w", err)
|
||||
}
|
||||
return ugradjeno, nedostaje, nil
|
||||
}
|
||||
|
||||
// ugradiIliPotrazujTx je interna verzija koja radi unutar postojeće transakcije.
|
||||
func ugradiIliPotrazujTx(ctx context.Context, tx *sql.Tx, nalogID, artikalID int64, kolicina int, cenaKomada float64, korisnikID *int64, predlozeno bool) (ugradjeno, nedostaje int, err error) {
|
||||
// Predloženi delovi: ne skidaju sa lagera, svaki predlog je poseban red (ne merge)
|
||||
if predlozeno {
|
||||
slog.Info("PREDLOG_INSERT", "nalogID", nalogID, "artikalID", artikalID, "kolicina", kolicina)
|
||||
_, err = tx.ExecContext(ctx,
|
||||
"INSERT INTO servisni_potrazivani_delovi (nalog_id, artikal_id, kolicina, cena_komada, predlozeno) VALUES (?, ?, ?, ?, 1)",
|
||||
nalogID, artikalID, kolicina, cenaKomada,
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("PREDLOG_INSERT_ERR", "err", err)
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: predlozeni: %w", err)
|
||||
}
|
||||
slog.Info("PREDLOG_INSERT_OK")
|
||||
return 0, kolicina, nil
|
||||
}
|
||||
|
||||
// Ugrađeni delovi: standardna logika — skidamo sa lagera
|
||||
var stanjePre int
|
||||
if err = tx.QueryRowContext(ctx,
|
||||
"SELECT kolicina FROM artikli WHERE id = ?", artikalID,
|
||||
).Scan(&stanjePre); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: dohvati stanje: %w", err)
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: dohvati stanje: %w", err)
|
||||
}
|
||||
|
||||
// ugrađujemo najviše onoliko koliko fizički imamo; ostatak ide u potraživane
|
||||
ugradjeno = kolicina
|
||||
if ugradjeno > stanjePre {
|
||||
ugradjeno = stanjePre
|
||||
@@ -81,75 +109,69 @@ func (r *ServisniDeloviRepo) UgradiIliPotrazuj(ctx context.Context, nalogID, art
|
||||
}
|
||||
nedostaje = kolicina - ugradjeno
|
||||
|
||||
// ugradi ono što imamo: merge u servisni_delovi, skini sa magacina, zabeleži promenu
|
||||
if ugradjeno > 0 {
|
||||
var postojeciID int64
|
||||
var postojeciKol int
|
||||
errRed := tx.QueryRowContext(ctx,
|
||||
"SELECT id, kolicina FROM servisni_delovi WHERE nalog_id = ? AND artikal_id = ? AND predlozeno = ?",
|
||||
nalogID, artikalID, predInt,
|
||||
"SELECT id, kolicina FROM servisni_delovi WHERE nalog_id = ? AND artikal_id = ? AND predlozeno = 0",
|
||||
nalogID, artikalID,
|
||||
).Scan(&postojeciID, &postojeciKol)
|
||||
if errRed == nil {
|
||||
if _, err = tx.ExecContext(ctx,
|
||||
"UPDATE servisni_delovi SET kolicina = ?, cena_komada = ? WHERE id = ?",
|
||||
postojeciKol+ugradjeno, cenaKomada, postojeciID,
|
||||
); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: merge: %w", err)
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: merge: %w", err)
|
||||
}
|
||||
} else if errors.Is(errRed, sql.ErrNoRows) {
|
||||
if _, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO servisni_delovi (nalog_id, artikal_id, kolicina, cena_komada, predlozeno)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
nalogID, artikalID, ugradjeno, cenaKomada, predInt,
|
||||
if _, err = tx.ExecContext(ctx,
|
||||
"INSERT INTO servisni_delovi (nalog_id, artikal_id, kolicina, cena_komada, predlozeno) VALUES (?, ?, ?, ?, 0)",
|
||||
nalogID, artikalID, ugradjeno, cenaKomada,
|
||||
); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: insert: %w", err)
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: insert: %w", err)
|
||||
}
|
||||
} else {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: proveri: %w", errRed)
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: proveri: %w", errRed)
|
||||
}
|
||||
|
||||
stanjePosle := stanjePre - ugradjeno
|
||||
if _, err = tx.ExecContext(ctx,
|
||||
"UPDATE artikli SET kolicina = ? WHERE id = ?", stanjePosle, artikalID,
|
||||
); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: update stanje: %w", err)
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: update stanje: %w", err)
|
||||
}
|
||||
if err = zabeleziMagacinPromenu(ctx, tx, artikalID, model.PromenaIzlazServis,
|
||||
-ugradjeno, stanjePre, stanjePosle, nalogID, korisnikID, ""); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: magacin: %w", err)
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: magacin: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// višak → potraživani delovi (ne skidamo sa lagera dok ne stigne)
|
||||
if nedostaje > 0 {
|
||||
var postojeciID int64
|
||||
var postojeciKol int
|
||||
errRed := tx.QueryRowContext(ctx,
|
||||
"SELECT id, kolicina FROM servisni_potrazivani_delovi WHERE nalog_id = ? AND artikal_id = ? AND predlozeno = ?",
|
||||
nalogID, artikalID, predInt,
|
||||
"SELECT id, kolicina FROM servisni_potrazivani_delovi WHERE nalog_id = ? AND artikal_id = ? AND predlozeno = 0",
|
||||
nalogID, artikalID,
|
||||
).Scan(&postojeciID, &postojeciKol)
|
||||
if errRed == nil {
|
||||
if _, err = tx.ExecContext(ctx,
|
||||
"UPDATE servisni_potrazivani_delovi SET kolicina = ?, cena_komada = ? WHERE id = ?",
|
||||
postojeciKol+nedostaje, cenaKomada, postojeciID,
|
||||
); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: potraživani update: %w", err)
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: potraživani update: %w", err)
|
||||
}
|
||||
} else if errors.Is(errRed, sql.ErrNoRows) {
|
||||
if _, err = tx.ExecContext(ctx,
|
||||
"INSERT INTO servisni_potrazivani_delovi (nalog_id, artikal_id, kolicina, cena_komada, predlozeno) VALUES (?, ?, ?, ?, ?)",
|
||||
nalogID, artikalID, nedostaje, cenaKomada, predInt,
|
||||
"INSERT INTO servisni_potrazivani_delovi (nalog_id, artikal_id, kolicina, cena_komada, predlozeno) VALUES (?, ?, ?, ?, 0)",
|
||||
nalogID, artikalID, nedostaje, cenaKomada,
|
||||
); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: potraživani insert: %w", err)
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: potraživani insert: %w", err)
|
||||
}
|
||||
} else {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: potraživani proveri: %w", errRed)
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: potraživani proveri: %w", errRed)
|
||||
}
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ServisniDeloviRepo.UgradiIliPotrazuj: commit: %w", err)
|
||||
}
|
||||
return ugradjeno, nedostaje, nil
|
||||
}
|
||||
|
||||
@@ -207,6 +229,7 @@ func (r *ServisniDeloviRepo) Obrisi(ctx context.Context, id int64, korisnikID *i
|
||||
|
||||
// PrihvatiPredlozene prebacuje sve predložene delove (i potraživane) naloga u
|
||||
// ugrađene (predlozeno → 0) — poziva se kad klijent prihvati predlog popravke.
|
||||
// Sada zaista skida sa lagera: predloženi delovi prethodno nisu dirali lager.
|
||||
func (r *ServisniDeloviRepo) PrihvatiPredlozene(ctx context.Context, nalogID int64) error {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
@@ -214,15 +237,51 @@ func (r *ServisniDeloviRepo) PrihvatiPredlozene(ctx context.Context, nalogID int
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
"UPDATE servisni_delovi SET predlozeno = 0 WHERE nalog_id = ? AND predlozeno = 1", nalogID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiPredlozene: delovi: %w", err)
|
||||
// Dohvati sve predložene potraživane delove
|
||||
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.PrihvatiPredlozene: dohvati predlozene: %w", err)
|
||||
}
|
||||
defer redovi.Close()
|
||||
|
||||
type predlozeniStavka struct {
|
||||
id int64
|
||||
artikalID int64
|
||||
kolicina int
|
||||
cenaKomada float64
|
||||
}
|
||||
var stavke []predlozeniStavka
|
||||
for redovi.Next() {
|
||||
var s predlozeniStavka
|
||||
if err := redovi.Scan(&s.id, &s.artikalID, &s.kolicina, &s.cenaKomada); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiPredlozene: scan: %w", err)
|
||||
}
|
||||
stavke = append(stavke, s)
|
||||
}
|
||||
|
||||
// Za svaki predloženi deo: probaj da ugradiš (skine sa lagera koliko može)
|
||||
for _, s := range stavke {
|
||||
_, _, err := ugradiIliPotrazujTx(ctx, tx, nalogID, s.artikalID, s.kolicina, s.cenaKomada, nil, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiPredlozene: ugradi: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Obriši predložene potraživane (prebačeni su u ugrađene/potraživane bez predlozeno)
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
"UPDATE servisni_potrazivani_delovi SET predlozeno = 0 WHERE nalog_id = ? AND predlozeno = 1", nalogID,
|
||||
"DELETE FROM servisni_potrazivani_delovi WHERE nalog_id = ? AND predlozeno = 1", nalogID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiPredlozene: potraživani: %w", err)
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiPredlozene: obrisi potraživane: %w", err)
|
||||
}
|
||||
|
||||
// Očisti i servisni_delovi sa predlozeno=1 (zaostali iz starije verzije)
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
"DELETE FROM servisni_delovi WHERE nalog_id = ? AND predlozeno = 1", nalogID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniDeloviRepo.PrihvatiPredlozene: obrisi delove: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
|
||||
@@ -54,6 +54,18 @@ func (r *ServisniPotrazivaniDeloviRepo) Obrisi(ctx context.Context, id int64) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObrisiPredlozeneZaArtikal briše samo predlozene redove (predlozeno=1) za dati artikal na nalogu
|
||||
func (r *ServisniPotrazivaniDeloviRepo) ObrisiPredlozeneZaArtikal(ctx context.Context, nalogID, artikalID int64) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
"DELETE FROM servisni_potrazivani_delovi WHERE nalog_id = ? AND artikal_id = ? AND predlozeno = 1",
|
||||
nalogID, artikalID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisniPotrazivaniDeloviRepo.ObrisiPredlozeneZaArtikal: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObrisiZaArtikal briše sve potraživane delove za dati artikal na datom nalogu
|
||||
func (r *ServisniPotrazivaniDeloviRepo) ObrisiZaArtikal(ctx context.Context, nalogID, artikalID int64) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
@@ -96,7 +108,7 @@ func (r *ServisniPotrazivaniDeloviRepo) ProveriIPocistiZaArtikal(ctx context.Con
|
||||
}
|
||||
|
||||
redovi, err := tx.QueryContext(ctx,
|
||||
"SELECT id, nalog_id, kolicina, cena_komada FROM servisni_potrazivani_delovi WHERE artikal_id = ? ORDER BY datum",
|
||||
"SELECT id, nalog_id, kolicina, cena_komada FROM servisni_potrazivani_delovi WHERE artikal_id = ? AND predlozeno = 0 ORDER BY datum",
|
||||
artikalID,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -165,7 +177,7 @@ func (r *ServisniPotrazivaniDeloviRepo) ProveriIPocistiZaArtikal(ctx context.Con
|
||||
for nalogID := range obrisaniNalozi {
|
||||
var preostalo int
|
||||
if err := tx.QueryRowContext(ctx,
|
||||
"SELECT COUNT(*) FROM servisni_potrazivani_delovi WHERE nalog_id = ?", nalogID,
|
||||
"SELECT COUNT(*) FROM servisni_potrazivani_delovi WHERE nalog_id = ? AND predlozeno = 0", nalogID,
|
||||
).Scan(&preostalo); err != nil {
|
||||
return nil, fmt.Errorf("ntech: ServisniPotrazivaniDeloviRepo.ProveriIPocistiZaArtikal: prebroji: %w", err)
|
||||
}
|
||||
@@ -187,7 +199,7 @@ func (r *ServisniPotrazivaniDeloviRepo) ugradiUNalog(ctx context.Context, tx *sq
|
||||
var postojeciID int64
|
||||
var postojeciKol int
|
||||
err := tx.QueryRowContext(ctx,
|
||||
"SELECT id, kolicina FROM servisni_delovi WHERE nalog_id = ? AND artikal_id = ?",
|
||||
"SELECT id, kolicina FROM servisni_delovi WHERE nalog_id = ? AND artikal_id = ? AND predlozeno = 0",
|
||||
nalogID, artikalID,
|
||||
).Scan(&postojeciID, &postojeciKol)
|
||||
|
||||
|
||||
@@ -124,6 +124,50 @@ func TestPotrazivaniDelimicnoPokrivanje(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPotrazivaniPredlozeniSeNePokrivaju: predloženi delovi (predlozeno=1) ne smeju
|
||||
// biti auto-konvertovani u ugrađene niti smeti otključavanje naloga.
|
||||
func TestPotrazivaniPredlozeniSeNePokrivaju(t *testing.T) {
|
||||
db := testDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
if _, err := db.ExecContext(ctx, "INSERT INTO artikli (id, naziv, kolicina) VALUES (1, 'GPU', 5)"); 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', 'U dijagnostici')",
|
||||
); err != nil {
|
||||
t.Fatalf("insert nalog: %v", err)
|
||||
}
|
||||
// predloženi deo serviser predlaže klijentu (predlozeno=1) — ne sme biti auto-ugradjen
|
||||
if _, err := db.ExecContext(ctx,
|
||||
"INSERT INTO servisni_potrazivani_delovi (nalog_id, artikal_id, kolicina, cena_komada, predlozeno) VALUES (1, 1, 3, 100, 1)",
|
||||
); err != nil {
|
||||
t.Fatalf("insert predlozeni: %v", err)
|
||||
}
|
||||
|
||||
potrRepo := NoviServisniPotrazivaniDeloviRepo(db)
|
||||
otkljucani, err := potrRepo.ProveriIPocistiZaArtikal(ctx, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("ProveriIPocistiZaArtikal: %v", err)
|
||||
}
|
||||
// nalog se NE otključava — predlozeni redovi se ignorišu
|
||||
if len(otkljucani) != 0 {
|
||||
t.Fatalf("nalog ne sme da se otključa zbog predlozenih redova, dobijeno %v", otkljucani)
|
||||
}
|
||||
// predlozeni red mora ostati nepromenjen
|
||||
if k := skalarInt(t, db, "SELECT kolicina FROM servisni_potrazivani_delovi WHERE nalog_id=1 AND predlozeno=1"); k != 3 {
|
||||
t.Fatalf("predlozeni deo mora ostati 3, dobijeno %d", k)
|
||||
}
|
||||
// magacin se ne sme dirljati
|
||||
if stanje := skalarInt(t, db, "SELECT kolicina FROM artikli WHERE id=1"); stanje != 5 {
|
||||
t.Fatalf("stanje magacina mora ostati 5, dobijeno %d", stanje)
|
||||
}
|
||||
// u servisni_delovi ne sme biti ništa ubačeno
|
||||
if k := skalarInt(t, db, "SELECT COUNT(*) FROM servisni_delovi WHERE nalog_id=1"); k != 0 {
|
||||
t.Fatalf("servisni_delovi mora biti prazan, 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()
|
||||
|
||||
@@ -21,8 +21,11 @@ func NoviServisniRadoviRepo(baza *sql.DB) *ServisniRadoviRepo {
|
||||
// DohvatiZaNalog vraća sve radove (usluge) jednog naloga, redom unosa
|
||||
func (r *ServisniRadoviRepo) DohvatiZaNalog(ctx context.Context, nalogID int64) ([]model.ServisniRad, error) {
|
||||
redovi, err := r.db.QueryContext(ctx, `
|
||||
SELECT id, nalog_id, COALESCE(usluga_id, 0), naziv, kolicina, cena_komada, datum, predlozeno
|
||||
FROM servis_radovi WHERE nalog_id = ? ORDER BY id`, nalogID)
|
||||
SELECT sr.id, sr.nalog_id, COALESCE(sr.usluga_id, 0), sr.naziv, sr.kolicina, sr.cena_komada, sr.datum, sr.predlozeno,
|
||||
COALESCE(u.sifra, '')
|
||||
FROM servis_radovi sr
|
||||
LEFT JOIN usluge u ON u.id = sr.usluga_id
|
||||
WHERE sr.nalog_id = ? ORDER BY sr.id`, nalogID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: ServisniRadoviRepo.DohvatiZaNalog: %w", err)
|
||||
}
|
||||
@@ -31,7 +34,7 @@ func (r *ServisniRadoviRepo) DohvatiZaNalog(ctx context.Context, nalogID int64)
|
||||
var radovi []model.ServisniRad
|
||||
for redovi.Next() {
|
||||
var rad model.ServisniRad
|
||||
if err := redovi.Scan(&rad.ID, &rad.NalogID, &rad.UslugaID, &rad.Naziv, &rad.Kolicina, &rad.CenaKomada, &rad.Datum, &rad.Predlozeno); err != nil {
|
||||
if err := redovi.Scan(&rad.ID, &rad.NalogID, &rad.UslugaID, &rad.Naziv, &rad.Kolicina, &rad.CenaKomada, &rad.Datum, &rad.Predlozeno, &rad.Sifra); err != nil {
|
||||
return nil, fmt.Errorf("ntech: ServisniRadoviRepo.DohvatiZaNalog: %w", err)
|
||||
}
|
||||
radovi = append(radovi, rad)
|
||||
|
||||
Reference in New Issue
Block a user