Prodaja: modal naplate sa fiskalnim računom (kao u Servisu)
- dugme Naplati otvara modal (primljeno/kusur, način plaćanja, prikaz računa) - primljeni iznos se prosleđuje u fiskalni zahtev radi tačnog Gotovina/Povraćaj - nova ruta i handler za prikaz/štampu fiskalnog računa prodaje - fiskalni tab se otvara preko imenovanog prozora (bez blokiranja popup-a) - Fisk mock: refund se sada računa iz stvarnih plaćanja, ne fiksno 0
This commit is contained in:
+10
-2
@@ -413,6 +413,14 @@ def _build_invoice_response(req, request_id):
|
|||||||
"messages": "Success",
|
"messages": "Success",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# povraćaj — ako je primljeno više nego što je duženo (npr. kupac dao krupniju
|
||||||
|
# novčanicu), razlika se ispisuje na računu; kod refundacije nema povraćaja
|
||||||
|
payments = inv_req.get("payment", [{"type": "Cash", "amount": total_amount}])
|
||||||
|
total_paid = round(sum(float(p.get("amount", 0)) for p in payments), 2)
|
||||||
|
refund = round(total_paid - total_amount, 2) if transaction_type != "Refund" else 0
|
||||||
|
if refund < 0:
|
||||||
|
refund = 0
|
||||||
|
|
||||||
# Puni podaci za snimanje i generisanje računa
|
# Puni podaci za snimanje i generisanje računa
|
||||||
full_data = {
|
full_data = {
|
||||||
**odgovor,
|
**odgovor,
|
||||||
@@ -420,7 +428,7 @@ def _build_invoice_response(req, request_id):
|
|||||||
"invoiceType": invoice_type,
|
"invoiceType": invoice_type,
|
||||||
"transactionType": transaction_type,
|
"transactionType": transaction_type,
|
||||||
"items": items,
|
"items": items,
|
||||||
"payments": inv_req.get("payment", [{"type": "Cash", "amount": total_amount}]),
|
"payments": payments,
|
||||||
"cashier": inv_req.get("cashier", "Kasir"),
|
"cashier": inv_req.get("cashier", "Kasir"),
|
||||||
"buyerId": inv_req.get("buyerId", ""),
|
"buyerId": inv_req.get("buyerId", ""),
|
||||||
"referentDocumentNumber": inv_req.get("referentDocumentNumber", ""),
|
"referentDocumentNumber": inv_req.get("referentDocumentNumber", ""),
|
||||||
@@ -430,7 +438,7 @@ def _build_invoice_response(req, request_id):
|
|||||||
"store": firma["locationName"],
|
"store": firma["locationName"],
|
||||||
"address": firma["address"],
|
"address": firma["address"],
|
||||||
"district": firma["district"],
|
"district": firma["district"],
|
||||||
"refund": 0,
|
"refund": refund,
|
||||||
# za avansni konačni
|
# za avansni konačni
|
||||||
"advancePaid": req.get("advancePaid", 0),
|
"advancePaid": req.get("advancePaid", 0),
|
||||||
"advanceTax": req.get("advanceTax", 0),
|
"advanceTax": req.get("advanceTax", 0),
|
||||||
|
|||||||
@@ -420,6 +420,7 @@ func main() {
|
|||||||
r.With(doz("prodaja.storno")).Post("/prodaja/storno/{id}", h.StornoProdaje)
|
r.With(doz("prodaja.storno")).Post("/prodaja/storno/{id}", h.StornoProdaje)
|
||||||
r.With(doz("prodaja.dodaj")).Post("/prodaja/{id}/fiskalizuj", h.RetryFiskalizacijaProdaje)
|
r.With(doz("prodaja.dodaj")).Post("/prodaja/{id}/fiskalizuj", h.RetryFiskalizacijaProdaje)
|
||||||
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja/{id}/stampa", h.StampaProdaje)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja/{id}/stampa", h.StampaProdaje)
|
||||||
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja/{id}/fiskalni-racun", h.StampaFiskalnogProdaje)
|
||||||
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja/{id}", h.DetaljiProdaje)
|
r.With(ntechmw.RequireDozvola(h.DozvoleRepo.ImaDozvolu, "prodaja.pregled")).Get("/prodaja/{id}", h.DetaljiProdaje)
|
||||||
|
|
||||||
// podsetnici
|
// podsetnici
|
||||||
|
|||||||
@@ -51,11 +51,14 @@ func TipPlacanja(nacin string) string {
|
|||||||
|
|
||||||
// NapraviZahtev konvertuje prodajni nalog i stavke u Teron InvoiceRequest.
|
// NapraviZahtev konvertuje prodajni nalog i stavke u Teron InvoiceRequest.
|
||||||
// VAŽNO: sve cene se konvertuju iz neto u bruto pre slanja (BrutoCena).
|
// VAŽNO: sve cene se konvertuju iz neto u bruto pre slanja (BrutoCena).
|
||||||
// pib/jmbg se koriste za buyerId identifikaciju kupca.
|
// pib/jmbg se koriste za buyerId identifikaciju kupca. primljeno je iznos koji je
|
||||||
|
// kupac stvarno predao (za obračun povraćaja na fiskalnom računu) — ako je 0 ili
|
||||||
|
// manji od ukupnog iznosa, šalje se tačan iznos duga (bez povraćaja).
|
||||||
func NapraviZahtev(
|
func NapraviZahtev(
|
||||||
nalog model.ProdajniNalog,
|
nalog model.ProdajniNalog,
|
||||||
stavke []model.StavkaProdajeSaArtiklom,
|
stavke []model.StavkaProdajeSaArtiklom,
|
||||||
pib, jmbg, kasir string,
|
pib, jmbg, kasir string,
|
||||||
|
primljeno float64,
|
||||||
) InvoiceRequest {
|
) InvoiceRequest {
|
||||||
items := make([]InvoiceItem, 0, len(stavke))
|
items := make([]InvoiceItem, 0, len(stavke))
|
||||||
ukupanIznos := 0.0
|
ukupanIznos := 0.0
|
||||||
@@ -90,6 +93,13 @@ func NapraviZahtev(
|
|||||||
// Ukupno za plaćanje je uvek suma bruto stavki (neto iz naloga bi bilo pogrešno)
|
// Ukupno za plaćanje je uvek suma bruto stavki (neto iz naloga bi bilo pogrešno)
|
||||||
ukupnoPlacanje := ukupanIznos
|
ukupnoPlacanje := ukupanIznos
|
||||||
|
|
||||||
|
// ako je kupac dao više od duga, na računu se iskazuje stvarno primljen iznos
|
||||||
|
// (fiskalni uređaj sam izračunava povraćaj kao razliku)
|
||||||
|
iznosPlacanja := ukupnoPlacanje
|
||||||
|
if primljeno > ukupnoPlacanje {
|
||||||
|
iznosPlacanja = primljeno
|
||||||
|
}
|
||||||
|
|
||||||
nacinPlacanja := nalog.NacinPlacanja
|
nacinPlacanja := nalog.NacinPlacanja
|
||||||
if nacinPlacanja == "" {
|
if nacinPlacanja == "" {
|
||||||
nacinPlacanja = "Gotovina"
|
nacinPlacanja = "Gotovina"
|
||||||
@@ -101,7 +111,7 @@ func NapraviZahtev(
|
|||||||
TransactionType: "Sale",
|
TransactionType: "Sale",
|
||||||
Payment: []PaymentItem{
|
Payment: []PaymentItem{
|
||||||
{
|
{
|
||||||
Amount: ukupnoPlacanje,
|
Amount: iznosPlacanja,
|
||||||
PaymentType: TipPlacanja(nacinPlacanja),
|
PaymentType: TipPlacanja(nacinPlacanja),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -127,7 +137,7 @@ func NapraviRefundZahtev(
|
|||||||
stavke []model.StavkaProdajeSaArtiklom,
|
stavke []model.StavkaProdajeSaArtiklom,
|
||||||
pib, jmbg, kasir, referentBroj string,
|
pib, jmbg, kasir, referentBroj string,
|
||||||
) InvoiceRequest {
|
) InvoiceRequest {
|
||||||
zahtev := NapraviZahtev(nalog, stavke, pib, jmbg, kasir)
|
zahtev := NapraviZahtev(nalog, stavke, pib, jmbg, kasir, 0)
|
||||||
zahtev.InvoiceRequest.TransactionType = "Refund"
|
zahtev.InvoiceRequest.TransactionType = "Refund"
|
||||||
zahtev.InvoiceRequest.ReferentDocumentNumber = referentBroj
|
zahtev.InvoiceRequest.ReferentDocumentNumber = referentBroj
|
||||||
return zahtev
|
return zahtev
|
||||||
|
|||||||
@@ -325,9 +325,14 @@ func (h *Handler) SacuvajProdaju(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fiskalizacija — ako je modul uključen (best-effort: prodaja ostaje validna i bez fiskalizacije)
|
// Fiskalizacija — ako je modul uključen (best-effort: prodaja ostaje validna i bez fiskalizacije)
|
||||||
|
racunKreiran := false
|
||||||
if h.modulUkljucen(r.Context(), config.ModulFiskalizacija) {
|
if h.modulUkljucen(r.Context(), config.ModulFiskalizacija) {
|
||||||
if klijent := h.fiskalKlijent(); klijent != nil {
|
if klijent := h.fiskalKlijent(); klijent != nil {
|
||||||
h.fiskalizujProdaju(r.Context(), id, klijent)
|
primljeno, _ := strconv.ParseFloat(strings.TrimSpace(r.FormValue("primljeno")), 64)
|
||||||
|
h.fiskalizujProdaju(r.Context(), id, klijent, primljeno)
|
||||||
|
if fr, _ := h.FiskalRepo.DohvatiPoProdaji(r.Context(), id); fr != nil {
|
||||||
|
racunKreiran = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +352,11 @@ func (h *Handler) SacuvajProdaju(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/prodaja/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther)
|
redirectURL := "/prodaja/" + strconv.FormatInt(id, 10) + "?sacuvano=1"
|
||||||
|
if r.FormValue("prikazi_racun") == "1" && racunKreiran {
|
||||||
|
redirectURL += "&racun=1"
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetaljiProdaje prikazuje pregled jednog prodajnog naloga sa svim stavkama
|
// DetaljiProdaje prikazuje pregled jednog prodajnog naloga sa svim stavkama
|
||||||
@@ -470,6 +479,60 @@ func (h *Handler) StampaProdaje(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.renderujStandalone(w, "prodaja_stampa", podaci)
|
h.renderujStandalone(w, "prodaja_stampa", podaci)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StampaFiskalnogProdaje renderuje journal izdatog fiskalnog računa za prodaju (za štampu/uvid)
|
||||||
|
func (h *Handler) StampaFiskalnogProdaje(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Neispravan ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fr, err := h.FiskalRepo.DohvatiPoProdaji(r.Context(), id)
|
||||||
|
if err != nil || fr == nil {
|
||||||
|
http.Error(w, "Fiskalni račun nije pronađen", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nalog, _ := h.ProdajaRepo.DohvatiID(r.Context(), id)
|
||||||
|
journal := ""
|
||||||
|
if fr.SiroviOdgovor != "" {
|
||||||
|
var raw map[string]any
|
||||||
|
if json.Unmarshal([]byte(fr.SiroviOdgovor), &raw) == nil {
|
||||||
|
if j, ok := raw["journal"].(string); ok {
|
||||||
|
journal = j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nalog != nil {
|
||||||
|
journal += "\n\n--- NTech prodaja: " + nalog.BrojNaloga + " ---\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Podeli journal na deo pre i posle QR placeholder-a
|
||||||
|
const qrPlaceholder = "{{{{QR-KOD}}}}"
|
||||||
|
pre, post, hasQR := strings.Cut(journal, qrPlaceholder)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
fmt.Fprint(w, `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Fiskalni račun</title>
|
||||||
|
<style>
|
||||||
|
*{box-sizing:border-box;}
|
||||||
|
body{font-family:monospace;font-size:12px;padding:20px;max-width:max-content;margin:0 auto;}
|
||||||
|
pre{white-space:pre;margin:0;padding:0;font-family:inherit;font-size:inherit;display:block;}
|
||||||
|
@media print{body{font-size:11px;padding:10px;}}
|
||||||
|
</style></head><body>`)
|
||||||
|
|
||||||
|
fmt.Fprint(w, `<pre>`)
|
||||||
|
fmt.Fprint(w, pre)
|
||||||
|
fmt.Fprint(w, `</pre>`)
|
||||||
|
|
||||||
|
if hasQR && fr.QRKod != "" {
|
||||||
|
fmt.Fprintf(w, `<div style="margin:10px 0;"><img src="data:image/png;base64,%s" style="display:block;margin:0 auto;width:72mm;height:72mm;"></div>`, fr.QRKod)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(w, `<pre>`)
|
||||||
|
fmt.Fprint(w, post)
|
||||||
|
fmt.Fprint(w, `</pre>`)
|
||||||
|
|
||||||
|
fmt.Fprint(w, `<p style="margin-top:16px;"><button onclick="window.print()" style="padding:8px 16px;">Štampaj</button></p></body></html>`)
|
||||||
|
}
|
||||||
|
|
||||||
// ObrisiProdaju prima POST zahtev i stornira nalog umesto fizičkog brisanja.
|
// ObrisiProdaju prima POST zahtev i stornira nalog umesto fizičkog brisanja.
|
||||||
// Jednom izdat račun ne sme da nestane — umesto brisanja koristi se storno.
|
// Jednom izdat račun ne sme da nestane — umesto brisanja koristi se storno.
|
||||||
func (h *Handler) ObrisiProdaju(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ObrisiProdaju(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -733,7 +796,9 @@ func (h *Handler) stornirajProdaju(ctx context.Context, id int64, razlog string,
|
|||||||
|
|
||||||
// fiskalizujProdaju šalje fiskalni zahtev za prodajni nalog. Best-effort: greške
|
// fiskalizujProdaju šalje fiskalni zahtev za prodajni nalog. Best-effort: greške
|
||||||
// se loguju, ne zaustavljaju tok pozivaoca (kreiranje prodaje ili ručni retry).
|
// se loguju, ne zaustavljaju tok pozivaoca (kreiranje prodaje ili ručni retry).
|
||||||
func (h *Handler) fiskalizujProdaju(ctx context.Context, prodajaID int64, klijent *fiskal.Klijent) {
|
// primljeno je iznos koji je kupac predao (za tačan povraćaj na računu) — 0 ako
|
||||||
|
// nije poznat (npr. ručni retry), tada se šalje tačan dug bez povraćaja.
|
||||||
|
func (h *Handler) fiskalizujProdaju(ctx context.Context, prodajaID int64, klijent *fiskal.Klijent, primljeno float64) {
|
||||||
nalog, err := h.ProdajaRepo.DohvatiID(ctx, prodajaID)
|
nalog, err := h.ProdajaRepo.DohvatiID(ctx, prodajaID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("fiskalizujProdaju: nije pronađen nalog", "prodaja_id", prodajaID, "error", err)
|
slog.Error("fiskalizujProdaju: nije pronađen nalog", "prodaja_id", prodajaID, "error", err)
|
||||||
@@ -757,7 +822,7 @@ func (h *Handler) fiskalizujProdaju(ctx context.Context, prodajaID int64, klijen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zahtev := fiskal.NapraviZahtev(*nalog, stavke, pib, jmbg, kasir)
|
zahtev := fiskal.NapraviZahtev(*nalog, stavke, pib, jmbg, kasir, primljeno)
|
||||||
|
|
||||||
odgovor, errFisk := klijent.IzdajRacun(ctx, zahtev)
|
odgovor, errFisk := klijent.IzdajRacun(ctx, zahtev)
|
||||||
if errFisk != nil {
|
if errFisk != nil {
|
||||||
@@ -822,7 +887,7 @@ func (h *Handler) RetryFiskalizacijaProdaje(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.fiskalizujProdaju(r.Context(), id, klijent)
|
h.fiskalizujProdaju(r.Context(), id, klijent, 0)
|
||||||
|
|
||||||
if fr, _ := h.FiskalRepo.DohvatiPoProdaji(r.Context(), id); fr != nil {
|
if fr, _ := h.FiskalRepo.DohvatiPoProdaji(r.Context(), id); fr != nil {
|
||||||
middleware.SetFlash(w, r, h.DB, "uspeh", "Fiskalni račun izdat.")
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Fiskalni račun izdat.")
|
||||||
|
|||||||
@@ -138,6 +138,10 @@ document.addEventListener('alpine:init', () => {
|
|||||||
Alpine.data('prodajaForma', () => ({
|
Alpine.data('prodajaForma', () => ({
|
||||||
stavke: [{artikal_id: '', kolicina: 1, cena: 0, cena_sa_pdv: 0, pdv_stopa: 20, popust: 0, kategorija_naziv: '', _naziv: '', _pretraga: '', _prikaziListu: false, _limit: 30}],
|
stavke: [{artikal_id: '', kolicina: 1, cena: 0, cena_sa_pdv: 0, pdv_stopa: 20, popust: 0, kategorija_naziv: '', _naziv: '', _pretraga: '', _prikaziListu: false, _limit: 30}],
|
||||||
zbirnoHover: false,
|
zbirnoHover: false,
|
||||||
|
nacinPlacanja: 'gotovina',
|
||||||
|
primljenoIznos: '',
|
||||||
|
prikaziRacun: true,
|
||||||
|
_fiskalniTab: null,
|
||||||
artikliOpcije: [],
|
artikliOpcije: [],
|
||||||
pretragaArtikal: '',
|
pretragaArtikal: '',
|
||||||
pdvObveznik: true,
|
pdvObveznik: true,
|
||||||
@@ -357,6 +361,24 @@ document.addEventListener('alpine:init', () => {
|
|||||||
this.stavke = this.stavke.filter(s => s.artikal_id)
|
this.stavke = this.stavke.filter(s => s.artikal_id)
|
||||||
this.$nextTick(() => e.target.submit())
|
this.$nextTick(() => e.target.submit())
|
||||||
},
|
},
|
||||||
|
otvoriModalNaplate() {
|
||||||
|
this.primljenoIznos = ''
|
||||||
|
this.$refs.dijalogNaplate.showModal()
|
||||||
|
},
|
||||||
|
kusurIznos() {
|
||||||
|
return (parseFloat(this.primljenoIznos) || 0) - parseFloat(this.ukupnoSvega())
|
||||||
|
},
|
||||||
|
naplataValidna() {
|
||||||
|
return this.primljenoIznos !== '' && (parseFloat(this.primljenoIznos) || 0) >= parseFloat(this.ukupnoSvega())
|
||||||
|
},
|
||||||
|
// pripremiFiskalniTab otvara prazan tab SINHRONO unutar klika (očuvan user gesture),
|
||||||
|
// da bi kasnije, posle redirekta na stranicu detalja prodaje, mogao da se imenom
|
||||||
|
// popuni URL-om fiskalnog računa bez blokiranja popup-blokera
|
||||||
|
pripremiFiskalniTab() {
|
||||||
|
if (this.prikaziRacun) {
|
||||||
|
this._fiskalniTab = window.open('', 'ntech-fiskalni-tab')
|
||||||
|
}
|
||||||
|
},
|
||||||
ukupnoStavke(s) {
|
ukupnoStavke(s) {
|
||||||
const cena = parseFloat(s.cena) || 0
|
const cena = parseFloat(s.cena) || 0
|
||||||
const popust = parseFloat(s.popust) || 0
|
const popust = parseFloat(s.popust) || 0
|
||||||
|
|||||||
@@ -287,6 +287,15 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
// ako je kroz modal naplate u „Nova prodaja" zatražen prikaz — otvoren je prazan
|
||||||
|
// imenovani tab pre redirekta ovde; sada ga popunjavamo (ne blokira popup-blocker)
|
||||||
|
if (new URLSearchParams(location.search).get('racun') === '1') {
|
||||||
|
window.open('/prodaja/{{.Nalog.ID}}/fiskalni-racun', 'ntech-fiskalni-tab')
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .FiskalGreska}}
|
{{if .FiskalGreska}}
|
||||||
|
|||||||
@@ -46,54 +46,44 @@
|
|||||||
<span style="font-size: 16px; font-weight: 500; color: var(--tekst-glavni)">Nova prodaja</span>
|
<span style="font-size: 16px; font-weight: 500; color: var(--tekst-glavni)">Nova prodaja</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; flex-direction: column; gap: 14px">
|
<div style="display: flex; flex-direction: column; gap: 14px">
|
||||||
<div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
<div>
|
||||||
<div>
|
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; flex-wrap: wrap; gap: 6px">
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; flex-wrap: wrap; gap: 6px">
|
<label class="polje-labela" style="margin-bottom: 0">Klijent</label>
|
||||||
<label class="polje-labela" style="margin-bottom: 0">Klijent</label>
|
<div style="display: flex; gap: 6px">
|
||||||
<div style="display: flex; gap: 6px">
|
<label class="tip-klijenta-opcija" :class="klasaTipKlijenta('sve')" @click="postaviTipKlijenta('sve')">
|
||||||
<label class="tip-klijenta-opcija" :class="klasaTipKlijenta('sve')" @click="postaviTipKlijenta('sve')">
|
<input type="radio" name="tip-klijenta-filter" value="sve" :checked="jeTipKlijenta('sve')" style="margin: 0" /> Sva lica
|
||||||
<input type="radio" name="tip-klijenta-filter" value="sve" :checked="jeTipKlijenta('sve')" style="margin: 0" /> Sva lica
|
</label>
|
||||||
</label>
|
<label class="tip-klijenta-opcija" :class="klasaTipKlijenta('pravno')" @click="postaviTipKlijenta('pravno')">
|
||||||
<label class="tip-klijenta-opcija" :class="klasaTipKlijenta('pravno')" @click="postaviTipKlijenta('pravno')">
|
<input type="radio" name="tip-klijenta-filter" value="pravno" :checked="jeTipKlijenta('pravno')" style="margin: 0" /> Pravna lica
|
||||||
<input type="radio" name="tip-klijenta-filter" value="pravno" :checked="jeTipKlijenta('pravno')" style="margin: 0" /> Pravna lica
|
</label>
|
||||||
</label>
|
<label class="tip-klijenta-opcija" :class="klasaTipKlijenta('fizicko')" @click="postaviTipKlijenta('fizicko')">
|
||||||
<label class="tip-klijenta-opcija" :class="klasaTipKlijenta('fizicko')" @click="postaviTipKlijenta('fizicko')">
|
<input type="radio" name="tip-klijenta-filter" value="fizicko" :checked="jeTipKlijenta('fizicko')" style="margin: 0" /> Fizička lica
|
||||||
<input type="radio" name="tip-klijenta-filter" value="fizicko" :checked="jeTipKlijenta('fizicko')" style="margin: 0" /> Fizička lica
|
</label>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="klijent_id" :value="izabraniKlijentId || ''" />
|
|
||||||
<template x-if="izabraniKlijent">
|
|
||||||
<div style="display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; padding: 8px 10px; background: var(--pozadina); border: 0.5px solid var(--ivica); border-radius: 8px">
|
|
||||||
<div>
|
|
||||||
<div style="font-size: 13px; font-weight: 500; color: var(--tekst-glavni)" x-text="izabraniKlijent.naziv"></div>
|
|
||||||
<template x-for="p in poljaIzabranogKlijenta(izabraniKlijent)" :key="p.labela">
|
|
||||||
<div style="font-size: 11px; color: var(--tekst-sporedni)"><span x-text="p.labela"></span>: <span x-text="p.vrednost"></span></div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn-sekundarno" @click="ocistiKlijenta()" style="padding: 3px 10px; font-size: 12px">Promeni</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div x-show="!izabraniKlijent" style="position: relative" @click.outside="prikaziListuKlijenata = false">
|
|
||||||
<input type="text" x-model="pretragaKlijent" autocomplete="off" @focus="pretragaKlijentPromenjena()" @input="pretragaKlijentPromenjena()" placeholder="Pretraži klijenta ili ostavi prazno..." style="width: 100%" />
|
|
||||||
<div x-show="prikaziListuKlijenata && filtriraniKlijenti.length" x-cloak @scroll="ucitajViseKlijenata($event)" style="position: absolute; z-index: 30; left: 0; right: 0; margin-top: 4px; background: var(--pozadina); border: 0.5px solid var(--ivica); border-radius: 10px; max-height: 220px; overflow: auto; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14)">
|
|
||||||
<template x-for="k in filtriraniKlijenti" :key="k.id">
|
|
||||||
<div @click="izaberiKlijenta(k)" class="klijent-stavka" style="padding: 8px 12px; cursor: pointer; border-bottom: 0.5px solid var(--ivica)">
|
|
||||||
<div style="font-size: 13px; font-weight: 500; color: var(--tekst-glavni)" x-text="k.naziv"></div>
|
|
||||||
<div style="font-size: 11px; color: var(--tekst-sporedni)" x-text="detaljKlijenta(k)"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div x-show="imaJosKlijenata" style="padding: 6px 12px; text-align: center; font-size: 11px; color: var(--tekst-sporedni)">Skrolujte za još rezultata...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; flex-direction: column; justify-content: flex-end">
|
<input type="hidden" name="klijent_id" :value="izabraniKlijentId || ''" />
|
||||||
<label class="polje-labela">Način plaćanja</label>
|
<template x-if="izabraniKlijent">
|
||||||
<select name="nacin_placanja" style="width:100%;">
|
<div style="display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; padding: 8px 10px; background: var(--pozadina); border: 0.5px solid var(--ivica); border-radius: 8px">
|
||||||
<option value="gotovina">Gotovina</option>
|
<div>
|
||||||
<option value="kartica">Platna kartica</option>
|
<div style="font-size: 13px; font-weight: 500; color: var(--tekst-glavni)" x-text="izabraniKlijent.naziv"></div>
|
||||||
<option value="prenos">Bankarski prenos</option>
|
<template x-for="p in poljaIzabranogKlijenta(izabraniKlijent)" :key="p.labela">
|
||||||
</select>
|
<div style="font-size: 11px; color: var(--tekst-sporedni)"><span x-text="p.labela"></span>: <span x-text="p.vrednost"></span></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-sekundarno" @click="ocistiKlijenta()" style="padding: 3px 10px; font-size: 12px">Promeni</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div x-show="!izabraniKlijent" style="position: relative" @click.outside="prikaziListuKlijenata = false">
|
||||||
|
<input type="text" x-model="pretragaKlijent" autocomplete="off" @focus="pretragaKlijentPromenjena()" @input="pretragaKlijentPromenjena()" placeholder="Pretraži klijenta ili ostavi prazno..." style="width: 100%" />
|
||||||
|
<div x-show="prikaziListuKlijenata && filtriraniKlijenti.length" x-cloak @scroll="ucitajViseKlijenata($event)" style="position: absolute; z-index: 30; left: 0; right: 0; margin-top: 4px; background: var(--pozadina); border: 0.5px solid var(--ivica); border-radius: 10px; max-height: 220px; overflow: auto; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14)">
|
||||||
|
<template x-for="k in filtriraniKlijenti" :key="k.id">
|
||||||
|
<div @click="izaberiKlijenta(k)" class="klijent-stavka" style="padding: 8px 12px; cursor: pointer; border-bottom: 0.5px solid var(--ivica)">
|
||||||
|
<div style="font-size: 13px; font-weight: 500; color: var(--tekst-glavni)" x-text="k.naziv"></div>
|
||||||
|
<div style="font-size: 11px; color: var(--tekst-sporedni)" x-text="detaljKlijenta(k)"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div x-show="imaJosKlijenata" style="padding: 6px 12px; text-align: center; font-size: 11px; color: var(--tekst-sporedni)">Skrolujte za još rezultata...</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -302,10 +292,52 @@
|
|||||||
<!-- dugmad forme -->
|
<!-- dugmad forme -->
|
||||||
<div style="display: flex; justify-content: flex-end; gap: 10px">
|
<div style="display: flex; justify-content: flex-end; gap: 10px">
|
||||||
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/prodaja" class="btn-sekundarno">Odustani</a>
|
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/prodaja" class="btn-sekundarno">Odustani</a>
|
||||||
<button type="submit" class="btn-primarno" :disabled="imaPrekoracenja() || !imaPopunjenuStavku()" :style="(imaPrekoracenja() || !imaPopunjenuStavku()) ? 'opacity:0.4;cursor:not-allowed;' : ''">
|
<button type="button" @click="otvoriModalNaplate()" class="btn-primarno" :disabled="imaPrekoracenja() || !imaPopunjenuStavku()" :style="(imaPrekoracenja() || !imaPopunjenuStavku()) ? 'opacity:0.4;cursor:not-allowed;' : ''">
|
||||||
Naplati
|
Naplati
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- modal naplate — unos primljenog iznosa/kusura i način plaćanja pre konačnog slanja prodaje -->
|
||||||
|
<dialog class="premesti-modal" x-ref="dijalogNaplate" onclick="if(event.target===this)this.close()">
|
||||||
|
<div class="premesti-zaglavlje">
|
||||||
|
<h3>Naplata</h3>
|
||||||
|
<button type="button" class="premesti-zatvori" @click="$refs.dijalogNaplate.close()" aria-label="Zatvori">×</button>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;flex-direction:column;gap:10px;padding:16px;">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
||||||
|
<span style="font-size:16px;font-weight:600;color:var(--tekst-glavni);">Za naplatu</span>
|
||||||
|
<span style="font-size:18px;font-weight:600;color:var(--tekst-glavni);" x-text="fmtDin(ukupnoSvega()) + ' din'"></span>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:6px;padding-top:10px;border-top:0.5px solid var(--ivica);">
|
||||||
|
<label class="polje-labela">Primljeno (din)</label>
|
||||||
|
<input type="number" name="primljeno" min="0" step="0.01" x-model="primljenoIznos" placeholder="Unesite primljeni iznos"
|
||||||
|
style="width:100%;padding:8px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-glavni);font-size:14px;box-sizing:border-box;">
|
||||||
|
</div>
|
||||||
|
<div x-show="primljenoIznos !== '' && kusurIznos() >= 0" x-cloak
|
||||||
|
style="display:flex;justify-content:space-between;align-items:center;padding:8px 12px;background:color-mix(in srgb,var(--uspeh) 10%,transparent);border:0.5px solid var(--uspeh);border-radius:8px;">
|
||||||
|
<span style="color:var(--uspeh);font-weight:500;">Kusur (vratiti)</span>
|
||||||
|
<span style="font-size:16px;font-weight:600;color:var(--uspeh);" x-text="fmtDin(kusurIznos()) + ' din'"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="polje-labela">Način plaćanja</label>
|
||||||
|
<select name="nacin_placanja" x-model="nacinPlacanja"
|
||||||
|
style="width:100%;padding:8px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-glavni);font-size:13px;">
|
||||||
|
<option value="gotovina">Gotovina</option>
|
||||||
|
<option value="kartica">Platna kartica</option>
|
||||||
|
<option value="prenos">Bankarski prenos</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label style="display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;">
|
||||||
|
<input type="checkbox" name="prikazi_racun" value="1" x-model="prikaziRacun"
|
||||||
|
style="width:16px;height:16px;accent-color:var(--sb-akcent);cursor:pointer;flex-shrink:0;">
|
||||||
|
<span style="font-size:13px;color:var(--tekst-glavni);">Prikaži fiskalni račun</span>
|
||||||
|
</label>
|
||||||
|
<button type="submit" class="btn-primarno" style="width:100%;margin-top:4px;" :disabled="!naplataValidna()"
|
||||||
|
:style="!naplataValidna() ? 'opacity:0.4;cursor:not-allowed;' : ''" @click="pripremiFiskalniTab()">
|
||||||
|
Naplati
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
Reference in New Issue
Block a user