Zalihe: storno nabavke umesto brisanja + ponderisana prosečna nabavna cena
This commit is contained in:
+1
-1
@@ -350,7 +350,7 @@ func main() {
|
|||||||
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "nabavka.pregled")).Get("/nabavke/nova", h.NovaNabavka)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "nabavka.pregled")).Get("/nabavke/nova", h.NovaNabavka)
|
||||||
r.With(doz("nabavka.dodaj")).Post("/nabavke/nova", h.SacuvajNabavku)
|
r.With(doz("nabavka.dodaj")).Post("/nabavke/nova", h.SacuvajNabavku)
|
||||||
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "nabavka.pregled")).Get("/nabavke/{id}", h.DetaljiNabavke)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "nabavka.pregled")).Get("/nabavke/{id}", h.DetaljiNabavke)
|
||||||
r.With(doz("nabavka.obrisi")).Post("/nabavke/obrisi/{id}", h.ObrisiNabavku)
|
r.With(doz("nabavka.storno")).Post("/nabavke/storno/{id}", h.StornoNabavke)
|
||||||
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "dobavljac.pregled")).Get("/dobavljaci", h.Dobavljaci)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "dobavljac.pregled")).Get("/dobavljaci", h.Dobavljaci)
|
||||||
r.Get("/dobavljaci/novi", h.NoviDobavljac)
|
r.Get("/dobavljaci/novi", h.NoviDobavljac)
|
||||||
r.With(doz("dobavljac.dodaj")).Post("/dobavljaci/novi", h.SacuvajDobavljaca)
|
r.With(doz("dobavljac.dodaj")).Post("/dobavljaci/novi", h.SacuvajDobavljaca)
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ func NoviNabavkaRepo(db *sql.DB) *NabavkaRepo {
|
|||||||
func (r *NabavkaRepo) Lista(ctx context.Context) ([]model.NabavkaSaDetaljem, error) {
|
func (r *NabavkaRepo) Lista(ctx context.Context) ([]model.NabavkaSaDetaljem, error) {
|
||||||
redovi, err := r.db.QueryContext(ctx, `
|
redovi, err := r.db.QueryContext(ctx, `
|
||||||
SELECT
|
SELECT
|
||||||
n.id, n.dobavljac_id, n.napomena, n.ukupno, n.metod_raspodele, n.datum,
|
n.id, n.dobavljac_id, n.napomena, n.ukupno, n.metod_raspodele,
|
||||||
|
n.stornirano, n.razlog_storniranja, n.datum,
|
||||||
COALESCE(d.naziv, '') AS dobavljac_naziv
|
COALESCE(d.naziv, '') AS dobavljac_naziv
|
||||||
FROM nabavke n
|
FROM nabavke n
|
||||||
LEFT JOIN dobavljaci d ON n.dobavljac_id = d.id
|
LEFT JOIN dobavljaci d ON n.dobavljac_id = d.id
|
||||||
@@ -36,10 +37,11 @@ func (r *NabavkaRepo) Lista(ctx context.Context) ([]model.NabavkaSaDetaljem, err
|
|||||||
for redovi.Next() {
|
for redovi.Next() {
|
||||||
var n model.NabavkaSaDetaljem
|
var n model.NabavkaSaDetaljem
|
||||||
var dobavljacID sql.NullInt64
|
var dobavljacID sql.NullInt64
|
||||||
var napomena, metod sql.NullString
|
var napomena, metod, razlogStorniranja sql.NullString
|
||||||
|
|
||||||
err := redovi.Scan(
|
err := redovi.Scan(
|
||||||
&n.ID, &dobavljacID, &napomena, &n.Ukupno, &metod, &n.Datum,
|
&n.ID, &dobavljacID, &napomena, &n.Ukupno, &metod,
|
||||||
|
&n.Stornirano, &razlogStorniranja, &n.Datum,
|
||||||
&n.DobavljacNaziv,
|
&n.DobavljacNaziv,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -51,6 +53,7 @@ func (r *NabavkaRepo) Lista(ctx context.Context) ([]model.NabavkaSaDetaljem, err
|
|||||||
}
|
}
|
||||||
n.Napomena = napomena.String
|
n.Napomena = napomena.String
|
||||||
n.MetodRaspodele = metod.String
|
n.MetodRaspodele = metod.String
|
||||||
|
n.RazlogStorniranja = razlogStorniranja.String
|
||||||
|
|
||||||
rezultat = append(rezultat, n)
|
rezultat = append(rezultat, n)
|
||||||
}
|
}
|
||||||
@@ -62,12 +65,14 @@ func (r *NabavkaRepo) Lista(ctx context.Context) ([]model.NabavkaSaDetaljem, err
|
|||||||
func (r *NabavkaRepo) DohvatiID(ctx context.Context, id int64) (*model.Nabavka, error) {
|
func (r *NabavkaRepo) DohvatiID(ctx context.Context, id int64) (*model.Nabavka, error) {
|
||||||
var n model.Nabavka
|
var n model.Nabavka
|
||||||
var dobavljacID sql.NullInt64
|
var dobavljacID sql.NullInt64
|
||||||
var napomena, metod sql.NullString
|
var napomena, metod, razlogStorniranja sql.NullString
|
||||||
|
|
||||||
err := r.db.QueryRowContext(ctx, `
|
err := r.db.QueryRowContext(ctx, `
|
||||||
SELECT id, dobavljac_id, napomena, ukupno, metod_raspodele, datum
|
SELECT id, dobavljac_id, napomena, ukupno, metod_raspodele,
|
||||||
|
stornirano, razlog_storniranja, datum
|
||||||
FROM nabavke WHERE id = ?`, id).Scan(
|
FROM nabavke WHERE id = ?`, id).Scan(
|
||||||
&n.ID, &dobavljacID, &napomena, &n.Ukupno, &metod, &n.Datum,
|
&n.ID, &dobavljacID, &napomena, &n.Ukupno, &metod,
|
||||||
|
&n.Stornirano, &razlogStorniranja, &n.Datum,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ntech: NabavkaRepo.DohvatiID: %w", err)
|
return nil, fmt.Errorf("ntech: NabavkaRepo.DohvatiID: %w", err)
|
||||||
@@ -78,6 +83,7 @@ func (r *NabavkaRepo) DohvatiID(ctx context.Context, id int64) (*model.Nabavka,
|
|||||||
}
|
}
|
||||||
n.Napomena = napomena.String
|
n.Napomena = napomena.String
|
||||||
n.MetodRaspodele = metod.String
|
n.MetodRaspodele = metod.String
|
||||||
|
n.RazlogStorniranja = razlogStorniranja.String
|
||||||
|
|
||||||
return &n, nil
|
return &n, nil
|
||||||
}
|
}
|
||||||
@@ -194,17 +200,26 @@ func (r *NabavkaRepo) Kreiraj(ctx context.Context, n *model.Nabavka, stavke []mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
var stanjePre int
|
var stanjePre int
|
||||||
|
var staraNabavnaCena float64
|
||||||
err = tx.QueryRowContext(ctx,
|
err = tx.QueryRowContext(ctx,
|
||||||
"SELECT kolicina FROM artikli WHERE id = ?", s.ArtikalID,
|
"SELECT kolicina, nabavna_cena FROM artikli WHERE id = ?", s.ArtikalID,
|
||||||
).Scan(&stanjePre)
|
).Scan(&stanjePre, &staraNabavnaCena)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: dohvati stanje: %w", err)
|
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: dohvati stanje: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ponderisana prosečna nabavna cena (MRS 2)
|
||||||
stanjePosle := stanjePre + s.Kolicina
|
stanjePosle := stanjePre + s.Kolicina
|
||||||
|
var novaProsecna float64
|
||||||
|
if stanjePosle > 0 {
|
||||||
|
novaProsecna = (float64(stanjePre)*staraNabavnaCena + float64(s.Kolicina)*s.CenaPoKomadu) / float64(stanjePosle)
|
||||||
|
} else {
|
||||||
|
novaProsecna = s.CenaPoKomadu
|
||||||
|
}
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx,
|
_, err = tx.ExecContext(ctx,
|
||||||
"UPDATE artikli SET kolicina = ? WHERE id = ?",
|
"UPDATE artikli SET kolicina = ?, nabavna_cena = ? WHERE id = ?",
|
||||||
stanjePosle, s.ArtikalID,
|
stanjePosle, novaProsecna, s.ArtikalID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: update kolicina: %w", err)
|
return 0, fmt.Errorf("ntech: NabavkaRepo.Kreiraj: update kolicina: %w", err)
|
||||||
@@ -223,19 +238,31 @@ func (r *NabavkaRepo) Kreiraj(ctx context.Context, n *model.Nabavka, stavke []mo
|
|||||||
return nabavkaID, nil
|
return nabavkaID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obrisi briše nabavku po ID-u, vraća količine artikala na stanje i beleži korekciju u magacinski trag.
|
// Storno označava nabavku kao storniranu, vraća količine artikala na stanje i beleži
|
||||||
func (r *NabavkaRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) error {
|
// korekciju u magacinski trag. Nabavna cena se NE računa unazad — ostaje kakva jeste.
|
||||||
|
func (r *NabavkaRepo) Storno(ctx context.Context, id int64, razlog string, korisnikID *int64) error {
|
||||||
tx, err := r.db.BeginTx(ctx, nil)
|
tx, err := r.db.BeginTx(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: begin: %w", err)
|
return fmt.Errorf("ntech: NabavkaRepo.Storno: begin: %w", err)
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
// učitaj stavke pre brisanja (CASCADE ih briše zajedno sa nabavkom)
|
// provera da li je već stornirano
|
||||||
|
var stornirano bool
|
||||||
|
if err := tx.QueryRowContext(ctx,
|
||||||
|
"SELECT stornirano FROM nabavke WHERE id = ?", id,
|
||||||
|
).Scan(&stornirano); err != nil {
|
||||||
|
return fmt.Errorf("ntech: NabavkaRepo.Storno: provera: %w", err)
|
||||||
|
}
|
||||||
|
if stornirano {
|
||||||
|
return fmt.Errorf("ntech: NabavkaRepo.Storno: nabavka je već stornirana")
|
||||||
|
}
|
||||||
|
|
||||||
|
// učitaj stavke
|
||||||
redovi, err := tx.QueryContext(ctx,
|
redovi, err := tx.QueryContext(ctx,
|
||||||
"SELECT artikal_id, kolicina FROM stavke_nabavke WHERE nabavka_id = ?", id)
|
"SELECT artikal_id, kolicina FROM stavke_nabavke WHERE nabavka_id = ?", id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: dohvati stavke: %w", err)
|
return fmt.Errorf("ntech: NabavkaRepo.Storno: dohvati stavke: %w", err)
|
||||||
}
|
}
|
||||||
type stavka struct {
|
type stavka struct {
|
||||||
artikalID int64
|
artikalID int64
|
||||||
@@ -246,7 +273,7 @@ func (r *NabavkaRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) e
|
|||||||
var s stavka
|
var s stavka
|
||||||
if err := redovi.Scan(&s.artikalID, &s.kolicina); err != nil {
|
if err := redovi.Scan(&s.artikalID, &s.kolicina); err != nil {
|
||||||
redovi.Close()
|
redovi.Close()
|
||||||
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: scan stavka: %w", err)
|
return fmt.Errorf("ntech: NabavkaRepo.Storno: scan stavka: %w", err)
|
||||||
}
|
}
|
||||||
stavke = append(stavke, s)
|
stavke = append(stavke, s)
|
||||||
}
|
}
|
||||||
@@ -258,7 +285,7 @@ func (r *NabavkaRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) e
|
|||||||
if err := tx.QueryRowContext(ctx,
|
if err := tx.QueryRowContext(ctx,
|
||||||
"SELECT kolicina FROM artikli WHERE id = ?", s.artikalID,
|
"SELECT kolicina FROM artikli WHERE id = ?", s.artikalID,
|
||||||
).Scan(&stanjePre); err != nil {
|
).Scan(&stanjePre); err != nil {
|
||||||
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: dohvati stanje: %w", err)
|
return fmt.Errorf("ntech: NabavkaRepo.Storno: dohvati stanje: %w", err)
|
||||||
}
|
}
|
||||||
stanjePosle := stanjePre - s.kolicina
|
stanjePosle := stanjePre - s.kolicina
|
||||||
if stanjePosle < 0 {
|
if stanjePosle < 0 {
|
||||||
@@ -267,20 +294,23 @@ func (r *NabavkaRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) e
|
|||||||
if _, err := tx.ExecContext(ctx,
|
if _, err := tx.ExecContext(ctx,
|
||||||
"UPDATE artikli SET kolicina = ? WHERE id = ?", stanjePosle, s.artikalID,
|
"UPDATE artikli SET kolicina = ? WHERE id = ?", stanjePosle, s.artikalID,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: update stanje: %w", err)
|
return fmt.Errorf("ntech: NabavkaRepo.Storno: update stanje: %w", err)
|
||||||
}
|
}
|
||||||
if err := zabeleziMagacinPromenu(ctx, tx, s.artikalID, model.PromenaKorekcija,
|
if err := zabeleziMagacinPromenu(ctx, tx, s.artikalID, model.PromenaKorekcija,
|
||||||
-s.kolicina, stanjePre, stanjePosle, id, korisnikID, "brisanje nabavke"); err != nil {
|
-s.kolicina, stanjePre, stanjePosle, id, korisnikID, "storno nabavke: "+razlog); err != nil {
|
||||||
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: magacin: %w", err)
|
return fmt.Errorf("ntech: NabavkaRepo.Storno: magacin: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := tx.ExecContext(ctx, "DELETE FROM nabavke WHERE id = ?", id); err != nil {
|
if _, err := tx.ExecContext(ctx,
|
||||||
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: delete: %w", err)
|
"UPDATE nabavke SET stornirano = 1, razlog_storniranja = ? WHERE id = ?",
|
||||||
|
nullString(razlog), id,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("ntech: NabavkaRepo.Storno: update nabavka: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return fmt.Errorf("ntech: NabavkaRepo.Obrisi: commit: %w", err)
|
return fmt.Errorf("ntech: NabavkaRepo.Storno: commit: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-19
@@ -43,6 +43,7 @@ type PodaciDetaljiNabavke struct {
|
|||||||
Troskovi []model.NabavkaTrosak
|
Troskovi []model.NabavkaTrosak
|
||||||
UkupanTrosak float64
|
UkupanTrosak float64
|
||||||
DobavljacNaziv string
|
DobavljacNaziv string
|
||||||
|
Sacuvano bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// artikalUJSON pretvara listu artikala u template.JS vrednost bezbednu za umetanje u <script> tag
|
// artikalUJSON pretvara listu artikala u template.JS vrednost bezbednu za umetanje u <script> tag
|
||||||
@@ -208,20 +209,14 @@ func (h *Handler) SacuvajNabavku(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// kalkulacija: ažuriraj nabavnu i prodajnu cenu artikla iz forme + nivelacioni trag.
|
// kalkulacija: ažuriraj prodajnu cenu artikla iz forme + nivelacioni trag.
|
||||||
|
// nabavna cena je već ponderisana prosečna (postavlja je Kreiraj) — ne preglašava se.
|
||||||
// prodajna[] je paralelni niz uz stavke (isti redosled kao artikal_id[]).
|
// prodajna[] je paralelni niz uz stavke (isti redosled kao artikal_id[]).
|
||||||
prodajne := r.Form["prodajna[]"]
|
prodajne := r.Form["prodajna[]"]
|
||||||
var korisnikID *int64
|
var korisnikID *int64
|
||||||
if k != nil {
|
if k != nil {
|
||||||
korisnikID = &k.ID
|
korisnikID = &k.ID
|
||||||
}
|
}
|
||||||
// kalkulativna nabavna cena po stavci (fakturna + raspodeljeni zavisni trošak) —
|
|
||||||
// računa se na serveru; bez troškova je jednaka čistoj ceni po komadu.
|
|
||||||
var ukupanTrosak float64
|
|
||||||
for _, t := range troskovi {
|
|
||||||
ukupanTrosak += t.Iznos
|
|
||||||
}
|
|
||||||
kalkNabavna := model.RasporediTroskove(stavke, ukupanTrosak, nabavka.MetodRaspodele)
|
|
||||||
for i, s := range stavke {
|
for i, s := range stavke {
|
||||||
if i >= len(prodajne) {
|
if i >= len(prodajne) {
|
||||||
break
|
break
|
||||||
@@ -230,12 +225,13 @@ func (h *Handler) SacuvajNabavku(w http.ResponseWriter, r *http.Request) {
|
|||||||
if e != nil || prodajna <= 0 {
|
if e != nil || prodajna <= 0 {
|
||||||
continue // prazno/nula ne sme da pregazi postojeću cenu
|
continue // prazno/nula ne sme da pregazi postojeću cenu
|
||||||
}
|
}
|
||||||
// stara prodajna cena — za nivelacioni zapis
|
// stara prodajna i tekuća (ponderisana) nabavna — nabavnu zadržavamo
|
||||||
var staraProdajna float64
|
var staraProdajna, nabavna float64
|
||||||
if a, e := h.Artikli.DohvatiID(r.Context(), s.ArtikalID); e == nil {
|
if a, e := h.Artikli.DohvatiID(r.Context(), s.ArtikalID); e == nil {
|
||||||
staraProdajna = a.ProdajnaCena
|
staraProdajna = a.ProdajnaCena
|
||||||
|
nabavna = a.NabavnaCena
|
||||||
}
|
}
|
||||||
if e := h.Artikli.AzurirajCene(r.Context(), s.ArtikalID, kalkNabavna[i], prodajna); e != nil {
|
if e := h.Artikli.AzurirajCene(r.Context(), s.ArtikalID, nabavna, prodajna); e != nil {
|
||||||
slog.Error("kalkulacija: ažuriranje cena nije uspelo", "artikal_id", s.ArtikalID, "error", e)
|
slog.Error("kalkulacija: ažuriranje cena nije uspelo", "artikal_id", s.ArtikalID, "error", e)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -333,14 +329,15 @@ func (h *Handler) DetaljiNabavke(w http.ResponseWriter, r *http.Request) {
|
|||||||
Troskovi: troskovi,
|
Troskovi: troskovi,
|
||||||
UkupanTrosak: ukupanTrosak,
|
UkupanTrosak: ukupanTrosak,
|
||||||
DobavljacNaziv: dobavljacNaziv,
|
DobavljacNaziv: dobavljacNaziv,
|
||||||
|
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
||||||
}
|
}
|
||||||
|
|
||||||
h.renderujTemplate(w, "nabavka_detalji", podaci)
|
h.renderujTemplate(w, "nabavka_detalji", podaci)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObrisiNabavku prima POST zahtev i briše nabavku po ID-u
|
// StornoNabavke prima POST zahtev i stornira nabavku po ID-u (umesto fizičkog brisanja)
|
||||||
func (h *Handler) ObrisiNabavku(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) StornoNabavke(w http.ResponseWriter, r *http.Request) {
|
||||||
k, ok := h.zahtevajDozvolu(w, r, "nabavka.obrisi")
|
k, ok := h.zahtevajDozvolu(w, r, "nabavka.storno")
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -349,17 +346,23 @@ func (h *Handler) ObrisiNabavku(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "Neispravan ID nabavke", http.StatusBadRequest)
|
http.Error(w, "Neispravan ID nabavke", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
if err := h.NabavkeRepo.Obrisi(r.Context(), id, &k.ID); err != nil {
|
http.Error(w, "Greška pri čitanju forme", http.StatusBadRequest)
|
||||||
http.Error(w, "Greška pri brisanju nabavke", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// ukloni vezani auto-KPR zapis (ako ga je ova nabavka kreirala)
|
razlog := strings.TrimSpace(r.FormValue("razlog"))
|
||||||
|
|
||||||
|
if err := h.NabavkeRepo.Storno(r.Context(), id, razlog, &k.ID); err != nil {
|
||||||
|
slog.Error("greška pri storniranju nabavke", "error", err)
|
||||||
|
http.Redirect(w, r, "/nabavke/"+strconv.FormatInt(id, 10), http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// stornirana nabavka ne ulazi u PDV — ukloni vezani auto-KPR zapis
|
||||||
if err := h.PdvKprRepo.ObrisiPoIzvoru(r.Context(), "nabavka", id); err != nil {
|
if err := h.PdvKprRepo.ObrisiPoIzvoru(r.Context(), "nabavka", id); err != nil {
|
||||||
slog.Error("brisanje vezanog KPR zapisa nije uspelo", "nabavka_id", id, "error", err)
|
slog.Error("brisanje vezanog KPR zapisa nije uspelo", "nabavka_id", id, "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/nabavke?obrisan=1", http.StatusSeeOther)
|
http.Redirect(w, r, "/nabavke/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseFormuNabavke čita zaglavlje, stavke i zavisne troškove iz HTTP forme
|
// parseFormuNabavke čita zaglavlje, stavke i zavisne troškove iz HTTP forme
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ var sveAkcije = []string{
|
|||||||
"nabavka.pregled",
|
"nabavka.pregled",
|
||||||
"nabavka.dodaj",
|
"nabavka.dodaj",
|
||||||
"nabavka.obrisi",
|
"nabavka.obrisi",
|
||||||
|
"nabavka.storno",
|
||||||
"dobavljac.pregled",
|
"dobavljac.pregled",
|
||||||
"dobavljac.dodaj",
|
"dobavljac.dodaj",
|
||||||
"dobavljac.izmeni",
|
"dobavljac.izmeni",
|
||||||
@@ -66,7 +67,7 @@ func ImaDozvolu(uloga, akcija string) bool {
|
|||||||
case "kategorija.pregled", "kategorija.dodaj", "kategorija.izmeni", "kategorija.obrisi":
|
case "kategorija.pregled", "kategorija.dodaj", "kategorija.izmeni", "kategorija.obrisi":
|
||||||
return true
|
return true
|
||||||
// nabavka
|
// nabavka
|
||||||
case "nabavka.pregled", "nabavka.dodaj", "nabavka.obrisi":
|
case "nabavka.pregled", "nabavka.dodaj", "nabavka.obrisi", "nabavka.storno":
|
||||||
return true
|
return true
|
||||||
// dobavljač
|
// dobavljač
|
||||||
case "dobavljac.pregled", "dobavljac.dodaj",
|
case "dobavljac.pregled", "dobavljac.dodaj",
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import (
|
|||||||
|
|
||||||
// Nabavka predstavlja zaglavlje jedne nabavke
|
// Nabavka predstavlja zaglavlje jedne nabavke
|
||||||
type Nabavka struct {
|
type Nabavka struct {
|
||||||
ID int64
|
ID int64
|
||||||
DobavljacID *int64
|
DobavljacID *int64
|
||||||
Napomena string
|
Napomena string
|
||||||
Ukupno float64
|
Ukupno float64
|
||||||
MetodRaspodele string // "vrednost" ili "kolicina"; prazno = nema zavisnih troškova
|
MetodRaspodele string // "vrednost" ili "kolicina"; prazno = nema zavisnih troškova
|
||||||
Datum time.Time
|
Stornirano bool
|
||||||
|
RazlogStorniranja string
|
||||||
|
Datum time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// NabavkaTrosak je jedna stavka zavisnih troškova nabavke (npr. prevoz, carina)
|
// NabavkaTrosak je jedna stavka zavisnih troškova nabavke (npr. prevoz, carina)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- Nabavke — storniranje umesto fizičkog brisanja (računovodstveni tretman ulaznih dokumenata)
|
||||||
|
ALTER TABLE nabavke ADD COLUMN stornirano INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE nabavke ADD COLUMN razlog_storniranja TEXT;
|
||||||
@@ -14,6 +14,10 @@
|
|||||||
{{define "sadrzaj"}}
|
{{define "sadrzaj"}}
|
||||||
<div class="kolona" style="gap:16px;">
|
<div class="kolona" style="gap:16px;">
|
||||||
|
|
||||||
|
{{if .Sacuvano}}
|
||||||
|
<div class="poruka-uspeh poruka-animacija">Nabavka je uspešno sačuvana.</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<!-- nazad dugme -->
|
<!-- nazad dugme -->
|
||||||
<a href="/nabavke" class="nazad-link" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
|
<a href="/nabavke" class="nazad-link" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg>
|
||||||
@@ -23,7 +27,14 @@
|
|||||||
<!-- zaglavlje nabavke -->
|
<!-- zaglavlje nabavke -->
|
||||||
<div class="kartica detalji-kartica animiraj">
|
<div class="kartica detalji-kartica animiraj">
|
||||||
<div style="margin-bottom:16px;padding-bottom:14px;border-bottom:0.5px solid var(--ivica);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px;">
|
<div style="margin-bottom:16px;padding-bottom:14px;border-bottom:0.5px solid var(--ivica);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px;">
|
||||||
<span style="font-size:16px;font-weight:500;color:var(--tekst-glavni);">Detalji nabavke</span>
|
<span style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
||||||
|
<span style="font-size:16px;font-weight:500;color:var(--tekst-glavni);">Detalji nabavke</span>
|
||||||
|
{{if .Nabavka.Stornirano}}
|
||||||
|
<span style="display:inline-block;padding:3px 10px;border-radius:20px;font-size:12px;font-weight:500;background:rgba(220,38,38,0.12);color:#dc2626;">
|
||||||
|
Stornirano
|
||||||
|
</span>
|
||||||
|
{{end}}
|
||||||
|
</span>
|
||||||
<span class="pomocni-tekst">{{.Nabavka.Datum.Format "02.01.2006. u 15:04"}}</span>
|
<span class="pomocni-tekst">{{.Nabavka.Datum.Format "02.01.2006. u 15:04"}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -46,6 +57,12 @@
|
|||||||
{{dinari .Nabavka.Ukupno}} din
|
{{dinari .Nabavka.Ukupno}} din
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{if .Nabavka.RazlogStorniranja}}
|
||||||
|
<div style="grid-column:1/-1;">
|
||||||
|
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Razlog storniranja</div>
|
||||||
|
<div style="font-size:13px;color:#dc2626;">{{.Nabavka.RazlogStorniranja}}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -146,23 +163,27 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<!-- zona za brisanje -->
|
<!-- zona za storniranje -->
|
||||||
|
{{if and (index .Dozvole "nabavka.storno") (not .Nabavka.Stornirano)}}
|
||||||
<div class="kartica detalji-kartica animiraj" style="border-color:#dc262633;">
|
<div class="kartica detalji-kartica animiraj" style="border-color:#dc262633;">
|
||||||
<div style="display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;">
|
<div style="display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;">
|
||||||
<div style="flex:1;min-width:200px;">
|
<div style="flex:1;min-width:200px;">
|
||||||
<div style="font-size:14px;font-weight:500;color:#dc2626;margin-bottom:4px;">Brisanje nabavke</div>
|
<div style="font-size:14px;font-weight:500;color:#f97316;margin-bottom:4px;">Storniranje nabavke</div>
|
||||||
<div class="pomocni-tekst">
|
<div class="pomocni-tekst">
|
||||||
Brisanje je trajno i ne vraća količine artikala u magacin.
|
Storno vraća količine artikala na stanje u magacinu. Nabavka ostaje u evidenciji sa oznakom stornirana. Nabavna cena se ne menja.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form method="POST" action="/nabavke/obrisi/{{.Nabavka.ID}}">
|
<form method="POST" action="/nabavke/storno/{{.Nabavka.ID}}" style="display:flex;flex-direction:column;gap:8px;align-items:flex-end;">
|
||||||
<button type="submit" class="btn-opasno"
|
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
|
||||||
data-potvrda="Da li ste sigurni da želite da obrišete ovu nabavku? Brisanje ne vraća količine artikala u magacin.">
|
<input type="text" name="razlog" placeholder="Razlog storniranja (opciono)" style="width:240px;">
|
||||||
Obriši nabavku
|
<button type="submit" class="btn-upozorenje"
|
||||||
|
data-potvrda="Stornirati ovu nabavku? Količine artikala biće vraćene na stanje.">
|
||||||
|
Storniraj nabavku
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -37,7 +37,10 @@
|
|||||||
{{.Datum.Format "02.01.2006."}}
|
{{.Datum.Format "02.01.2006."}}
|
||||||
</td>
|
</td>
|
||||||
<td style="padding:12px 16px;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
<td style="padding:12px 16px;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||||
{{if .DobavljacNaziv}}{{.DobavljacNaziv}}{{else}}—{{end}}
|
<span style="display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||||
|
{{if .DobavljacNaziv}}{{.DobavljacNaziv}}{{else}}—{{end}}
|
||||||
|
{{if .Stornirano}}<span style="padding:2px 8px;border-radius:20px;font-size:11px;font-weight:500;background:rgba(220,38,38,0.12);color:#dc2626;">Stornirano</span>{{end}}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td style="padding:12px 16px;font-size:13px;color:var(--tekst-sporedni);">
|
<td style="padding:12px 16px;font-size:13px;color:var(--tekst-sporedni);">
|
||||||
{{if .Napomena}}{{.Napomena}}{{else}}—{{end}}
|
{{if .Napomena}}{{.Napomena}}{{else}}—{{end}}
|
||||||
@@ -50,14 +53,6 @@
|
|||||||
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/nabavke/{{.ID}}" class="btn-primarno-malo">
|
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/nabavke/{{.ID}}" class="btn-primarno-malo">
|
||||||
Detalji
|
Detalji
|
||||||
</a>
|
</a>
|
||||||
{{if index $.Dozvole "nabavka.obrisi"}}
|
|
||||||
<form method="POST" action="/nabavke/obrisi/{{.ID}}" style="display:inline;">
|
|
||||||
<button type="submit" class="btn-obrisi-malo"
|
|
||||||
data-potvrda="Da li ste sigurni? Brisanje nabavke ne vraća količine artikala u magacin.">
|
|
||||||
Obriši
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -79,8 +74,9 @@
|
|||||||
<div class="kartica nabavka-kartica animiraj">
|
<div class="kartica nabavka-kartica animiraj">
|
||||||
<div class="red-izmedju" style="align-items:flex-start;gap:12px;margin-bottom:10px;">
|
<div class="red-izmedju" style="align-items:flex-start;gap:12px;margin-bottom:10px;">
|
||||||
<div>
|
<div>
|
||||||
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">
|
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||||
{{if .DobavljacNaziv}}{{.DobavljacNaziv}}{{else}}Bez dobavljača{{end}}
|
{{if .DobavljacNaziv}}{{.DobavljacNaziv}}{{else}}Bez dobavljača{{end}}
|
||||||
|
{{if .Stornirano}}<span style="padding:2px 8px;border-radius:20px;font-size:11px;font-weight:500;background:rgba(220,38,38,0.12);color:#dc2626;">Stornirano</span>{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:2px;">
|
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:2px;">
|
||||||
{{.Datum.Format "02.01.2006."}}
|
{{.Datum.Format "02.01.2006."}}
|
||||||
@@ -97,14 +93,6 @@
|
|||||||
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/nabavke/{{.ID}}" class="btn-primarno-malo" style="flex:1;justify-content:center;">
|
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/nabavke/{{.ID}}" class="btn-primarno-malo" style="flex:1;justify-content:center;">
|
||||||
Detalji
|
Detalji
|
||||||
</a>
|
</a>
|
||||||
{{if index $.Dozvole "nabavka.obrisi"}}
|
|
||||||
<form method="POST" action="/nabavke/obrisi/{{.ID}}" style="flex:1;">
|
|
||||||
<button type="submit" class="btn-obrisi-malo" style="width:100%;justify-content:center;"
|
|
||||||
data-potvrda="Da li ste sigurni? Brisanje nabavke ne vraća količine artikala u magacin.">
|
|
||||||
Obriši
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|||||||
Reference in New Issue
Block a user