Prodaja: filteri liste (period, storno) i pretraga klijenta u novoj prodaji
- Lista prodaje: filter po periodu (od/do), prečica 'Ovaj mesec', filter samo stornirano - Nova prodaja: pretraga klijenta sa filterom tipa (sva/pravna/fizička lica) umesto običnog dropdown-a, sa dogruzavanjem rezultata pri skrolu - Ispravljen nedostajući CSRF token u formi nove prodaje (POST je uvek padao na 403) - Ispravljeno vizuelno poravnanje polja Način plaćanja
This commit is contained in:
@@ -219,9 +219,17 @@ type ServisRepository interface {
|
||||
SacuvajNaplatu(ctx context.Context, id int64, nacinPlacanja string, naplaceno float64) error
|
||||
}
|
||||
|
||||
// ProdajaFilter definiše parametre za filtriranje liste prodajnih naloga
|
||||
type ProdajaFilter struct {
|
||||
Pretraga string
|
||||
Od string // YYYY-MM-DD, prazno = bez donje granice
|
||||
Do string // YYYY-MM-DD, prazno = bez gornje granice
|
||||
SamoStornirano bool
|
||||
}
|
||||
|
||||
// ProdajaRepository definiše operacije nad prodajnim nalozima
|
||||
type ProdajaRepository interface {
|
||||
Lista(ctx context.Context, pretraga string) ([]model.ProdajniNalogSaDetaljem, error)
|
||||
Lista(ctx context.Context, filter ProdajaFilter) ([]model.ProdajniNalogSaDetaljem, error)
|
||||
DohvatiID(ctx context.Context, id int64) (*model.ProdajniNalog, error)
|
||||
DohvatiStavke(ctx context.Context, nalogID int64) ([]model.StavkaProdajeSaArtiklom, error)
|
||||
Kreiraj(ctx context.Context, n *model.ProdajniNalog, stavke []model.StavkaProdaje, korisnikID *int64) (int64, error)
|
||||
|
||||
@@ -37,8 +37,8 @@ func (r *ProdajaRepo) SledeciBroj(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf("PR-%d-%04d", godina, sledeci), nil
|
||||
}
|
||||
|
||||
// Lista vraća listu prodajnih naloga sa imenom klijenta, opcionom pretragom
|
||||
func (r *ProdajaRepo) Lista(ctx context.Context, pretraga string) ([]model.ProdajniNalogSaDetaljem, error) {
|
||||
// Lista vraća listu prodajnih naloga sa imenom klijenta, po zadatom filteru
|
||||
func (r *ProdajaRepo) Lista(ctx context.Context, filter db.ProdajaFilter) ([]model.ProdajniNalogSaDetaljem, error) {
|
||||
upit := `
|
||||
SELECT
|
||||
pn.id, pn.klijent_id, pn.broj_naloga, pn.napomena, pn.ukupno,
|
||||
@@ -50,9 +50,22 @@ func (r *ProdajaRepo) Lista(ctx context.Context, pretraga string) ([]model.Proda
|
||||
|
||||
args := []any{}
|
||||
|
||||
if pretraga != "" {
|
||||
if filter.Pretraga != "" {
|
||||
upit += " AND (pn.broj_naloga LIKE ? OR kp.naziv LIKE ? OR pn.napomena LIKE ?)"
|
||||
args = append(args, "%"+pretraga+"%", "%"+pretraga+"%", "%"+pretraga+"%")
|
||||
args = append(args, "%"+filter.Pretraga+"%", "%"+filter.Pretraga+"%", "%"+filter.Pretraga+"%")
|
||||
}
|
||||
// pn.datum se čuva preko time.Time.String() (uključuje monotonic sufiks i naziv zone),
|
||||
// pa ga SQLite-ova date() funkcija ne parsira ispravno — poredimo samo ISO datum na početku stringa
|
||||
if filter.Od != "" {
|
||||
upit += " AND substr(pn.datum, 1, 10) >= ?"
|
||||
args = append(args, filter.Od)
|
||||
}
|
||||
if filter.Do != "" {
|
||||
upit += " AND substr(pn.datum, 1, 10) <= ?"
|
||||
args = append(args, filter.Do)
|
||||
}
|
||||
if filter.SamoStornirano {
|
||||
upit += " AND pn.stornirano = 1"
|
||||
}
|
||||
|
||||
upit += " ORDER BY pn.datum DESC"
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
appdb "ntech/internal/db"
|
||||
"ntech/internal/db/sqlite"
|
||||
"ntech/internal/middleware"
|
||||
"ntech/internal/model"
|
||||
@@ -163,7 +164,7 @@ func (h *Handler) KpoBackfill(w http.ResponseWriter, r *http.Request) {
|
||||
var kreirano, preskoceno int
|
||||
|
||||
// prodajni nalozi
|
||||
nalozi, err := h.ProdajaRepo.Lista(ctx, "")
|
||||
nalozi, err := h.ProdajaRepo.Lista(ctx, appdb.ProdajaFilter{})
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju prodaje", http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
appdb "ntech/internal/db"
|
||||
"ntech/internal/db/sqlite"
|
||||
"ntech/internal/middleware"
|
||||
"ntech/internal/model"
|
||||
@@ -222,7 +223,7 @@ func (h *Handler) KirBackfillProdaje(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
sada := time.Now()
|
||||
|
||||
nalozi, err := h.ProdajaRepo.Lista(ctx, "")
|
||||
nalozi, err := h.ProdajaRepo.Lista(ctx, appdb.ProdajaFilter{})
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju prodaje", http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
+58
-10
@@ -25,20 +25,26 @@ import (
|
||||
// PodaciProdaje su podaci za stranicu sa listom prodajnih naloga
|
||||
type PodaciProdaje struct {
|
||||
model.PodaciStranice
|
||||
Nalozi []model.ProdajniNalogSaDetaljem
|
||||
Sacuvano bool
|
||||
Obrisan bool
|
||||
Pretraga string
|
||||
NemaFiskalnog map[int64]bool // ID-evi naloga bez izdatog fiskalnog računa
|
||||
Nalozi []model.ProdajniNalogSaDetaljem
|
||||
Sacuvano bool
|
||||
Obrisan bool
|
||||
Pretraga string
|
||||
Od string
|
||||
Do string
|
||||
OvajMesecOd string
|
||||
OvajMesecDo string
|
||||
SamoStornirano bool
|
||||
NemaFiskalnog map[int64]bool // ID-evi naloga bez izdatog fiskalnog računa
|
||||
}
|
||||
|
||||
// PodaciFormeProdaje su podaci za formu unosa nove prodaje
|
||||
type PodaciFormeProdaje struct {
|
||||
model.PodaciStranice
|
||||
Artikli []model.ArtikalSaKategorijom
|
||||
ArtikliJSON template.JS
|
||||
Klijenti []model.Klijent
|
||||
Greska string
|
||||
Artikli []model.ArtikalSaKategorijom
|
||||
ArtikliJSON template.JS
|
||||
Klijenti []model.Klijent
|
||||
KlijentiJSON template.JS
|
||||
Greska string
|
||||
}
|
||||
|
||||
// PodaciDetaljiProdaje su podaci za pregled jedne prodaje sa stavkama
|
||||
@@ -97,6 +103,29 @@ func artikalUJSONSaCenom(artikli []model.ArtikalSaKategorijom) template.JS {
|
||||
return template.JS(b)
|
||||
}
|
||||
|
||||
// klijentiUJSON pretvara listu klijenata u template.JS vrednost za pretragu na strani klijenta (naziv, tip)
|
||||
func klijentiUJSON(klijenti []model.Klijent) template.JS {
|
||||
type stavka struct {
|
||||
ID int64 `json:"id"`
|
||||
Naziv string `json:"naziv"`
|
||||
Tip string `json:"tip"`
|
||||
Mesto string `json:"mesto"`
|
||||
Telefon string `json:"telefon"`
|
||||
}
|
||||
lista := make([]stavka, 0, len(klijenti))
|
||||
for _, k := range klijenti {
|
||||
lista = append(lista, stavka{
|
||||
ID: k.ID,
|
||||
Naziv: k.PunoIme(),
|
||||
Tip: k.Tip,
|
||||
Mesto: k.Mesto,
|
||||
Telefon: k.Telefon,
|
||||
})
|
||||
}
|
||||
b, _ := json.Marshal(lista)
|
||||
return template.JS(b)
|
||||
}
|
||||
|
||||
// Prodaja renderuje listu svih prodajnih naloga
|
||||
func (h *Handler) Prodaja(w http.ResponseWriter, r *http.Request) {
|
||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
@@ -106,8 +135,16 @@ func (h *Handler) Prodaja(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
pretraga := strings.TrimSpace(r.URL.Query().Get("pretraga"))
|
||||
od := r.URL.Query().Get("od")
|
||||
do := r.URL.Query().Get("do")
|
||||
samoStornirano := r.URL.Query().Get("stornirano") == "1"
|
||||
|
||||
nalozi, err := h.ProdajaRepo.Lista(r.Context(), pretraga)
|
||||
nalozi, err := h.ProdajaRepo.Lista(r.Context(), appdb.ProdajaFilter{
|
||||
Pretraga: pretraga,
|
||||
Od: od,
|
||||
Do: do,
|
||||
SamoStornirano: samoStornirano,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju prodaje", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -118,6 +155,10 @@ func (h *Handler) Prodaja(w http.ResponseWriter, r *http.Request) {
|
||||
nemaFiskalnog, _ = h.FiskalRepo.ProdajeBezFiskalnog(r.Context())
|
||||
}
|
||||
|
||||
sada := time.Now()
|
||||
prviDanMeseca := time.Date(sada.Year(), sada.Month(), 1, 0, 0, 0, 0, sada.Location())
|
||||
poslednjiDanMeseca := prviDanMeseca.AddDate(0, 1, -1)
|
||||
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "prodaja"
|
||||
ps.NaslovStranice = "Prodaja"
|
||||
@@ -127,6 +168,11 @@ func (h *Handler) Prodaja(w http.ResponseWriter, r *http.Request) {
|
||||
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
||||
Obrisan: r.URL.Query().Get("obrisan") == "1",
|
||||
Pretraga: pretraga,
|
||||
Od: od,
|
||||
Do: do,
|
||||
OvajMesecOd: prviDanMeseca.Format("2006-01-02"),
|
||||
OvajMesecDo: poslednjiDanMeseca.Format("2006-01-02"),
|
||||
SamoStornirano: samoStornirano,
|
||||
NemaFiskalnog: nemaFiskalnog,
|
||||
}
|
||||
|
||||
@@ -161,6 +207,7 @@ func (h *Handler) NovaProdaja(w http.ResponseWriter, r *http.Request) {
|
||||
Artikli: artikli,
|
||||
ArtikliJSON: artikalUJSONSaCenom(artikli),
|
||||
Klijenti: klijenti,
|
||||
KlijentiJSON: klijentiUJSON(klijenti),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -189,6 +236,7 @@ func (h *Handler) SacuvajProdaju(w http.ResponseWriter, r *http.Request) {
|
||||
Artikli: artikli,
|
||||
ArtikliJSON: artikalUJSONSaCenom(artikli),
|
||||
Klijenti: klijenti,
|
||||
KlijentiJSON: klijentiUJSON(klijenti),
|
||||
Greska: poruka,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user