Srednji prioritet iz pregleda koda: konzistentnost, refaktor, sitne popravke
- konstantno-vremensko poređenje CSRF tokena i kartica-PIN-a
- CSRF grupa proširena na /prijava, /prijava/totp, /setup
- router-level dozvola na svim /admin/podesavanja/* i modul("fiskalizacija")
na fiskal.pazar rutama
- KlijentRepo.Pronadji razlikuje sql.ErrNoRows od stvarnih DB grešaka
- izdvojen zajednički vratiStavkeNaStanje umesto 4 duplirana bloka
(ProdajaRepo/ServisRepo Storno/Obrisi)
- datumi u KIR/KPR sada se upisuju/porede kao "2006-01-02", usklađeno sa KPO
- ArtikalRepo.DohvatiVise (batch dohvat) — ukida N+1 upite u SacuvajNabavku
- KategorijaRepo.Obrisi umesto direktnog SQL-a u handleru, sa
ErrKategorijaUUpotrebi porukom
- KolicinMin -> KolicinaMin (tipfeler), Trosak/Usluga.DatumUnosa string -> time.Time
- dedup OznakaPoreskogBroja (KIR/KPR)
- inicijali() template funkcija (rune, ne bajtovi) za avatar inicijale
This commit is contained in:
@@ -92,7 +92,7 @@ func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]mod
|
||||
|
||||
err := redovi.Scan(
|
||||
&a.ID, &kategorijaID, &sifra, &barkod, &a.Naziv, &a.Opis, &a.Tip, &a.JedinicaMere,
|
||||
&a.Kolicina, &a.KolicinMin, &a.Lokacija,
|
||||
&a.Kolicina, &a.KolicinaMin, &a.Lokacija,
|
||||
&a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &a.CenaSaPdv, &marza, &a.Napomena, &a.DatumUnosa, &arhiviran,
|
||||
&a.KategorijaNaziv, &katMarza,
|
||||
)
|
||||
@@ -118,7 +118,7 @@ func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]mod
|
||||
}
|
||||
|
||||
// kritična zaliha važi samo za proizvode (usluge/troškovi nemaju lager)
|
||||
a.KriticnaZaliha = a.PratiLager() && a.Kolicina <= a.KolicinMin
|
||||
a.KriticnaZaliha = a.PratiLager() && a.Kolicina <= a.KolicinaMin
|
||||
|
||||
rezultat = append(rezultat, a)
|
||||
}
|
||||
@@ -126,24 +126,23 @@ func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]mod
|
||||
return rezultat, nil
|
||||
}
|
||||
|
||||
// DohvatiID vraća jedan artikal po ID-u
|
||||
func (r *ArtikalRepo) DohvatiID(ctx context.Context, id int64) (*model.Artikal, error) {
|
||||
const artikalKolone = `id, kategorija_id, sifra, barkod, naziv, opis, tip, jedinica_mere, kolicina, kolicina_min,
|
||||
lokacija, nabavna_cena, prodajna_cena, pdv_stopa, cena_sa_pdv, marza, napomena, datum_unosa, arhiviran`
|
||||
|
||||
// skenirajArtikal čita jedan red artikla iz Scan-kompatibilnog izvora (red ili redovi)
|
||||
func skenirajArtikal(scan func(...any) error) (*model.Artikal, error) {
|
||||
var a model.Artikal
|
||||
var kategorijaID sql.NullInt64
|
||||
var sifra, barkod sql.NullString
|
||||
var marza sql.NullFloat64
|
||||
var arhiviran int
|
||||
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT id, kategorija_id, sifra, barkod, naziv, opis, tip, jedinica_mere, kolicina, kolicina_min,
|
||||
lokacija, nabavna_cena, prodajna_cena, pdv_stopa, cena_sa_pdv, marza, napomena, datum_unosa, arhiviran
|
||||
FROM artikli WHERE id = ?`, id).Scan(
|
||||
if err := scan(
|
||||
&a.ID, &kategorijaID, &sifra, &barkod, &a.Naziv, &a.Opis, &a.Tip, &a.JedinicaMere,
|
||||
&a.Kolicina, &a.KolicinMin, &a.Lokacija,
|
||||
&a.Kolicina, &a.KolicinaMin, &a.Lokacija,
|
||||
&a.NabavnaCena, &a.ProdajnaCena, &a.PdvStopa, &a.CenaSaPdv, &marza, &a.Napomena, &a.DatumUnosa, &arhiviran,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: ArtikalRepo.DohvatiID: %w", err)
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.Arhiviran = arhiviran == 1
|
||||
|
||||
@@ -163,6 +162,52 @@ func (r *ArtikalRepo) DohvatiID(ctx context.Context, id int64) (*model.Artikal,
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
// DohvatiID vraća jedan artikal po ID-u
|
||||
func (r *ArtikalRepo) DohvatiID(ctx context.Context, id int64) (*model.Artikal, error) {
|
||||
row := r.db.QueryRowContext(ctx, "SELECT "+artikalKolone+" FROM artikli WHERE id = ?", id)
|
||||
a, err := skenirajArtikal(row.Scan)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: ArtikalRepo.DohvatiID: %w", err)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// DohvatiVise vraća artikle za dati skup ID-jeva kao mapu id → artikal — jedan upit
|
||||
// umesto poziva DohvatiID u petlji (koristi se npr. pri obradi stavki nabavke).
|
||||
func (r *ArtikalRepo) DohvatiVise(ctx context.Context, ids []int64) (map[int64]*model.Artikal, error) {
|
||||
rezultat := make(map[int64]*model.Artikal, len(ids))
|
||||
if len(ids) == 0 {
|
||||
return rezultat, nil
|
||||
}
|
||||
|
||||
drzaci := make([]string, len(ids))
|
||||
args := make([]any, len(ids))
|
||||
for i, id := range ids {
|
||||
drzaci[i] = "?"
|
||||
args[i] = id
|
||||
}
|
||||
|
||||
redovi, err := r.db.QueryContext(ctx,
|
||||
"SELECT "+artikalKolone+" FROM artikli WHERE id IN ("+strings.Join(drzaci, ",")+")", args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: ArtikalRepo.DohvatiVise: %w", err)
|
||||
}
|
||||
defer redovi.Close()
|
||||
|
||||
for redovi.Next() {
|
||||
a, err := skenirajArtikal(redovi.Scan)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: ArtikalRepo.DohvatiVise: scan: %w", err)
|
||||
}
|
||||
rezultat[a.ID] = a
|
||||
}
|
||||
if err := redovi.Err(); err != nil {
|
||||
return nil, fmt.Errorf("ntech: ArtikalRepo.DohvatiVise: %w", err)
|
||||
}
|
||||
|
||||
return rezultat, nil
|
||||
}
|
||||
|
||||
// Kreiraj dodaje novi artikal u bazu
|
||||
func (r *ArtikalRepo) Kreiraj(ctx context.Context, a *model.Artikal) (int64, error) {
|
||||
var sifra, barkod any
|
||||
@@ -178,7 +223,7 @@ func (r *ArtikalRepo) Kreiraj(ctx context.Context, a *model.Artikal) (int64, err
|
||||
(kategorija_id, sifra, barkod, naziv, opis, tip, jedinica_mere, kolicina, kolicina_min, lokacija,
|
||||
nabavna_cena, prodajna_cena, pdv_stopa, cena_sa_pdv, marza, napomena)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
a.KategorijaID, sifra, barkod, a.Naziv, a.Opis, a.Tip, a.JedinicaMere, a.Kolicina, a.KolicinMin,
|
||||
a.KategorijaID, sifra, barkod, a.Naziv, a.Opis, a.Tip, a.JedinicaMere, a.Kolicina, a.KolicinaMin,
|
||||
a.Lokacija, a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.CenaSaPdv, a.Marza, a.Napomena,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -210,7 +255,7 @@ func (r *ArtikalRepo) Izmeni(ctx context.Context, a *model.Artikal) error {
|
||||
nabavna_cena = ?, prodajna_cena = ?, pdv_stopa = ?, cena_sa_pdv = ?, marza = ?, napomena = ?
|
||||
WHERE id = ?`,
|
||||
a.KategorijaID, sifra, barkod, a.Naziv, a.Opis, a.Tip, a.JedinicaMere,
|
||||
a.Kolicina, a.KolicinMin, a.Lokacija,
|
||||
a.Kolicina, a.KolicinaMin, a.Lokacija,
|
||||
a.NabavnaCena, a.ProdajnaCena, a.PdvStopa, a.CenaSaPdv, a.Marza, a.Napomena, a.ID,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -333,7 +333,7 @@ func (r *sqliteIzvestajRepo) StanjeZaliha(ctx context.Context) ([]model.StanjeZa
|
||||
for rows.Next() {
|
||||
var s model.StanjeZalihaRed
|
||||
if err := rows.Scan(&s.Naziv, &s.Sifra, &s.Kategorija,
|
||||
&s.Kolicina, &s.KolicinMin, &s.NabavnaCena, &s.ProdajnaCena,
|
||||
&s.Kolicina, &s.KolicinaMin, &s.NabavnaCena, &s.ProdajnaCena,
|
||||
&s.CenaSaPdv, &s.VrednostZalihe, &s.VrednostSaPdv); err != nil {
|
||||
return nil, fmt.Errorf("ntech: IzvestajRepo.StanjeZaliha: scan: %w", err)
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ func TestIzvestajArtikliBrojaci(t *testing.T) {
|
||||
t.Fatalf("Kreiraj: %v", err)
|
||||
}
|
||||
}
|
||||
dodaj(&model.Artikal{Naziv: "A", Kolicina: 10, KolicinMin: 5})
|
||||
dodaj(&model.Artikal{Naziv: "B", Kolicina: 2, KolicinMin: 5})
|
||||
dodaj(&model.Artikal{Naziv: "C", Kolicina: 0, KolicinMin: 5})
|
||||
dodaj(&model.Artikal{Naziv: "A", Kolicina: 10, KolicinaMin: 5})
|
||||
dodaj(&model.Artikal{Naziv: "B", Kolicina: 2, KolicinaMin: 5})
|
||||
dodaj(&model.Artikal{Naziv: "C", Kolicina: 0, KolicinaMin: 5})
|
||||
|
||||
if n, err := izv.BrojArtikala(ctx); err != nil || n != 3 {
|
||||
t.Fatalf("BrojArtikala = %d, err=%v; očekivano 3", n, err)
|
||||
|
||||
@@ -3,9 +3,13 @@ package sqlite
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"ntech/internal/db"
|
||||
"ntech/internal/model"
|
||||
|
||||
mosqlite "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// KategorijaRepo je SQLite implementacija KategorijaRepository interfejsa
|
||||
@@ -109,3 +113,17 @@ func (r *KategorijaRepo) Izmeni(ctx context.Context, k *model.Kategorija) error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obrisi briše kategoriju. Ako je referencirana od artikla (FK ograničenje),
|
||||
// vraća db.ErrKategorijaUUpotrebi da pozivalac može da prikaže razumljivu poruku.
|
||||
func (r *KategorijaRepo) Obrisi(ctx context.Context, id int64) error {
|
||||
_, err := r.db.ExecContext(ctx, "DELETE FROM kategorije WHERE id = ?", id)
|
||||
if err != nil {
|
||||
var sqliteErr *mosqlite.Error
|
||||
if errors.As(err, &sqliteErr) && sqliteErr.Code() == 787 {
|
||||
return db.ErrKategorijaUUpotrebi
|
||||
}
|
||||
return fmt.Errorf("ntech: KategorijaRepo.Obrisi: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -122,6 +122,8 @@ func (r *KlijentRepo) Pronadji(ctx context.Context, tip, ime, prezime, nazivFirm
|
||||
FROM klijenti WHERE tip = 'pravno' AND pib = ? LIMIT 1`, jmbg)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
} else if !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.Pronadji: %w", err)
|
||||
}
|
||||
}
|
||||
// 2. Naziv firme + mesto
|
||||
@@ -131,6 +133,8 @@ func (r *KlijentRepo) Pronadji(ctx context.Context, tip, ime, prezime, nazivFirm
|
||||
FROM klijenti WHERE tip = 'pravno' AND naziv_firme = ? AND mesto = ? LIMIT 1`, nazivFirme, mesto)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
} else if !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.Pronadji: %w", err)
|
||||
}
|
||||
}
|
||||
// 3. Samo naziv firme
|
||||
@@ -152,6 +156,8 @@ func (r *KlijentRepo) Pronadji(ctx context.Context, tip, ime, prezime, nazivFirm
|
||||
FROM klijenti WHERE tip != 'pravno' AND jmbg = ? LIMIT 1`, jmbg)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
} else if !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.Pronadji: %w", err)
|
||||
}
|
||||
}
|
||||
// 2. Ime + prezime + mesto
|
||||
@@ -161,6 +167,8 @@ func (r *KlijentRepo) Pronadji(ctx context.Context, tip, ime, prezime, nazivFirm
|
||||
FROM klijenti WHERE tip != 'pravno' AND ime = ? AND prezime = ? AND mesto = ? LIMIT 1`, ime, prezime, mesto)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
} else if !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.Pronadji: %w", err)
|
||||
}
|
||||
}
|
||||
// 3. Ime + prezime + telefon
|
||||
@@ -170,6 +178,8 @@ func (r *KlijentRepo) Pronadji(ctx context.Context, tip, ime, prezime, nazivFirm
|
||||
FROM klijenti WHERE tip != 'pravno' AND ime = ? AND prezime = ? AND telefon = ? LIMIT 1`, ime, prezime, telefon)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
} else if !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.Pronadji: %w", err)
|
||||
}
|
||||
}
|
||||
// 4. Ime + prezime + email
|
||||
@@ -179,6 +189,8 @@ func (r *KlijentRepo) Pronadji(ctx context.Context, tip, ime, prezime, nazivFirm
|
||||
FROM klijenti WHERE tip != 'pravno' AND ime = ? AND prezime = ? AND email = ? LIMIT 1`, ime, prezime, email)
|
||||
if err := skeniraj(row); err == nil {
|
||||
goto popuni
|
||||
} else if !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("ntech: KlijentRepo.Pronadji: %w", err)
|
||||
}
|
||||
}
|
||||
// 5. Samo ime + prezime (poslednji fallback)
|
||||
|
||||
@@ -100,3 +100,63 @@ func zabeleziMagacinPromenu(
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// vratiStavkeNaStanje čita (artikal_id, kolicina) iz izvorne tabele stavki
|
||||
// (stavke_prodaje ili servisni_delovi — uvek fiksan literal iz poziva, nikad
|
||||
// korisnički unos) za dati nalog, vraća proizvode na stanje i beleži magacinsku
|
||||
// promenu za svaki. Usluge i troškovi (koji ne prate stanje) se preskaču.
|
||||
// Zajednički je za Storno/Obrisi u ProdajaRepo i ServisRepo — sve unutar
|
||||
// postojeće transakcije pozivaoca.
|
||||
func vratiStavkeNaStanje(ctx context.Context, tx *sql.Tx, tabelaStavki string, nalogID int64, korisnikID *int64, napomena string) error {
|
||||
redovi, err := tx.QueryContext(ctx,
|
||||
"SELECT artikal_id, kolicina FROM "+tabelaStavki+" WHERE nalog_id = ?", nalogID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dohvati stavke: %w", err)
|
||||
}
|
||||
|
||||
type stavkaPovrat struct {
|
||||
artikalID int64
|
||||
kolicina int
|
||||
}
|
||||
var stavke []stavkaPovrat
|
||||
for redovi.Next() {
|
||||
var p stavkaPovrat
|
||||
if err := redovi.Scan(&p.artikalID, &p.kolicina); err != nil {
|
||||
redovi.Close()
|
||||
return fmt.Errorf("scan stavke: %w", err)
|
||||
}
|
||||
stavke = append(stavke, p)
|
||||
}
|
||||
redovi.Close()
|
||||
if err := redovi.Err(); err != nil {
|
||||
return fmt.Errorf("iteracija stavki: %w", err)
|
||||
}
|
||||
|
||||
for _, p := range stavke {
|
||||
var stanjePre int
|
||||
var tip string
|
||||
if err := tx.QueryRowContext(ctx,
|
||||
"SELECT kolicina, tip FROM artikli WHERE id = ?", p.artikalID,
|
||||
).Scan(&stanjePre, &tip); err != nil {
|
||||
return fmt.Errorf("dohvati stanje: %w", err)
|
||||
}
|
||||
|
||||
// usluge i troškovi nemaju stanje na lageru — preskačemo povraćaj
|
||||
if !(tip == model.TipProizvod || tip == "") {
|
||||
continue
|
||||
}
|
||||
|
||||
stanjePosle := stanjePre + p.kolicina
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
"UPDATE artikli SET kolicina = ? WHERE id = ?", stanjePosle, p.artikalID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("vrati stanje: %w", err)
|
||||
}
|
||||
|
||||
if err := zabeleziMagacinPromenu(ctx, tx, p.artikalID, model.PromenaPovracaj,
|
||||
p.kolicina, stanjePre, stanjePosle, nalogID, korisnikID, napomena); err != nil {
|
||||
return fmt.Errorf("magacin: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ func (r *PdvKirRepo) Lista(ctx context.Context, od, do time.Time) ([]model.PdvKi
|
||||
args := []any{}
|
||||
if !od.IsZero() {
|
||||
upit += " AND datum_prometa >= ?"
|
||||
args = append(args, od)
|
||||
args = append(args, od.Format("2006-01-02"))
|
||||
}
|
||||
if !do.IsZero() {
|
||||
upit += " AND datum_prometa <= ?"
|
||||
args = append(args, do)
|
||||
args = append(args, do.Format("2006-01-02"))
|
||||
}
|
||||
upit += " ORDER BY datum_prometa ASC, id ASC"
|
||||
|
||||
@@ -105,7 +105,7 @@ func (r *PdvKirRepo) Kreiraj(ctx context.Context, k *model.PdvKir) (int64, error
|
||||
osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna,
|
||||
osloboden_sa_pravom, osloboden_bez_prava, ukupno, napomena, izvor, izvor_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
k.DatumPrometa, k.DatumKnjizenja, k.BrojDokumenta,
|
||||
k.DatumPrometa.Format("2006-01-02"), k.DatumKnjizenja.Format("2006-01-02"), k.BrojDokumenta,
|
||||
k.KupacNaziv, k.KupacPib, k.KupacMesto,
|
||||
k.OsnovicaOpsta, k.PdvOpsta, k.OsnovicaPosebna, k.PdvPosebna,
|
||||
k.OslobodenSaPravom, k.OslobodenBezPrava, k.Ukupno, k.Napomena,
|
||||
@@ -229,11 +229,11 @@ func (r *PdvKprRepo) Lista(ctx context.Context, od, do time.Time) ([]model.PdvKp
|
||||
args := []any{}
|
||||
if !od.IsZero() {
|
||||
upit += " AND datum_prometa >= ?"
|
||||
args = append(args, od)
|
||||
args = append(args, od.Format("2006-01-02"))
|
||||
}
|
||||
if !do.IsZero() {
|
||||
upit += " AND datum_prometa <= ?"
|
||||
args = append(args, do)
|
||||
args = append(args, do.Format("2006-01-02"))
|
||||
}
|
||||
upit += " ORDER BY datum_prometa ASC, id ASC"
|
||||
|
||||
@@ -302,7 +302,7 @@ func skenirajKpr(scan func(...any) error) (model.PdvKpr, error) {
|
||||
func (r *PdvKprRepo) Kreiraj(ctx context.Context, k *model.PdvKpr) (int64, error) {
|
||||
var datumPlacanja any
|
||||
if k.DatumPlacanja != nil {
|
||||
datumPlacanja = *k.DatumPlacanja
|
||||
datumPlacanja = k.DatumPlacanja.Format("2006-01-02")
|
||||
}
|
||||
uvoz := 0
|
||||
if k.Uvoz {
|
||||
@@ -315,7 +315,7 @@ func (r *PdvKprRepo) Kreiraj(ctx context.Context, k *model.PdvKpr) (int64, error
|
||||
osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna,
|
||||
pdv_bez_odbitka, osloboden_nabavka, ukupno, napomena, izvor, izvor_id, uvoz
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
k.DatumPrometa, k.DatumKnjizenja, datumPlacanja, k.BrojDokumenta,
|
||||
k.DatumPrometa.Format("2006-01-02"), k.DatumKnjizenja.Format("2006-01-02"), datumPlacanja, k.BrojDokumenta,
|
||||
k.DobavljacNaziv, k.DobavljacPib, k.DobavljacMesto,
|
||||
k.OsnovicaOpsta, k.PdvOpsta, k.OsnovicaPosebna, k.PdvPosebna,
|
||||
k.PdvBezOdbitka, k.OslobodenNabavka, k.Ukupno, k.Napomena,
|
||||
|
||||
@@ -294,53 +294,8 @@ func (r *ProdajaRepo) Storno(ctx context.Context, id int64, razlog string, koris
|
||||
}
|
||||
|
||||
// vraćanje stanja u magacin
|
||||
redovi, err := tx.QueryContext(ctx,
|
||||
"SELECT artikal_id, kolicina FROM stavke_prodaje WHERE nalog_id = ?", id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Storno: dohvati stavke: %w", err)
|
||||
}
|
||||
|
||||
type stavkaPovrat struct {
|
||||
artikalID int64
|
||||
kolicina int
|
||||
}
|
||||
var stavke []stavkaPovrat
|
||||
for redovi.Next() {
|
||||
var p stavkaPovrat
|
||||
if err := redovi.Scan(&p.artikalID, &p.kolicina); err != nil {
|
||||
redovi.Close()
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Storno: scan stavke: %w", err)
|
||||
}
|
||||
stavke = append(stavke, p)
|
||||
}
|
||||
redovi.Close()
|
||||
|
||||
for _, p := range stavke {
|
||||
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: ProdajaRepo.Storno: dohvati stanje: %w", err)
|
||||
}
|
||||
|
||||
// usluge i troškovi nemaju stanje na lageru — preskačemo povraćaj
|
||||
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: ProdajaRepo.Storno: vrati stanje: %w", err)
|
||||
}
|
||||
|
||||
err = zabeleziMagacinPromenu(ctx, tx, p.artikalID, model.PromenaPovracaj,
|
||||
p.kolicina, stanjePre, stanjePosle, id, korisnikID, razlog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Storno: magacin: %w", err)
|
||||
}
|
||||
if err := vratiStavkeNaStanje(ctx, tx, "stavke_prodaje", id, korisnikID, razlog); err != nil {
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Storno: %w", err)
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx,
|
||||
@@ -374,54 +329,8 @@ func (r *ProdajaRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) e
|
||||
}
|
||||
|
||||
if !stornirano {
|
||||
redovi, err := tx.QueryContext(ctx,
|
||||
"SELECT artikal_id, kolicina FROM stavke_prodaje WHERE nalog_id = ?", id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Obrisi: dohvati stavke: %w", err)
|
||||
}
|
||||
|
||||
type povrat struct {
|
||||
artikalID int64
|
||||
kolicina int
|
||||
}
|
||||
var stavke []povrat
|
||||
for redovi.Next() {
|
||||
var p povrat
|
||||
if err := redovi.Scan(&p.artikalID, &p.kolicina); err != nil {
|
||||
redovi.Close()
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Obrisi: scan stavke: %w", err)
|
||||
}
|
||||
stavke = append(stavke, p)
|
||||
}
|
||||
redovi.Close()
|
||||
|
||||
for _, p := range stavke {
|
||||
// usluge i troškovi nemaju stanje — 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: ProdajaRepo.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: ProdajaRepo.Obrisi: vrati stanje: %w", err)
|
||||
}
|
||||
|
||||
err = zabeleziMagacinPromenu(ctx, tx, p.artikalID, model.PromenaPovracaj,
|
||||
p.kolicina, stanjePre, stanjePosle, id, korisnikID, "brisanje prodajnog naloga")
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Obrisi: magacin: %w", err)
|
||||
}
|
||||
if err := vratiStavkeNaStanje(ctx, tx, "stavke_prodaje", id, korisnikID, "brisanje prodajnog naloga"); err != nil {
|
||||
return fmt.Errorf("ntech: ProdajaRepo.Obrisi: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -394,53 +394,8 @@ func (r *ServisRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) er
|
||||
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)
|
||||
}
|
||||
if err := vratiStavkeNaStanje(ctx, tx, "servisni_delovi", id, korisnikID, "brisanje servisnog naloga"); err != nil {
|
||||
return fmt.Errorf("ntech: ServisRepo.Obrisi: %w", err)
|
||||
}
|
||||
|
||||
// potraživani delovi nemaju ON DELETE CASCADE — ručno ih čistimo da brisanje ne padne na FK
|
||||
@@ -480,52 +435,8 @@ func (r *ServisRepo) Storno(ctx context.Context, id int64, razlog string, korisn
|
||||
return fmt.Errorf("ntech: ServisRepo.Storno: nalog je već storniran")
|
||||
}
|
||||
|
||||
redovi, err := tx.QueryContext(ctx,
|
||||
"SELECT artikal_id, kolicina FROM servisni_delovi WHERE nalog_id = ?", id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisRepo.Storno: 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.Storno: scan dela: %w", err)
|
||||
}
|
||||
delovi = append(delovi, p)
|
||||
}
|
||||
redovi.Close()
|
||||
|
||||
for _, p := range delovi {
|
||||
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.Storno: 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.Storno: vrati stanje: %w", err)
|
||||
}
|
||||
|
||||
err = zabeleziMagacinPromenu(ctx, tx, p.artikalID, model.PromenaPovracaj,
|
||||
p.kolicina, stanjePre, stanjePosle, id, korisnikID, "storno servisnog naloga")
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisRepo.Storno: magacin: %w", err)
|
||||
}
|
||||
if err := vratiStavkeNaStanje(ctx, tx, "servisni_delovi", id, korisnikID, "storno servisnog naloga"); err != nil {
|
||||
return fmt.Errorf("ntech: ServisRepo.Storno: %w", err)
|
||||
}
|
||||
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
|
||||
Reference in New Issue
Block a user