Files
GoNtech/internal/db/sqlite/servisni_delovi.go
T
Dasko d4dcecb3ea Servis: potrazivani delovi, garancija, zakljucavanje statusa
Nova tabela servisni_potrazivani_delovi — visak preko stanja ide u potrazivane, lager NE ide u minus. Merge istog artikla na nalogu. Vizuelni indikator prekoracenja u tabeli (crveno). Status zakljucan na Ceka delove dok ima potrazivanih. Garancija: kalendar sa default vrednoscu. data-refresh podrzava vise selektora. Brisanje dela brise i potrazivane. Inline animation:none za AJAX refresh.
2026-06-22 03:17:29 +02:00

195 lines
6.2 KiB
Go

package sqlite
import (
"context"
"database/sql"
"errors"
"fmt"
"ntech/internal/model"
)
// ServisniDeloviRepo je SQLite implementacija ServisniDeloviRepository interfejsa
type ServisniDeloviRepo struct {
db *sql.DB
}
// NoviServisniDeloviRepo kreira novi ServisniDeloviRepo
func NoviServisniDeloviRepo(db *sql.DB) *ServisniDeloviRepo {
return &ServisniDeloviRepo{db: db}
}
// DohvatiZaNalog vraća sve ugrađene delove za dati servisni nalog
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,
a.naziv
FROM servisni_delovi sd
JOIN artikli a ON a.id = sd.artikal_id
WHERE sd.nalog_id = ?
ORDER BY sd.datum`, nalogID)
if err != nil {
return nil, fmt.Errorf("ntech: ServisniDeloviRepo.DohvatiZaNalog: %w", err)
}
defer redovi.Close()
var rezultat []model.ServisniDeoSaArtiklom
for redovi.Next() {
var d model.ServisniDeoSaArtiklom
err := redovi.Scan(
&d.ID, &d.NalogID, &d.ArtikalID, &d.Kolicina, &d.CenaKomada, &d.Datum,
&d.ArtikalNaziv,
)
if err != nil {
return nil, fmt.Errorf("ntech: ServisniDeloviRepo.DohvatiZaNalog: scan: %w", err)
}
rezultat = append(rezultat, d)
}
return rezultat, nil
}
// Dodaj dodaje jedan artikal u servisni nalog, smanjuje stanje u magacinu i beleži promenu
func (r *ServisniDeloviRepo) Dodaj(ctx context.Context, nalogID, artikalID int64, kolicina int, cenaKomada float64, korisnikID *int64) (int64, error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return 0, fmt.Errorf("ntech: ServisniDeloviRepo.Dodaj: begin tx: %w", err)
}
defer tx.Rollback()
var naziv string
var stanjePre int
err = tx.QueryRowContext(ctx,
"SELECT naziv, kolicina FROM artikli WHERE id = ?", artikalID,
).Scan(&naziv, &stanjePre)
if err != nil {
return 0, fmt.Errorf("ntech: ServisniDeloviRepo.Dodaj: dohvati artikal: %w", err)
}
// dozvoljavamo backorder: deo se može ugraditi i kad nema dovoljno na stanju
// (lager tada ide u minus kao signal da treba nabavka); handler nalog prebacuje
// u „Čeka delove". Zato ovde NE odbijamo prekoračenje.
stanjePosle := stanjePre - kolicina
_, err = tx.ExecContext(ctx,
"UPDATE artikli SET kolicina = ? WHERE id = ?", stanjePosle, artikalID,
)
if err != nil {
return 0, fmt.Errorf("ntech: ServisniDeloviRepo.Dodaj: update stanje: %w", err)
}
// ako isti artikal već postoji na nalogu — saberi količinu i osveži cenu;
// u suprotnom kreiraj novi red (bez dupliranja istog artikla)
var deoID int64
var postojeciID int64
var postojeciKol int
err = tx.QueryRowContext(ctx,
"SELECT id, kolicina FROM servisni_delovi WHERE nalog_id = ? AND artikal_id = ?",
nalogID, artikalID,
).Scan(&postojeciID, &postojeciKol)
if err == nil {
// već postoji — saberi
novaKol := postojeciKol + kolicina
_, err = tx.ExecContext(ctx,
"UPDATE servisni_delovi SET kolicina = ?, cena_komada = ? WHERE id = ?",
novaKol, cenaKomada, postojeciID,
)
if err != nil {
return 0, fmt.Errorf("ntech: ServisniDeloviRepo.Dodaj: merge update: %w", err)
}
deoID = postojeciID
} else if errors.Is(err, sql.ErrNoRows) {
// novi artikal na nalogu — insert
rezultat, err := tx.ExecContext(ctx, `
INSERT INTO servisni_delovi (nalog_id, artikal_id, kolicina, cena_komada)
VALUES (?, ?, ?, ?)`,
nalogID, artikalID, kolicina, cenaKomada,
)
if err != nil {
return 0, fmt.Errorf("ntech: ServisniDeloviRepo.Dodaj: insert: %w", err)
}
deoID, err = rezultat.LastInsertId()
if err != nil {
return 0, fmt.Errorf("ntech: ServisniDeloviRepo.Dodaj: last insert id: %w", err)
}
} else {
return 0, fmt.Errorf("ntech: ServisniDeloviRepo.Dodaj: proveri postojanje: %w", err)
}
err = zabeleziMagacinPromenu(ctx, tx, artikalID, model.PromenaIzlazServis,
-kolicina, stanjePre, stanjePosle, nalogID, korisnikID, "")
if err != nil {
return 0, fmt.Errorf("ntech: ServisniDeloviRepo.Dodaj: magacin: %w", err)
}
if err := tx.Commit(); err != nil {
return 0, fmt.Errorf("ntech: ServisniDeloviRepo.Dodaj: commit: %w", err)
}
return deoID, nil
}
// Obrisi uklanja servisni deo i vraća količinu na stanje u magacinu
func (r *ServisniDeloviRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("ntech: ServisniDeloviRepo.Obrisi: begin tx: %w", err)
}
defer tx.Rollback()
var artikalID int64
var nalogID int64
var kolicina int
err = tx.QueryRowContext(ctx,
"SELECT artikal_id, nalog_id, kolicina FROM servisni_delovi WHERE id = ?", id,
).Scan(&artikalID, &nalogID, &kolicina)
if err != nil {
return fmt.Errorf("ntech: ServisniDeloviRepo.Obrisi: dohvati deo: %w", err)
}
var stanjePre int
err = tx.QueryRowContext(ctx,
"SELECT kolicina FROM artikli WHERE id = ?", artikalID,
).Scan(&stanjePre)
if err != nil {
return fmt.Errorf("ntech: ServisniDeloviRepo.Obrisi: dohvati stanje: %w", err)
}
stanjePosle := stanjePre + kolicina
_, err = tx.ExecContext(ctx,
"UPDATE artikli SET kolicina = ? WHERE id = ?", stanjePosle, artikalID,
)
if err != nil {
return fmt.Errorf("ntech: ServisniDeloviRepo.Obrisi: vrati stanje: %w", err)
}
_, err = tx.ExecContext(ctx, "DELETE FROM servisni_delovi WHERE id = ?", id)
if err != nil {
return fmt.Errorf("ntech: ServisniDeloviRepo.Obrisi: delete: %w", err)
}
err = zabeleziMagacinPromenu(ctx, tx, artikalID, model.PromenaPovracaj,
kolicina, stanjePre, stanjePosle, nalogID, korisnikID, "uklonjen servisni deo")
if err != nil {
return fmt.Errorf("ntech: ServisniDeloviRepo.Obrisi: magacin: %w", err)
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("ntech: ServisniDeloviRepo.Obrisi: 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
err := r.db.QueryRowContext(ctx,
"SELECT artikal_id FROM servisni_delovi WHERE id = ?", deoID,
).Scan(&artikalID)
if err != nil {
return 0, fmt.Errorf("ntech: ServisniDeloviRepo.DohvatiArtikalID: %w", err)
}
return artikalID, nil
}