prodaja: popust, kategorija, cene po režimu, pretraga, zabrana brisanja
Migracija 092: popust_procenat na stavke_prodaje. Model: PopustProcenat u StavkaProdaje, KategorijaNaziv u StavkaProdajeSaArtiklom. Repo: pretraga prodaje proširena na klijenta i napomenu. DohvatiStavke dodaje popust i kategoriju. Kreiraj primenjuje popust na ukupno. Handler: artikalUJSONSaCenom proširen sa sifra, barkod, cena_sa_pdv, kategorija_naziv. parseFormuProdaje parsira popust. ObrisiProdaju sada koristi storno (zabrana fizičkog brisanja). JS: prodajaForma ažurirana — pretraga artikala po nazivu/šifri/barkodu, popust polje, kategorija, kalkulacija sa popustom. Forma: pretraga artikala, popust kolona, kategorija, PDV režim (cena/cena_sa_pdv). Responsive desktop+mobilne kartice. Detalji: kategorija ispod naziva, popust kolona, 'Ukloni nalog' (storno) umesto 'Obriši'.
This commit is contained in:
@@ -51,8 +51,8 @@ func (r *ProdajaRepo) Lista(ctx context.Context, pretraga string) ([]model.Proda
|
||||
args := []any{}
|
||||
|
||||
if pretraga != "" {
|
||||
upit += " AND pn.broj_naloga LIKE ?"
|
||||
args = append(args, "%"+pretraga+"%")
|
||||
upit += " AND (pn.broj_naloga LIKE ? OR kp.naziv LIKE ? OR pn.napomena LIKE ?)"
|
||||
args = append(args, "%"+pretraga+"%", "%"+pretraga+"%", "%"+pretraga+"%")
|
||||
}
|
||||
|
||||
upit += " ORDER BY pn.datum DESC"
|
||||
@@ -122,10 +122,13 @@ func (r *ProdajaRepo) DohvatiID(ctx context.Context, id int64) (*model.ProdajniN
|
||||
// DohvatiStavke vraća stavke prodaje sa nazivima artikala i PDV podacima za dati nalog
|
||||
func (r *ProdajaRepo) DohvatiStavke(ctx context.Context, nalogID int64) ([]model.StavkaProdajeSaArtiklom, error) {
|
||||
redovi, err := r.db.QueryContext(ctx, `
|
||||
SELECT sp.id, sp.nalog_id, sp.artikal_id, sp.kolicina, sp.cena_po_komadu, sp.ukupno,
|
||||
sp.pdv_stopa, sp.pdv_iznos, sp.cena_bez_pdv, a.naziv, a.jedinica_mere
|
||||
SELECT sp.id, sp.nalog_id, sp.artikal_id, sp.kolicina, sp.cena_po_komadu,
|
||||
sp.popust_procenat, sp.ukupno,
|
||||
sp.pdv_stopa, sp.pdv_iznos, sp.cena_bez_pdv,
|
||||
a.naziv, a.jedinica_mere, COALESCE(k.naziv, '') AS kategorija_naziv
|
||||
FROM stavke_prodaje sp
|
||||
JOIN artikli a ON a.id = sp.artikal_id
|
||||
LEFT JOIN kategorije k ON k.id = a.kategorija_id
|
||||
WHERE sp.nalog_id = ?
|
||||
ORDER BY sp.id`, nalogID)
|
||||
if err != nil {
|
||||
@@ -138,9 +141,9 @@ func (r *ProdajaRepo) DohvatiStavke(ctx context.Context, nalogID int64) ([]model
|
||||
var s model.StavkaProdajeSaArtiklom
|
||||
err := redovi.Scan(
|
||||
&s.ID, &s.NalogID, &s.ArtikalID, &s.Kolicina,
|
||||
&s.CenaPoKomadu, &s.Ukupno,
|
||||
&s.CenaPoKomadu, &s.PopustProcenat, &s.Ukupno,
|
||||
&s.PdvStopa, &s.PdvIznos, &s.CenaBezPdv,
|
||||
&s.ArtikalNaziv, &s.JedinicaMere,
|
||||
&s.ArtikalNaziv, &s.JedinicaMere, &s.KategorijaNaziv,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: ProdajaRepo.DohvatiStavke: scan: %w", err)
|
||||
@@ -215,12 +218,17 @@ func (r *ProdajaRepo) Kreiraj(ctx context.Context, n *model.ProdajniNalog, stavk
|
||||
pdvIznos = cenaBezPdv * s.PdvStopa / 100
|
||||
}
|
||||
|
||||
ukupnoStavke := float64(s.Kolicina) * s.CenaPoKomadu
|
||||
// popust umanjuje cenu po komadu pre računanja ukupnog i PDV-a
|
||||
cenaPoslePopusta := s.CenaPoKomadu
|
||||
if s.PopustProcenat > 0 {
|
||||
cenaPoslePopusta = cenaPoslePopusta * (1 - s.PopustProcenat/100)
|
||||
}
|
||||
ukupnoStavke := float64(s.Kolicina) * cenaPoslePopusta
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO stavke_prodaje
|
||||
(nalog_id, artikal_id, kolicina, cena_po_komadu, ukupno, pdv_stopa, pdv_iznos, cena_bez_pdv)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
nalogID, s.ArtikalID, s.Kolicina, s.CenaPoKomadu, ukupnoStavke,
|
||||
(nalog_id, artikal_id, kolicina, cena_po_komadu, popust_procenat, ukupno, pdv_stopa, pdv_iznos, cena_bez_pdv)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
nalogID, s.ArtikalID, s.Kolicina, s.CenaPoKomadu, s.PopustProcenat, ukupnoStavke,
|
||||
s.PdvStopa, pdvIznos, cenaBezPdv,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
+42
-17
@@ -62,18 +62,32 @@ type PodaciStampeProdaje struct {
|
||||
PIB string
|
||||
}
|
||||
|
||||
// artikalUJSONSaCenom pretvara listu artikala u template.JS vrednost sa prodajnom cenom, PDV stopom i stanjem
|
||||
// artikalUJSONSaCenom pretvara listu artikala u template.JS vrednost sa prodajnom cenom, PDV stopom, kategorijom i stanjem
|
||||
func artikalUJSONSaCenom(artikli []model.ArtikalSaKategorijom) template.JS {
|
||||
type stavka struct {
|
||||
ID int64 `json:"id"`
|
||||
Naziv string `json:"naziv"`
|
||||
Cena float64 `json:"cena"`
|
||||
PdvStopa float64 `json:"pdv_stopa"`
|
||||
Kolicina int `json:"kolicina"`
|
||||
ID int64 `json:"id"`
|
||||
Naziv string `json:"naziv"`
|
||||
Sifra string `json:"sifra"`
|
||||
Barkod string `json:"barkod"`
|
||||
Cena float64 `json:"cena"`
|
||||
CenaSaPdv float64 `json:"cena_sa_pdv"`
|
||||
PdvStopa float64 `json:"pdv_stopa"`
|
||||
Kolicina int `json:"kolicina"`
|
||||
KategorijaNaziv string `json:"kategorija_naziv"`
|
||||
}
|
||||
lista := make([]stavka, 0, len(artikli))
|
||||
for _, a := range artikli {
|
||||
lista = append(lista, stavka{ID: a.ID, Naziv: a.Naziv, Cena: a.ProdajnaCena, PdvStopa: a.PdvStopa, Kolicina: a.Kolicina})
|
||||
lista = append(lista, stavka{
|
||||
ID: a.ID,
|
||||
Naziv: a.Naziv,
|
||||
Sifra: a.Sifra,
|
||||
Barkod: a.Barkod,
|
||||
Cena: a.ProdajnaCena,
|
||||
CenaSaPdv: a.CenaSaPdv,
|
||||
PdvStopa: a.PdvStopa,
|
||||
Kolicina: a.Kolicina,
|
||||
KategorijaNaziv: a.KategorijaNaziv,
|
||||
})
|
||||
}
|
||||
b, _ := json.Marshal(lista)
|
||||
return template.JS(b)
|
||||
@@ -186,7 +200,11 @@ func (h *Handler) SacuvajProdaju(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var ukupno float64
|
||||
for _, s := range stavke {
|
||||
ukupno += float64(s.Kolicina) * s.CenaPoKomadu
|
||||
cenaPoslePopusta := s.CenaPoKomadu
|
||||
if s.PopustProcenat > 0 {
|
||||
cenaPoslePopusta = cenaPoslePopusta * (1 - s.PopustProcenat/100)
|
||||
}
|
||||
ukupno += float64(s.Kolicina) * cenaPoslePopusta
|
||||
}
|
||||
nalog.Ukupno = ukupno
|
||||
|
||||
@@ -394,9 +412,10 @@ func (h *Handler) StampaProdaje(w http.ResponseWriter, r *http.Request) {
|
||||
h.renderujStandalone(w, "prodaja_stampa", podaci)
|
||||
}
|
||||
|
||||
// ObrisiProdaju prima POST zahtev, vraća stanje na magacin i briše nalog
|
||||
// ObrisiProdaju prima POST zahtev i stornira nalog umesto fizičkog brisanja.
|
||||
// Jednom izdat račun ne sme da nestane — umesto brisanja koristi se storno.
|
||||
func (h *Handler) ObrisiProdaju(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "prodaja.obrisi"); !ok {
|
||||
if _, ok := h.zahtevajDozvolu(w, r, "prodaja.storno"); !ok {
|
||||
return
|
||||
}
|
||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||
@@ -406,9 +425,8 @@ func (h *Handler) ObrisiProdaju(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.ProdajaRepo.Obrisi(r.Context(), id, &k.ID); err != nil {
|
||||
http.Error(w, "Greška pri brisanju naloga", http.StatusInternalServerError)
|
||||
return
|
||||
if err := h.ProdajaRepo.Storno(r.Context(), id, "administrativno uklanjanje", &k.ID); err != nil {
|
||||
slog.Error("greška pri uklanjanju naloga", "error", err)
|
||||
}
|
||||
// ukloni vezani auto-KIR zapis (ako ga je ova prodaja kreirala)
|
||||
if err := h.PdvKirRepo.ObrisiPoIzvoru(r.Context(), "prodaja", id); err != nil {
|
||||
@@ -438,6 +456,7 @@ func parseFormuProdaje(r *http.Request) (model.ProdajniNalog, []model.StavkaProd
|
||||
kolicine := r.Form["kolicina[]"]
|
||||
cene := r.Form["cena_po_komadu[]"]
|
||||
pdvStope := r.Form["pdv_stopa[]"]
|
||||
popusti := r.Form["popust[]"]
|
||||
|
||||
if len(artikalIDovi) == 0 {
|
||||
return nalog, nil, "Prodaja mora imati najmanje jednu stavku."
|
||||
@@ -469,11 +488,17 @@ func parseFormuProdaje(r *http.Request) (model.ProdajniNalog, []model.StavkaProd
|
||||
pdvStopa, _ = strconv.ParseFloat(strings.TrimSpace(pdvStope[i]), 64)
|
||||
}
|
||||
|
||||
var popust float64
|
||||
if i < len(popusti) {
|
||||
popust, _ = strconv.ParseFloat(strings.TrimSpace(popusti[i]), 64)
|
||||
}
|
||||
|
||||
stavke = append(stavke, model.StavkaProdaje{
|
||||
ArtikalID: artikalID,
|
||||
Kolicina: kolicina,
|
||||
CenaPoKomadu: cena,
|
||||
PdvStopa: pdvStopa,
|
||||
ArtikalID: artikalID,
|
||||
Kolicina: kolicina,
|
||||
CenaPoKomadu: cena,
|
||||
PdvStopa: pdvStopa,
|
||||
PopustProcenat: popust,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+13
-11
@@ -17,15 +17,16 @@ type ProdajniNalog struct {
|
||||
|
||||
// StavkaProdaje predstavlja jednu liniju (artikal) unutar prodaje
|
||||
type StavkaProdaje struct {
|
||||
ID int64
|
||||
NalogID int64
|
||||
ArtikalID int64
|
||||
Kolicina int
|
||||
CenaPoKomadu float64
|
||||
Ukupno float64
|
||||
PdvStopa float64
|
||||
PdvIznos float64
|
||||
CenaBezPdv float64
|
||||
ID int64
|
||||
NalogID int64
|
||||
ArtikalID int64
|
||||
Kolicina int
|
||||
CenaPoKomadu float64
|
||||
PopustProcenat float64 // rabat u procentima (npr. 10 = 10% popusta)
|
||||
Ukupno float64
|
||||
PdvStopa float64
|
||||
PdvIznos float64
|
||||
CenaBezPdv float64
|
||||
}
|
||||
|
||||
// UkupnoBezPdv vraća ukupno bez PDV-a za stavku (CenaBezPdv × količina)
|
||||
@@ -42,6 +43,7 @@ type ProdajniNalogSaDetaljem struct {
|
||||
// StavkaProdajeSaArtiklom je stavka prodaje sa nazivom artikla — za prikaz u detaljima
|
||||
type StavkaProdajeSaArtiklom struct {
|
||||
StavkaProdaje
|
||||
ArtikalNaziv string
|
||||
JedinicaMere string
|
||||
ArtikalNaziv string
|
||||
JedinicaMere string
|
||||
KategorijaNaziv string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user