Fiskalizacija: refund pri stornu, dnevni pazar KIR, popust na stavci
This commit is contained in:
@@ -312,6 +312,8 @@ func main() {
|
|||||||
r.With(modul("pdv"), doz("pdv.dodaj")).Post("/pdv/kir/nova", h.SacuvajPdvKir)
|
r.With(modul("pdv"), doz("pdv.dodaj")).Post("/pdv/kir/nova", h.SacuvajPdvKir)
|
||||||
r.With(modul("pdv"), doz("pdv.obrisi")).Post("/pdv/kir/obrisi/{id}", h.ObrisiPdvKir)
|
r.With(modul("pdv"), doz("pdv.obrisi")).Post("/pdv/kir/obrisi/{id}", h.ObrisiPdvKir)
|
||||||
r.With(modul("pdv"), doz("pdv.dodaj")).Post("/pdv/kir/backfill-prodaje", h.KirBackfillProdaje)
|
r.With(modul("pdv"), doz("pdv.dodaj")).Post("/pdv/kir/backfill-prodaje", h.KirBackfillProdaje)
|
||||||
|
r.With(modul("pdv"), doz("pdv.dodaj")).Get("/pdv/kir/dnevni-pazar", h.DnevniPazarKir)
|
||||||
|
r.With(modul("pdv"), doz("pdv.dodaj")).Post("/pdv/kir/dnevni-pazar", h.SacuvajDnevniPazarKir)
|
||||||
r.With(modul("pdv")).Get("/pdv/kpr", h.PdvKpr)
|
r.With(modul("pdv")).Get("/pdv/kpr", h.PdvKpr)
|
||||||
r.With(modul("pdv")).Get("/pdv/kpr/nova", h.NoviPdvKpr)
|
r.With(modul("pdv")).Get("/pdv/kpr/nova", h.NoviPdvKpr)
|
||||||
r.With(modul("pdv"), doz("pdv.dodaj")).Post("/pdv/kpr/nova", h.SacuvajPdvKpr)
|
r.With(modul("pdv"), doz("pdv.dodaj")).Post("/pdv/kpr/nova", h.SacuvajPdvKpr)
|
||||||
|
|||||||
@@ -223,6 +223,8 @@ type ProdajaRepository interface {
|
|||||||
Storno(ctx context.Context, id int64, razlog string, korisnikID *int64) error
|
Storno(ctx context.Context, id int64, razlog string, korisnikID *int64) error
|
||||||
Obrisi(ctx context.Context, id int64, korisnikID *int64) error
|
Obrisi(ctx context.Context, id int64, korisnikID *int64) error
|
||||||
SledeciBroj(ctx context.Context) (string, error)
|
SledeciBroj(ctx context.Context) (string, error)
|
||||||
|
// DnevniPrometMaloprodaje vraća zbir maloprodajnih stavki (bez klijenta) za zadati dan (YYYY-MM-DD).
|
||||||
|
DnevniPrometMaloprodaje(ctx context.Context, datum string) (model.DnevniPrometKir, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServisniDeloviRepository definiše operacije nad ugrađenim delovima u servisu
|
// ServisniDeloviRepository definiše operacije nad ugrađenim delovima u servisu
|
||||||
@@ -381,4 +383,5 @@ type FiskalRepository interface {
|
|||||||
Kreiraj(ctx context.Context, fr *model.FiskalniRacun) (int64, error)
|
Kreiraj(ctx context.Context, fr *model.FiskalniRacun) (int64, error)
|
||||||
DohvatiPoProdaji(ctx context.Context, prodajaID int64) (*model.FiskalniRacun, error)
|
DohvatiPoProdaji(ctx context.Context, prodajaID int64) (*model.FiskalniRacun, error)
|
||||||
DohvatiPoServisu(ctx context.Context, servisID int64) (*model.FiskalniRacun, error)
|
DohvatiPoServisu(ctx context.Context, servisID int64) (*model.FiskalniRacun, error)
|
||||||
|
OznačiKaoStorniran(ctx context.Context, id int64) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,3 +114,12 @@ func (r *FiskalRepo) DohvatiPoServisu(ctx context.Context, servisID int64) (*mod
|
|||||||
fr.Storniran = storniran != 0
|
fr.Storniran = storniran != 0
|
||||||
return fr, nil
|
return fr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OznačiKaoStorniran postavlja storniran=1 za fiskalni račun sa datim ID-jem.
|
||||||
|
func (r *FiskalRepo) OznačiKaoStorniran(ctx context.Context, id int64) error {
|
||||||
|
_, err := r.db.ExecContext(ctx, "UPDATE fiskalni_racuni SET storniran = 1 WHERE id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ntech: FiskalRepo.OznačiKaoStorniran: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -414,3 +414,47 @@ func (r *ProdajaRepo) Obrisi(ctx context.Context, id int64, korisnikID *int64) e
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DnevniPrometMaloprodaje vraća zbrojene iznose maloprodajnih stavki za zadati dan.
|
||||||
|
// datum je string oblika "YYYY-MM-DD". Isključuje stornirane naloge i B2B (sa klijentom).
|
||||||
|
func (r *ProdajaRepo) DnevniPrometMaloprodaje(ctx context.Context, datum string) (model.DnevniPrometKir, error) {
|
||||||
|
var p model.DnevniPrometKir
|
||||||
|
|
||||||
|
// broj naloga tog dana
|
||||||
|
err := r.db.QueryRowContext(ctx, `
|
||||||
|
SELECT COUNT(*) FROM prodajni_nalozi
|
||||||
|
WHERE klijent_id IS NULL AND stornirano = 0 AND DATE(datum) = ?`, datum,
|
||||||
|
).Scan(&p.BrojNaloga)
|
||||||
|
if err != nil {
|
||||||
|
return p, fmt.Errorf("ntech: ProdajaRepo.DnevniPrometMaloprodaje: count: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stavke grupisane po PDV stopi
|
||||||
|
redovi, err := r.db.QueryContext(ctx, `
|
||||||
|
SELECT s.pdv_stopa, SUM(s.ukupno), SUM(s.ukupno * s.pdv_stopa / 100.0)
|
||||||
|
FROM stavke_prodaje s
|
||||||
|
JOIN prodajni_nalozi p ON p.id = s.nalog_id
|
||||||
|
WHERE p.klijent_id IS NULL AND p.stornirano = 0 AND DATE(p.datum) = ?
|
||||||
|
GROUP BY s.pdv_stopa`, datum,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return p, fmt.Errorf("ntech: ProdajaRepo.DnevniPrometMaloprodaje: query: %w", err)
|
||||||
|
}
|
||||||
|
defer redovi.Close()
|
||||||
|
|
||||||
|
for redovi.Next() {
|
||||||
|
var stopa, osnovica, pdv float64
|
||||||
|
if err := redovi.Scan(&stopa, &osnovica, &pdv); err != nil {
|
||||||
|
return p, fmt.Errorf("ntech: ProdajaRepo.DnevniPrometMaloprodaje: scan: %w", err)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case stopa >= 19.9 && stopa <= 20.1:
|
||||||
|
p.OsnovicaOpsta += osnovica
|
||||||
|
p.PdvOpsta += pdv
|
||||||
|
case stopa >= 9.9 && stopa <= 10.1:
|
||||||
|
p.OsnovicaPosebna += osnovica
|
||||||
|
p.PdvPosebna += pdv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, redovi.Err()
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,10 +62,13 @@ func NapraviZahtev(
|
|||||||
if s.PdvStopa > 0 {
|
if s.PdvStopa > 0 {
|
||||||
stopa = s.PdvStopa
|
stopa = s.PdvStopa
|
||||||
}
|
}
|
||||||
// CenaPoKomadu je neto cena — konvertujemo u bruto
|
// CenaPoKomadu je neto cena — konvertujemo u bruto; popust umanjuje neto cenu pre PDV-a
|
||||||
netoCena := s.CenaBezPdv
|
netoCena := s.CenaBezPdv
|
||||||
if netoCena == 0 {
|
if netoCena == 0 {
|
||||||
netoCena = s.CenaPoKomadu // fallback: ako CenaBezPdv nije popunjena
|
netoCena = s.CenaPoKomadu
|
||||||
|
}
|
||||||
|
if s.PopustProcenat > 0 {
|
||||||
|
netoCena = math.Round(netoCena*(1-s.PopustProcenat/100)*100) / 100
|
||||||
}
|
}
|
||||||
brutoCena := BrutoCena(netoCena, stopa)
|
brutoCena := BrutoCena(netoCena, stopa)
|
||||||
brutoTotal := math.Round(brutoCena*float64(s.Kolicina)*100) / 100
|
brutoTotal := math.Round(brutoCena*float64(s.Kolicina)*100) / 100
|
||||||
@@ -80,11 +83,8 @@ func NapraviZahtev(
|
|||||||
ukupanIznos += brutoTotal
|
ukupanIznos += brutoTotal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ukupno za plaćanje — iz naloga (bruto)
|
// Ukupno za plaćanje je uvek suma bruto stavki (neto iz naloga bi bilo pogrešno)
|
||||||
ukupnoPlacanje := nalog.Ukupno
|
ukupnoPlacanje := ukupanIznos
|
||||||
if ukupnoPlacanje == 0 {
|
|
||||||
ukupnoPlacanje = ukupanIznos
|
|
||||||
}
|
|
||||||
|
|
||||||
nacinPlacanja := nalog.NacinPlacanja
|
nacinPlacanja := nalog.NacinPlacanja
|
||||||
if nacinPlacanja == "" {
|
if nacinPlacanja == "" {
|
||||||
@@ -115,3 +115,16 @@ func NapraviZahtev(
|
|||||||
|
|
||||||
return zahtev
|
return zahtev
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NapraviRefundZahtev gradi Refund zahtev za storno fiskalnog računa.
|
||||||
|
// referentBroj je PfrBroj originalnog fiskalnog računa (invoiceNumber iz PFR odgovora).
|
||||||
|
func NapraviRefundZahtev(
|
||||||
|
nalog model.ProdajniNalog,
|
||||||
|
stavke []model.StavkaProdajeSaArtiklom,
|
||||||
|
pib, jmbg, kasir, referentBroj string,
|
||||||
|
) InvoiceRequest {
|
||||||
|
zahtev := NapraviZahtev(nalog, stavke, pib, jmbg, kasir)
|
||||||
|
zahtev.InvoiceRequest.TransactionType = "Refund"
|
||||||
|
zahtev.InvoiceRequest.ReferentDocumentNumber = referentBroj
|
||||||
|
return zahtev
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ var saSidebar = []string{
|
|||||||
"nabavke", "nabavka_forma", "nabavka_detalji",
|
"nabavke", "nabavka_forma", "nabavka_detalji",
|
||||||
"podesavanja", "podesavanja_opste", "podesavanja_izgled", "podesavanja_sistem", "podesavanja_servis", "podesavanja_fiskalizacija",
|
"podesavanja", "podesavanja_opste", "podesavanja_izgled", "podesavanja_sistem", "podesavanja_servis", "podesavanja_fiskalizacija",
|
||||||
"pdv_stope",
|
"pdv_stope",
|
||||||
"pdv_kir", "pdv_kir_forma",
|
"pdv_kir", "pdv_kir_forma", "pdv_kir_dnevni_pazar",
|
||||||
"pdv_kpr", "pdv_kpr_forma",
|
"pdv_kpr", "pdv_kpr_forma",
|
||||||
"pdv_obracun",
|
"pdv_obracun",
|
||||||
"nivelacije",
|
"nivelacije",
|
||||||
@@ -125,6 +125,8 @@ var sablonskeFunkcije = template.FuncMap{
|
|||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
},
|
},
|
||||||
|
// zbirF64 vraća zbir dva float64 — za aritmetiku u šablonima
|
||||||
|
"zbirF64": func(a, b float64) float64 { return a + b },
|
||||||
}
|
}
|
||||||
|
|
||||||
// KreirajKes parsuje sve šablone iz fsys i vraća ih keširane u mapi
|
// KreirajKes parsuje sve šablone iz fsys i vraća ih keširane u mapi
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
"ntech/internal/middleware"
|
"ntech/internal/middleware"
|
||||||
"ntech/internal/model"
|
"ntech/internal/model"
|
||||||
|
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -245,3 +247,87 @@ func (h *Handler) KirBackfillProdaje(w http.ResponseWriter, r *http.Request) {
|
|||||||
middleware.SetFlash(w, r, h.DB, "uspeh", fmt.Sprintf("Backfill završen: %d novih KIR zapisa, %d preskočeno.", kreirano, preskoceno))
|
middleware.SetFlash(w, r, h.DB, "uspeh", fmt.Sprintf("Backfill završen: %d novih KIR zapisa, %d preskočeno.", kreirano, preskoceno))
|
||||||
http.Redirect(w, r, "/pdv/kir", http.StatusSeeOther)
|
http.Redirect(w, r, "/pdv/kir", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PodaciDnevniPazar su podaci za formu zbirnog KIR unosa (maloprodaja)
|
||||||
|
type PodaciDnevniPazar struct {
|
||||||
|
model.PodaciStranice
|
||||||
|
Datum string
|
||||||
|
Promet model.DnevniPrometKir
|
||||||
|
Ukupno float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// DnevniPazarKir prikazuje formu za potvrdu zbirnog KIR unosa za izabrani datum.
|
||||||
|
// Iznosi se računaju iz maloprodajnih naloga (bez klijenta) tog dana.
|
||||||
|
func (h *Handler) DnevniPazarKir(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if _, ok := h.zahtevajDozvolu(w, r, "pdv.dodaj"); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
datum := strings.TrimSpace(r.URL.Query().Get("datum"))
|
||||||
|
if datum == "" {
|
||||||
|
datum = time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
promet, err := h.ProdajaRepo.DnevniPrometMaloprodaje(r.Context(), datum)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Greška pri računanju dnevnog prometa", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ukupno := math.Round((promet.OsnovicaOpsta+promet.PdvOpsta+promet.OsnovicaPosebna+promet.PdvPosebna)*100) / 100
|
||||||
|
|
||||||
|
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||||
|
ps.Stranica = "pdv-kir"
|
||||||
|
ps.NaslovStranice = "Dnevni pazar — KIR unos"
|
||||||
|
h.renderujTemplate(w, "pdv_kir_dnevni_pazar", PodaciDnevniPazar{
|
||||||
|
PodaciStranice: ps,
|
||||||
|
Datum: datum,
|
||||||
|
Promet: promet,
|
||||||
|
Ukupno: ukupno,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SacuvajDnevniPazarKir prima POST i upisuje zbirni KIR zapis za dnevni pazar.
|
||||||
|
func (h *Handler) SacuvajDnevniPazarKir(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if _, ok := h.zahtevajDozvolu(w, r, "pdv.dodaj"); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "Greška pri čitanju forme", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
datum, e1 := time.Parse("2006-01-02", strings.TrimSpace(r.FormValue("datum")))
|
||||||
|
if e1 != nil {
|
||||||
|
middleware.SetFlash(w, r, h.DB, "greska", "Datum je obavezan.")
|
||||||
|
http.Redirect(w, r, "/pdv/kir/dnevni-pazar", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
z := model.PdvKir{
|
||||||
|
DatumPrometa: datum,
|
||||||
|
DatumKnjizenja: datum,
|
||||||
|
BrojDokumenta: fmt.Sprintf("FISK-%s", datum.Format("20060102")),
|
||||||
|
KupacNaziv: "Promet fizičkim licima",
|
||||||
|
OsnovicaOpsta: parsiraIznos(r.FormValue("osnovica_opsta")),
|
||||||
|
PdvOpsta: parsiraIznos(r.FormValue("pdv_opsta")),
|
||||||
|
OsnovicaPosebna: parsiraIznos(r.FormValue("osnovica_posebna")),
|
||||||
|
PdvPosebna: parsiraIznos(r.FormValue("pdv_posebna")),
|
||||||
|
Napomena: "Zbirni promet fizičkim licima — fiskalna kasa",
|
||||||
|
Izvor: "rucno",
|
||||||
|
}
|
||||||
|
z.Ukupno = math.Round((z.OsnovicaOpsta+z.PdvOpsta+z.OsnovicaPosebna+z.PdvPosebna)*100) / 100
|
||||||
|
|
||||||
|
if _, err := h.PdvKirRepo.Kreiraj(r.Context(), &z); err != nil {
|
||||||
|
slog.Error("greška pri čuvanju dnevnog pazara u KIR", "datum", datum, "error", err)
|
||||||
|
http.Error(w, "Greška pri čuvanju zapisa", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
middleware.SetFlash(w, r, h.DB, "uspeh", fmt.Sprintf("Dnevni pazar za %s upisan u KIR.", datum.Format("02.01.2006.")))
|
||||||
|
http.Redirect(w, r, "/pdv/kir", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|||||||
@@ -532,6 +532,59 @@ func (h *Handler) StornoProdaje(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err := h.PdvKirRepo.ObrisiPoIzvoru(r.Context(), "prodaja", id); err != nil {
|
if err := h.PdvKirRepo.ObrisiPoIzvoru(r.Context(), "prodaja", id); err != nil {
|
||||||
slog.Error("brisanje vezanog KIR zapisa nije uspelo", "prodaja_id", id, "error", err)
|
slog.Error("brisanje vezanog KIR zapisa nije uspelo", "prodaja_id", id, "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fiskalni refund — best-effort (storno ostaje validan i bez uspešnog refunda)
|
||||||
|
if h.modulUkljucen(r.Context(), config.ModulFiskalizacija) {
|
||||||
|
if fr, _ := h.FiskalRepo.DohvatiPoProdaji(r.Context(), id); fr != nil && !fr.Storniran {
|
||||||
|
if fk := h.fiskalKlijent(); fk != nil {
|
||||||
|
if nalogFisk, e := h.ProdajaRepo.DohvatiID(r.Context(), id); e == nil {
|
||||||
|
if stavkeFisk, e2 := h.ProdajaRepo.DohvatiStavke(r.Context(), id); e2 == nil {
|
||||||
|
kasirFisk, _ := sqlite.DohvatiPodesavanje(r.Context(), h.DB, "pfr_kasir")
|
||||||
|
if kasirFisk == "" {
|
||||||
|
kasirFisk = "NTech"
|
||||||
|
}
|
||||||
|
var pibFisk, jmbgFisk string
|
||||||
|
if nalogFisk.KlijentID != nil {
|
||||||
|
if kk, e3 := h.KlijentiRepo.DohvatiID(r.Context(), *nalogFisk.KlijentID); e3 == nil {
|
||||||
|
pibFisk, jmbgFisk = kk.PIB, kk.JMBG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zahtevFisk := fiskal.NapraviRefundZahtev(*nalogFisk, stavkeFisk, pibFisk, jmbgFisk, kasirFisk, fr.PfrBroj)
|
||||||
|
if odgFisk, errFisk := fk.IzdajRacun(r.Context(), zahtevFisk); errFisk != nil {
|
||||||
|
slog.Error("fiskalni refund nije uspeo", "prodaja_id", id, "error", errFisk)
|
||||||
|
} else {
|
||||||
|
poreskeJSON, _ := json.Marshal(odgFisk.TaxItems)
|
||||||
|
siroviJSON, _ := json.Marshal(odgFisk)
|
||||||
|
refundFr := &model.FiskalniRacun{
|
||||||
|
ProdajaID: id,
|
||||||
|
TipRacuna: "Normal",
|
||||||
|
TipTransakcije: "Refund",
|
||||||
|
PfrBroj: odgFisk.InvoiceNumber,
|
||||||
|
PfrVreme: odgFisk.SdcDateTime,
|
||||||
|
Brojac: odgFisk.InvoiceCounter,
|
||||||
|
EkstenzijaBrojaca: odgFisk.InvoiceCounterExtension,
|
||||||
|
UrlVerifikacija: odgFisk.VerificationURL,
|
||||||
|
QRKod: odgFisk.VerificationQRCode,
|
||||||
|
PoreskeStavke: string(poreskeJSON),
|
||||||
|
UkupnoZaNaplatu: odgFisk.TotalAmount,
|
||||||
|
UkupanPorez: odgFisk.TotalTax,
|
||||||
|
SiroviOdgovor: string(siroviJSON),
|
||||||
|
Potpisao: odgFisk.SignedBy,
|
||||||
|
Zatrazio: odgFisk.RequestedBy,
|
||||||
|
Poruka: odgFisk.Messages,
|
||||||
|
}
|
||||||
|
if _, errFisk = h.FiskalRepo.Kreiraj(r.Context(), refundFr); errFisk != nil {
|
||||||
|
slog.Error("čuvanje fiskalnog refunda nije uspelo", "prodaja_id", id, "error", errFisk)
|
||||||
|
} else {
|
||||||
|
_ = h.FiskalRepo.OznačiKaoStorniran(r.Context(), fr.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/prodaja/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther)
|
http.Redirect(w, r, "/prodaja/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,3 +47,13 @@ type StavkaProdajeSaArtiklom struct {
|
|||||||
JedinicaMere string
|
JedinicaMere string
|
||||||
KategorijaNaziv string
|
KategorijaNaziv string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DnevniPrometKir su zbrojeni iznosi maloprodaje za jedan dan, razvrstani po PDV stopi.
|
||||||
|
// Koristi se za kreiranje zbirnog KIR zapisa (promet fizičkim licima).
|
||||||
|
type DnevniPrometKir struct {
|
||||||
|
OsnovicaOpsta float64 // neto suma po stopi 20%
|
||||||
|
PdvOpsta float64
|
||||||
|
OsnovicaPosebna float64 // neto suma po stopi 10%
|
||||||
|
PdvPosebna float64
|
||||||
|
BrojNaloga int // ukupan broj prodajnih naloga taj dan
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
</form>
|
</form>
|
||||||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
||||||
{{if index .Dozvole "pdv.dodaj"}}
|
{{if index .Dozvole "pdv.dodaj"}}
|
||||||
|
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML"
|
||||||
|
href="/pdv/kir/dnevni-pazar" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;white-space:nowrap;">
|
||||||
|
Dnevni pazar
|
||||||
|
</a>
|
||||||
<form method="POST" action="/pdv/kir/backfill-prodaje" data-full-reload>
|
<form method="POST" action="/pdv/kir/backfill-prodaje" data-full-reload>
|
||||||
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
|
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
|
||||||
<button type="submit" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;white-space:nowrap;"
|
<button type="submit" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;white-space:nowrap;"
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
{{template "base" .}}
|
||||||
|
|
||||||
|
{{define "naslov"}}Dnevni pazar — KIR unos — NTech{{end}}
|
||||||
|
|
||||||
|
{{define "sadrzaj"}}
|
||||||
|
<div class="stranica-stack" style="width:100%;max-width:640px;">
|
||||||
|
|
||||||
|
<a href="/pdv/kir" 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"><polyline points="15 18 9 12 15 6"/></svg>
|
||||||
|
Nazad na KIR
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- izbor datuma -->
|
||||||
|
<div class="kartica animiraj" style="margin-bottom:16px;">
|
||||||
|
<div class="kartica-naslov">Dnevni pazar — zbirni KIR unos</div>
|
||||||
|
<div style="padding:16px;">
|
||||||
|
<p style="font-size:13px;color:var(--tekst-sporedni);margin:0 0 16px;">
|
||||||
|
Unosi jedan zbirni KIR zapis za promet fizičkim licima (maloprodaja) za izabrani datum.
|
||||||
|
Iznosi se računaju iz fiskalizovanih prodajnih naloga bez klijenta.
|
||||||
|
</p>
|
||||||
|
<form method="GET" action="/pdv/kir/dnevni-pazar" style="display:flex;gap:10px;align-items:flex-end;">
|
||||||
|
<div>
|
||||||
|
<label class="polje-labela">Datum</label>
|
||||||
|
<input type="date" name="datum" value="{{.Datum}}" style="padding:8px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
|
||||||
|
</div>
|
||||||
|
<button type="submit" style="padding:8px 16px;background:var(--sb-aktivan);color:var(--tekst-jak);border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;cursor:pointer;">Izračunaj</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- pregled iznosa -->
|
||||||
|
<div class="kartica animiraj" style="margin-bottom:16px;">
|
||||||
|
<div class="kartica-naslov">Iznosi za {{.Datum}} ({{.Promet.BrojNaloga}} naloga)</div>
|
||||||
|
<div style="padding:16px;">
|
||||||
|
<table style="width:100%;border-collapse:collapse;font-size:13px;">
|
||||||
|
<thead>
|
||||||
|
<tr style="color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);">
|
||||||
|
<th style="padding:8px 0;text-align:left;">Stopa</th>
|
||||||
|
<th style="padding:8px 0;text-align:right;">Osnovica (neto)</th>
|
||||||
|
<th style="padding:8px 0;text-align:right;">PDV</th>
|
||||||
|
<th style="padding:8px 0;text-align:right;">Bruto</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{if gt .Promet.OsnovicaOpsta 0.0}}
|
||||||
|
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||||
|
<td style="padding:8px 0;">Opšta 20%</td>
|
||||||
|
<td style="padding:8px 0;text-align:right;">{{dinari .Promet.OsnovicaOpsta}}</td>
|
||||||
|
<td style="padding:8px 0;text-align:right;">{{dinari .Promet.PdvOpsta}}</td>
|
||||||
|
<td style="padding:8px 0;text-align:right;font-weight:600;">{{dinari (zbirF64 .Promet.OsnovicaOpsta .Promet.PdvOpsta)}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{if gt .Promet.OsnovicaPosebna 0.0}}
|
||||||
|
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||||
|
<td style="padding:8px 0;">Posebna 10%</td>
|
||||||
|
<td style="padding:8px 0;text-align:right;">{{dinari .Promet.OsnovicaPosebna}}</td>
|
||||||
|
<td style="padding:8px 0;text-align:right;">{{dinari .Promet.PdvPosebna}}</td>
|
||||||
|
<td style="padding:8px 0;text-align:right;font-weight:600;">{{dinari (zbirF64 .Promet.OsnovicaPosebna .Promet.PdvPosebna)}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{if eq .Promet.BrojNaloga 0}}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" style="padding:16px 0;text-align:center;color:var(--tekst-sporedni);">Nema maloprodajnih naloga za ovaj datum.</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
{{if gt .Promet.BrojNaloga 0}}
|
||||||
|
<tfoot>
|
||||||
|
<tr style="font-weight:700;border-top:0.5px solid var(--ivica);">
|
||||||
|
<td style="padding:10px 0;">Ukupno</td>
|
||||||
|
<td style="padding:10px 0;text-align:right;">{{dinari (zbirF64 .Promet.OsnovicaOpsta .Promet.OsnovicaPosebna)}}</td>
|
||||||
|
<td style="padding:10px 0;text-align:right;">{{dinari (zbirF64 .Promet.PdvOpsta .Promet.PdvPosebna)}}</td>
|
||||||
|
<td style="padding:10px 0;text-align:right;">{{dinari .Ukupno}}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
{{end}}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if gt .Promet.BrojNaloga 0}}
|
||||||
|
<!-- forma za upis -->
|
||||||
|
<form method="POST" action="/pdv/kir/dnevni-pazar">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
|
||||||
|
<input type="hidden" name="datum" value="{{.Datum}}">
|
||||||
|
<input type="hidden" name="osnovica_opsta" value="{{printf "%.2f" .Promet.OsnovicaOpsta}}">
|
||||||
|
<input type="hidden" name="pdv_opsta" value="{{printf "%.2f" .Promet.PdvOpsta}}">
|
||||||
|
<input type="hidden" name="osnovica_posebna" value="{{printf "%.2f" .Promet.OsnovicaPosebna}}">
|
||||||
|
<input type="hidden" name="pdv_posebna" value="{{printf "%.2f" .Promet.PdvPosebna}}">
|
||||||
|
<div class="kartica animiraj">
|
||||||
|
<div style="padding:16px;display:flex;justify-content:flex-end;gap:12px;">
|
||||||
|
<a href="/pdv/kir" class="btn-sekundarno" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">Odustani</a>
|
||||||
|
<button type="submit" class="btn-primarno"
|
||||||
|
data-potvrda="Upisati dnevni pazar za {{.Datum}} u KIR?">
|
||||||
|
Upiši u KIR
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
Reference in New Issue
Block a user