Compare commits

..

10 Commits

Author SHA1 Message Date
Dasko fc2743e98d docs: README — knjigovodstvene funkcije (kalkulacija, nivelacija, PDV, uvoz) 2026-06-14 17:45:35 +02:00
Dasko e3f42e64a8 Merge feature/kw-podesavanja-kalkulacija: podstavka „Kalkulacija i PDV" + ispravka mobilnih stavki nabavke 2026-06-14 17:41:41 +02:00
Dasko ae7fd7b5e2 fix(nabavka): mobilni prikaz stavki — ukloni inline display:none
Inline display:none na .stavke-kartice nadjačavao je @media iz main.css, pa se na
telefonu nije video nijedan raspored stavki. Prikaz sada vodi samo CSS klasa.
2026-06-14 17:40:54 +02:00
Dasko 4d81c576cd feat(podesavanja): podstavka „Kalkulacija i PDV" — marža + PDV stope na jednom mestu
- PdvStope handler: dodata podrazumevana marža; stranica „podesavanja-kalkulacija-pdv",
  naslov „Kalkulacija i PDV", ruta /admin/podesavanja/kalkulacija-pdv
- pdv_stope.html: sekcija „Kalkulacija" (forma marže preko /podesavanja/sacuvaj) iznad šifarnika
- marža uklonjena iz stranice „Sistem"
- sidebar: podstavka „PDV stope" → „Kalkulacija i PDV"
2026-06-14 17:40:38 +02:00
Dasko 6444e19808 Merge feature/kw-kalkulacija-prodajna: dvosmerna marža↔prodajna + status PDV obveznika 2026-06-14 17:16:58 +02:00
Dasko 6ee9eefdd0 Merge feature/kw-uvoz: uvoz robe — KPR zastavica i PPPDV 006/106 2026-06-14 17:16:58 +02:00
Dasko 100915d453 feat(nabavka): dvosmerna marža↔prodajna + poštovanje statusa PDV obveznika
- ručni unos prodajne cene preračunava maržu (izracunajMarzu): obrnuta formula
  marža = (prodajna / (nabavna × (1+pdv/100)) − 1) × 100
- kalkulacija poštuje da li je firma PDV obveznik: ako nije, prodajna se ne
  uvećava za PDV (pdvStopa = 0); PodaciFormeNabavke.PdvObveznik → JS _ntechPdvObveznik
- proširena kolona „Marža %" da se vidi cela vrednost
2026-06-14 17:16:24 +02:00
Dasko 42c74a725a feat(pdv): uvoz robe — KPR zastavica i mapiranje u PPPDV 006/106
- migracija 048: kolona uvoz na pdv_kpr (0=domaća nabavka, 1=uvoz)
- model PdvKpr.Uvoz; MapirajPPPDV(kir, kprDomace, kprUvoz) rutira uvoz u 006/106,
  domaće u 008/108; test ažuriran + uvozni scenario
- repo: KPR Lista/DohvatiID/Kreiraj čitaju i pišu uvoz
- obračun: KPR se razdvaja na domaće/uvozne; obaveza ostaje na ukupnom KPR-u
- KPR forma: kvačica „Uvoz (JCI)"; lista: oznaka UVOZ uz broj dokumenta
2026-06-14 17:16:01 +02:00
Dasko c7470ebbc9 Merge feature/kw-kalkulacija-troskovi: Faza C — marža po kategoriji/artiklu + zavisni troškovi (§5.4) 2026-06-14 16:24:31 +02:00
Dasko 803e1f6341 feat(nabavka): UI zavisnih troškova — forma i prikaz u detaljima (Faza C, celina 2)
- forma nabavke: sekcija „Zavisni troškovi" (slobodne stavke naziv+iznos,
  dodavanje/uklanjanje) + izbor metoda raspodele (po vrednosti / po količini)
- JS: kalkNabavna (kalkulativna nabavna po stavci, prati server) i preracunajSve —
  promena troška/metoda/količine/cene preračunava prodajne svih stavki
- detalji nabavke: prikaz zavisnih troškova, metoda raspodele i ukupnog iznosa
- handler DetaljiNabavke dohvata troškove (DohvatiTroskove)
2026-06-14 16:23:11 +02:00
19 changed files with 289 additions and 70 deletions
+8 -1
View File
@@ -40,6 +40,12 @@ The goal is simple: everything the repair shop needs to track is located in one
- Service orders — intake, status bar, costs, receipt
- Sales orders — items, calculation, receipt with company and client details
- Procurement — records of purchases from suppliers
- Sales price calculation on procurement — markup (global, per category, per item), landed costs (customs, shipping...) allocated across items, two-way markup↔price computation; respects VAT-payer status
- Price revaluation (nivelacija) — sales price changes with an audit trail (old→new, reason, source, user)
- Company profile and modules — features toggle based on company type and VAT-payer status
- VAT records (KIR/KPR) — books of issued and received invoices, auto-filled from sales and procurement
- VAT calculation per period + mapping to the PP-PDV form; imports (customs declaration) tracked in fields 006/106
- VAT rate code list
- Clients and suppliers — contact database
- Reminders — records with deadlines
- Reports — revenue overview, inventory status
@@ -55,7 +61,8 @@ The goal is simple: everything the repair shop needs to track is located in one
### Planned
- Fiscalization and VAT calculation (specification in Project.md)
- Fiscalization (ESIR/PFR) — specification in Project.md
- KPO book and double-entry bookkeeping (optional, later phase)
- PostgreSQL support (for multi-user environments)
- WebAuthn / Passkey login (database schema is already prepared)
- Notifications (email / WhatsApp) — deferred to a later phase
+8 -1
View File
@@ -40,6 +40,12 @@ Cilj je jednostavan: sve što servis treba da prati nalazi se na jednom mestu, b
- Servisni nalozi — prijem, statusna traka, troškovi, priznanica
- Prodajni nalozi — stavke, obračun, priznanica sa podacima firme i klijenta
- Nabavke — evidencija nabavki od dobavljača
- Kalkulacija prodajne cene pri nabavci — marža (globalna, po kategoriji i po artiklu), zavisni troškovi (carina, prevoz...) sa raspodelom na stavke, dvosmerni izračun marža↔prodajna; poštuje status PDV obveznika
- Nivelacija — promena prodajne cene uz trag (istorija promena: stara→nova, razlog, izvor, korisnik)
- Profil firme i moduli — funkcije se uključuju prema tipu firme i statusu PDV obveznika
- PDV evidencija (KIR/KPR) — knjige izdatih i primljenih računa, automatsko punjenje iz prodaje i nabavke
- PDV obračun za period + mapiranje na obrazac PP-PDV; uvoz robe (JCI) se vodi u poljima 006/106
- Šifarnik PDV stopa
- Klijenti i dobavljači — baza kontakata
- Podsetnici — evidencija sa rokom
- Izveštaji — pregled prihoda, stanje magacina
@@ -55,7 +61,8 @@ Cilj je jednostavan: sve što servis treba da prati nalazi se na jednom mestu, b
### Planirano
- Fiskalizacija i PDV obračun (specifikacija u Project.md)
- Fiskalizacija (ESIR/PFR) — specifikacija u Project.md
- KPO knjiga i dvojno knjigovodstvo (opciono, kasnija faza)
- Podrška za PostgreSQL (za višekorisničko okruženje)
- WebAuthn / Passkey prijava (šema baze je pripremljena)
- Obaveštenja (e-pošta / WhatsApp) — odloženo za kasniju fazu
+1 -1
View File
@@ -247,7 +247,7 @@ func main() {
r.Get("/admin/podesavanja/opste", h.PodesavanjaOpste)
r.Get("/admin/podesavanja/izgled", h.PodesavanjaIzgled)
r.Get("/admin/podesavanja/sistem", h.PodesavanjaSistem)
r.Get("/admin/podesavanja/pdv-stope", h.PdvStope)
r.Get("/admin/podesavanja/kalkulacija-pdv", h.PdvStope)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/dodaj", h.DodajPdvStopu)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/{id}/izmeni", h.IzmeniPdvStopu)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/{id}/aktivnost", h.PromeniAktivnostPdvStope)
+12 -6
View File
@@ -171,7 +171,7 @@ func (r *PdvKprRepo) Lista(ctx context.Context, od, do time.Time) ([]model.PdvKp
dobavljac_naziv, COALESCE(dobavljac_pib, ''), COALESCE(dobavljac_mesto, ''),
osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna,
pdv_bez_odbitka, osloboden_nabavka, ukupno,
COALESCE(napomena, ''), izvor, izvor_id, datum_unosa
COALESCE(napomena, ''), izvor, izvor_id, uvoz, datum_unosa
FROM pdv_kpr WHERE 1=1`
args := []any{}
if !od.IsZero() {
@@ -211,7 +211,7 @@ func (r *PdvKprRepo) DohvatiID(ctx context.Context, id int64) (*model.PdvKpr, er
dobavljac_naziv, COALESCE(dobavljac_pib, ''), COALESCE(dobavljac_mesto, ''),
osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna,
pdv_bez_odbitka, osloboden_nabavka, ukupno,
COALESCE(napomena, ''), izvor, izvor_id, datum_unosa
COALESCE(napomena, ''), izvor, izvor_id, uvoz, datum_unosa
FROM pdv_kpr WHERE id = ?`, id)
k, err := skenirajKpr(red.Scan)
if err != nil {
@@ -225,12 +225,13 @@ func skenirajKpr(scan func(...any) error) (model.PdvKpr, error) {
var k model.PdvKpr
var datumPlacanja sql.NullTime
var izvorID sql.NullInt64
var uvoz int64
if err := scan(
&k.ID, &k.DatumPrometa, &k.DatumKnjizenja, &datumPlacanja, &k.BrojDokumenta,
&k.DobavljacNaziv, &k.DobavljacPib, &k.DobavljacMesto,
&k.OsnovicaOpsta, &k.PdvOpsta, &k.OsnovicaPosebna, &k.PdvPosebna,
&k.PdvBezOdbitka, &k.OslobodenNabavka, &k.Ukupno,
&k.Napomena, &k.Izvor, &izvorID, &k.DatumUnosa,
&k.Napomena, &k.Izvor, &izvorID, &uvoz, &k.DatumUnosa,
); err != nil {
return model.PdvKpr{}, err
}
@@ -240,6 +241,7 @@ func skenirajKpr(scan func(...any) error) (model.PdvKpr, error) {
if izvorID.Valid {
k.IzvorID = &izvorID.Int64
}
k.Uvoz = uvoz != 0
return k, nil
}
@@ -249,18 +251,22 @@ func (r *PdvKprRepo) Kreiraj(ctx context.Context, k *model.PdvKpr) (int64, error
if k.DatumPlacanja != nil {
datumPlacanja = *k.DatumPlacanja
}
uvoz := 0
if k.Uvoz {
uvoz = 1
}
rez, err := r.db.ExecContext(ctx, `
INSERT INTO pdv_kpr (
datum_prometa, datum_knjizenja, datum_placanja, broj_dokumenta,
dobavljac_naziv, dobavljac_pib, dobavljac_mesto,
osnovica_opsta, pdv_opsta, osnovica_posebna, pdv_posebna,
pdv_bez_odbitka, osloboden_nabavka, ukupno, napomena, izvor, izvor_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
pdv_bez_odbitka, osloboden_nabavka, ukupno, napomena, izvor, izvor_id, uvoz
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
k.DatumPrometa, k.DatumKnjizenja, datumPlacanja, k.BrojDokumenta,
k.DobavljacNaziv, k.DobavljacPib, k.DobavljacMesto,
k.OsnovicaOpsta, k.PdvOpsta, k.OsnovicaPosebna, k.PdvPosebna,
k.PdvBezOdbitka, k.OslobodenNabavka, k.Ukupno, k.Napomena,
izvorIliRucno(k.Izvor), izvorIDArg(k.IzvorID))
izvorIliRucno(k.Izvor), izvorIDArg(k.IzvorID), uvoz)
if err != nil {
return 0, fmt.Errorf("ntech: PdvKprRepo.Kreiraj: %w", err)
}
+17
View File
@@ -31,6 +31,7 @@ type PodaciFormeNabavke struct {
Dobavljaci []model.Dobavljac
Kategorije []model.Kategorija // za dropdown u modalu novog artikla
Marza string // podrazumevana marža (%) za kalkulaciju
PdvObveznik bool // da li firma obračunava PDV (utiče na prodajnu cenu u kalkulaciji)
Greska string
}
@@ -39,6 +40,8 @@ type PodaciDetaljiNabavke struct {
model.PodaciStranice
Nabavka model.Nabavka
Stavke []model.StavkaSaArtiklom
Troskovi []model.NabavkaTrosak
UkupanTrosak float64
DobavljacNaziv string
}
@@ -125,6 +128,7 @@ func (h *Handler) NovaNabavka(w http.ResponseWriter, r *http.Request) {
Dobavljaci: dobavljaci,
Kategorije: kategorije,
Marza: vrednostIliDefault(podesavanja, "kalkulacija_marza", "20"),
PdvObveznik: h.modulUkljucen(r.Context(), "pdv"),
})
}
@@ -155,6 +159,7 @@ func (h *Handler) SacuvajNabavku(w http.ResponseWriter, r *http.Request) {
Dobavljaci: dobavljaci,
Kategorije: kategorije,
Marza: vrednostIliDefault(podesavanja, "kalkulacija_marza", "20"),
PdvObveznik: h.modulUkljucen(r.Context(), "pdv"),
Greska: greska,
})
return
@@ -259,6 +264,12 @@ func (h *Handler) DetaljiNabavke(w http.ResponseWriter, r *http.Request) {
return
}
troskovi, err := h.NabavkeRepo.DohvatiTroskove(r.Context(), id)
if err != nil {
http.Error(w, "Greška pri učitavanju troškova", http.StatusInternalServerError)
return
}
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
if err != nil {
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
@@ -277,10 +288,16 @@ func (h *Handler) DetaljiNabavke(w http.ResponseWriter, r *http.Request) {
ps := h.popuniPodaciStranice(r, podesavanja)
ps.Stranica = "nabavke"
ps.NaslovStranice = "Detalji nabavke"
var ukupanTrosak float64
for _, t := range troskovi {
ukupanTrosak += t.Iznos
}
podaci := PodaciDetaljiNabavke{
PodaciStranice: ps,
Nabavka: *nabavka,
Stavke: stavke,
Troskovi: troskovi,
UkupanTrosak: ukupanTrosak,
DobavljacNaziv: dobavljacNaziv,
}
+1
View File
@@ -128,6 +128,7 @@ func (h *Handler) SacuvajPdvKpr(w http.ResponseWriter, r *http.Request) {
PdvBezOdbitka: parsiraIznos(r.FormValue("pdv_bez_odbitka")),
OslobodenNabavka: parsiraIznos(r.FormValue("osloboden_nabavka")),
Napomena: strings.TrimSpace(r.FormValue("napomena")),
Uvoz: r.FormValue("uvoz") == "1",
}
// datum plaćanja je opcionalan
if dp := parsiraDatumOpcionalno(r.FormValue("datum_placanja")); !dp.IsZero() {
+14 -1
View File
@@ -59,6 +59,19 @@ func (h *Handler) PdvObracunStranica(w http.ResponseWriter, r *http.Request) {
kirSume := model.SumirajKir(kirZapisi)
kprSume := model.SumirajKpr(kprZapisi)
// razdvajamo KPR na domaće i uvozne — uvoz se u PPPDV mapira u polja 006/106,
// domaća nabavka u 008/108; obračun obaveze ostaje na ukupnom KPR-u (uvozni PDV je odbitni)
var kprDomaci, kprUvozni []model.PdvKpr
for _, z := range kprZapisi {
if z.Uvoz {
kprUvozni = append(kprUvozni, z)
} else {
kprDomaci = append(kprDomaci, z)
}
}
kprDomaceSume := model.SumirajKpr(kprDomaci)
kprUvozSume := model.SumirajKpr(kprUvozni)
ps := h.popuniPodaciStranice(r, podesavanja)
ps.Stranica = "pdv-obracun"
ps.NaslovStranice = "PDV obračun"
@@ -69,6 +82,6 @@ func (h *Handler) PdvObracunStranica(w http.ResponseWriter, r *http.Request) {
KirSume: kirSume,
KprSume: kprSume,
Obracun: model.ObracunajPdv(kirSume, kprSume),
PPPDV: model.MapirajPPPDV(kirSume, kprSume),
PPPDV: model.MapirajPPPDV(kirSume, kprDomaceSume, kprUvozSume),
})
}
+16 -10
View File
@@ -19,13 +19,15 @@ var validneOznakeStope = map[string]bool{
"oslobodjeno": true,
}
// PodaciPdvStope su podaci za stranicu šifarnika PDV stopa
// PodaciPdvStope su podaci za stranicu „Kalkulacija i PDV" (marža + šifarnik PDV stopa)
type PodaciPdvStope struct {
model.PodaciStranice
Stope []model.PdvStopa
KalkulacijaMarza string // podrazumevana marža (%) za kalkulaciju
}
// PdvStope renderuje šifarnik PDV stopa (sve stope, uključujući arhivirane)
// PdvStope renderuje stranicu „Kalkulacija i PDV": podešavanje podrazumevane marže
// i šifarnik PDV stopa (sve stope, uključujući arhivirane)
func (h *Handler) PdvStope(w http.ResponseWriter, r *http.Request) {
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.pregled"); !ok {
return
@@ -42,9 +44,13 @@ func (h *Handler) PdvStope(w http.ResponseWriter, r *http.Request) {
}
ps := h.popuniPodaciStranice(r, podesavanja)
ps.Stranica = "podesavanja-pdv-stope"
ps.NaslovStranice = "PDV stope"
h.renderujTemplate(w, "pdv_stope", PodaciPdvStope{PodaciStranice: ps, Stope: stope})
ps.Stranica = "podesavanja-kalkulacija-pdv"
ps.NaslovStranice = "Kalkulacija i PDV"
h.renderujTemplate(w, "pdv_stope", PodaciPdvStope{
PodaciStranice: ps,
Stope: stope,
KalkulacijaMarza: vrednostIliDefault(podesavanja, "kalkulacija_marza", "20"),
})
}
// parsePdvStopuForma čita i proverava polja forme; vraća popunjenu stopu i poruku o grešci
@@ -86,7 +92,7 @@ func (h *Handler) DodajPdvStopu(w http.ResponseWriter, r *http.Request) {
stopa, greska := parsePdvStopuForma(r)
if greska != "" {
middleware.SetFlash(w, r, h.DB, "greska", greska)
http.Redirect(w, r, "/admin/podesavanja/pdv-stope", http.StatusSeeOther)
http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv", http.StatusSeeOther)
return
}
if _, err := h.PdvStopeRepo.Kreiraj(r.Context(), &stopa); err != nil {
@@ -94,7 +100,7 @@ func (h *Handler) DodajPdvStopu(w http.ResponseWriter, r *http.Request) {
return
}
middleware.SetFlash(w, r, h.DB, "uspeh", "PDV stopa je dodata.")
http.Redirect(w, r, "/admin/podesavanja/pdv-stope", http.StatusSeeOther)
http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv", http.StatusSeeOther)
}
// IzmeniPdvStopu prima POST i menja postojeću stopu
@@ -114,7 +120,7 @@ func (h *Handler) IzmeniPdvStopu(w http.ResponseWriter, r *http.Request) {
stopa, greska := parsePdvStopuForma(r)
if greska != "" {
middleware.SetFlash(w, r, h.DB, "greska", greska)
http.Redirect(w, r, "/admin/podesavanja/pdv-stope", http.StatusSeeOther)
http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv", http.StatusSeeOther)
return
}
stopa.ID = id
@@ -123,7 +129,7 @@ func (h *Handler) IzmeniPdvStopu(w http.ResponseWriter, r *http.Request) {
return
}
middleware.SetFlash(w, r, h.DB, "uspeh", "PDV stopa je izmenjena.")
http.Redirect(w, r, "/admin/podesavanja/pdv-stope", http.StatusSeeOther)
http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv", http.StatusSeeOther)
}
// PromeniAktivnostPdvStope arhivira ili vraća stopu u upotrebu (toggle, bez brisanja)
@@ -150,5 +156,5 @@ func (h *Handler) PromeniAktivnostPdvStope(w http.ResponseWriter, r *http.Reques
poruka = "PDV stopa je vraćena u upotrebu."
}
middleware.SetFlash(w, r, h.DB, "uspeh", poruka)
http.Redirect(w, r, "/admin/podesavanja/pdv-stope", http.StatusSeeOther)
http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv", http.StatusSeeOther)
}
+9 -5
View File
@@ -138,6 +138,7 @@ type PdvKpr struct {
Napomena string
Izvor string // "rucno" | "prodaja" | "nabavka"
IzvorID *int64 // id izvorne nabavke (nil za ručni unos)
Uvoz bool // true = uvoz (JCI) → PPPDV 006/106; false = domaća nabavka → 008/108
DatumUnosa time.Time
}
@@ -287,9 +288,10 @@ func (p PPPDV) Polje110Apsolutno() int64 {
}
// MapirajPPPDV preslikava zbirove KIR i KPR na polja obrasca PPPDV (cele dinare).
// ⚠ Uvoz (006/106) i PDV nadoknada poljoprivredniku (007/107) se ne prate zasebno
// pa ostaju 0 — sav odbitni PDV iz KPR pada u polje 008/108.
func MapirajPPPDV(kir PdvKirSume, kpr PdvKprSume) PPPDV {
// KPR je razdvojen: uvoz (JCI) ide u polja 006/106 (prethodni porez pri uvozu),
// domaća nabavka u 008/108 (ostali prethodni porez).
// ⚠ PDV nadoknada poljoprivredniku (007/107) se i dalje ne prati zasebno (ostaje 0).
func MapirajPPPDV(kir PdvKirSume, kprDomace, kprUvoz PdvKprSume) PPPDV {
uDinare := func(v float64) int64 { return int64(math.Round(v)) }
p := PPPDV{
Polje001: uDinare(kir.OslobodenSaPravom),
@@ -298,8 +300,10 @@ func MapirajPPPDV(kir PdvKirSume, kpr PdvKprSume) PPPDV {
Polje103: uDinare(kir.PdvOpsta),
Polje004: uDinare(kir.OsnovicaPosebna),
Polje104: uDinare(kir.PdvPosebna),
Polje008: uDinare(kpr.OsnovicaOpsta + kpr.OsnovicaPosebna),
Polje108: uDinare(kpr.PdvOpsta + kpr.PdvPosebna),
Polje006: uDinare(kprUvoz.OsnovicaOpsta + kprUvoz.OsnovicaPosebna),
Polje106: uDinare(kprUvoz.PdvOpsta + kprUvoz.PdvPosebna),
Polje008: uDinare(kprDomace.OsnovicaOpsta + kprDomace.OsnovicaPosebna),
Polje108: uDinare(kprDomace.PdvOpsta + kprDomace.PdvPosebna),
}
// zbirovi se računaju iz zaokruženih polja da kolone na obrascu uvek zbiraju
p.Polje005 = p.Polje001 + p.Polje002 + p.Polje003 + p.Polje004
+20 -3
View File
@@ -108,7 +108,7 @@ func TestMapirajPPPDV(t *testing.T) {
PdvBezOdbitka: 25, // ne sme da uđe nigde u PPPDV
}
p := MapirajPPPDV(kir, kpr)
p := MapirajPPPDV(kir, kpr, PdvKprSume{})
if p.Polje001 != 300 || p.Polje002 != 101 || p.Polje003 != 1000 || p.Polje103 != 200 {
t.Errorf("I deo (001/002/003/103) = %d/%d/%d/%d", p.Polje001, p.Polje002, p.Polje003, p.Polje103)
@@ -120,7 +120,7 @@ func TestMapirajPPPDV(t *testing.T) {
if p.Polje005 != 1902 || p.Polje105 != 250 {
t.Errorf("zbir 005/105 = %d/%d, očekivano 1902/250", p.Polje005, p.Polje105)
}
// uvoz i poljoprivrednik se ne prate
// u ovom scenariju nema uvoza (prazan kprUvoz) ni poljoprivrednika → 006/106/007/107 = 0
if p.Polje006 != 0 || p.Polje106 != 0 || p.Polje007 != 0 || p.Polje107 != 0 {
t.Errorf("006/106/007/107 moraju biti 0")
}
@@ -137,8 +137,25 @@ func TestMapirajPPPDV(t *testing.T) {
}
// scenario povraćaja: izlazni < odbitni → 110 negativan, Povracaj=true
p2 := MapirajPPPDV(PdvKirSume{PdvOpsta: 30}, PdvKprSume{PdvOpsta: 90})
p2 := MapirajPPPDV(PdvKirSume{PdvOpsta: 30}, PdvKprSume{PdvOpsta: 90}, PdvKprSume{})
if p2.Polje110 != -60 || !p2.Povracaj || p2.Polje110Apsolutno() != 60 {
t.Errorf("povraćaj: 110=%d Povracaj=%v abs=%d, očekivano -60/true/60", p2.Polje110, p2.Povracaj, p2.Polje110Apsolutno())
}
// scenario uvoza: uvozni KPR ide u 006/106, domaći u 008/108
p3 := MapirajPPPDV(
PdvKirSume{},
PdvKprSume{OsnovicaOpsta: 1000, PdvOpsta: 200}, // domaće → 008/108
PdvKprSume{OsnovicaOpsta: 500, PdvOpsta: 100, OsnovicaPosebna: 50, PdvPosebna: 5}, // uvoz → 006/106
)
if p3.Polje006 != 550 || p3.Polje106 != 105 {
t.Errorf("uvoz 006/106 = %d/%d, očekivano 550/105", p3.Polje006, p3.Polje106)
}
if p3.Polje008 != 1000 || p3.Polje108 != 200 {
t.Errorf("domaće 008/108 = %d/%d, očekivano 1000/200", p3.Polje008, p3.Polje108)
}
// 009 = 006+007+008 = 550+0+1000 = 1550; 109 = 106+107+108 = 105+0+200 = 305
if p3.Polje009 != 1550 || p3.Polje109 != 305 {
t.Errorf("zbir prethodnog 009/109 = %d/%d, očekivano 1550/305", p3.Polje009, p3.Polje109)
}
}
+6
View File
@@ -0,0 +1,6 @@
-- Zastavica „uvoz" na zapisima knjige primljenih računa (KPR).
-- Uvoz se unosi RUČNO sa JCI (osnovica i PDV iz carinskog dokumenta drže postojeće
-- kolone osnovica_opsta / pdv_opsta). Zastavica služi da se uvozni PDV u PPPDV obrascu
-- rutira u polja 006/106 (prethodni porez pri uvozu) umesto 008/108 (ostali prethodni).
-- 0 = domaća nabavka (podrazumevano), 1 = uvoz.
ALTER TABLE pdv_kpr ADD COLUMN uvoz INTEGER NOT NULL DEFAULT 0;
+55 -4
View File
@@ -189,6 +189,9 @@ document.addEventListener('alpine:init', () => {
stavke: [{artikal_id: '', kolicina: 1, cena: 0, marza: 0, prodajna: 0}],
artikliOpcije: [],
marzaDefault: 0,
troskovi: [], // zavisni troškovi {naziv, iznos}
metodRaspodele: 'vrednost', // 'vrednost' ili 'kolicina'
pdvObveznik: true, // da li firma obračunava PDV (utiče na prodajnu cenu)
isMobile: false,
modal: false,
modalUcitavanje: false,
@@ -214,6 +217,7 @@ document.addEventListener('alpine:init', () => {
init() {
this.artikliOpcije = window._ntechArtikli || []
this.marzaDefault = parseFloat(window._ntechMarza) || 0
this.pdvObveznik = window._ntechPdvObveznik === true
this.stavke.forEach(s => { s.marza = this.marzaDefault })
this.isMobile = window.matchMedia('(max-width: 768px)').matches
window.matchMedia('(max-width: 768px)').addEventListener('change', e => {
@@ -222,9 +226,12 @@ document.addEventListener('alpine:init', () => {
},
dodajStavku() {
this.stavke.push({artikal_id: '', kolicina: 1, cena: 0, marza: this.marzaDefault, prodajna: 0})
this.preracunajSve()
},
// PDV stopa izabranog artikla (iz JSON liste) — za obračun prodajne cene
// PDV stopa izabranog artikla (iz JSON liste) — za obračun prodajne cene.
// Ako firma nije PDV obveznik, PDV se ne dodaje na prodajnu cenu (stopa = 0).
pdvStopa(artikalId) {
if (!this.pdvObveznik) return 0
const a = this.artikliOpcije.find(x => String(x.id) === String(artikalId))
return a ? (parseFloat(a.pdv_stopa) || 0) : 0
},
@@ -238,15 +245,59 @@ document.addEventListener('alpine:init', () => {
}
this.izracunajProdajnu(s)
},
// prodajna (sa PDV) = nabavna × (1 + marža/100) × (1 + pdvStopa/100), zaokruženo na 2 decimale
izracunajProdajnu(s) {
// ukupan zavisni trošak nabavke
ukupanTrosak() {
return this.troskovi.reduce((z, t) => z + (parseFloat(t.iznos) || 0), 0)
},
// osnovica raspodele po izabranom metodu (zbir po svim stavkama)
osnovicaRaspodele() {
return this.stavke.reduce((z, s) => {
const kol = parseFloat(s.kolicina) || 0
return z + (this.metodRaspodele === 'kolicina' ? kol : kol * (parseFloat(s.cena) || 0))
}, 0)
},
// kalkulativna nabavna cena po komadu (fakturna + raspodeljeni trošak) — isto kao server
kalkNabavna(s) {
const cena = parseFloat(s.cena) || 0
const kol = parseFloat(s.kolicina) || 0
const trosak = this.ukupanTrosak()
const osn = this.osnovicaRaspodele()
if (trosak <= 0 || osn <= 0 || kol <= 0) return cena
const baza = this.metodRaspodele === 'kolicina' ? kol : kol * cena
const trosakPoKomadu = (trosak * (baza / osn)) / kol
return Math.round((cena + trosakPoKomadu) * 100) / 100
},
// prodajna (sa PDV) = kalkulativna nabavna × (1 + marža/100) × (1 + pdvStopa/100)
izracunajProdajnu(s) {
const nabavna = this.kalkNabavna(s)
const marza = parseFloat(s.marza) || 0
const pdv = this.pdvStopa(s.artikal_id)
s.prodajna = Math.round(cena * (1 + marza / 100) * (1 + pdv / 100) * 100) / 100
s.prodajna = Math.round(nabavna * (1 + marza / 100) * (1 + pdv / 100) * 100) / 100
},
// obrnuti smer: iz ručno unete prodajne cene izvedi maržu (%)
// marža = (prodajna / (nabavna × (1 + pdv/100)) 1) × 100
izracunajMarzu(s) {
const nabavna = this.kalkNabavna(s)
const pdv = this.pdvStopa(s.artikal_id)
const osnovica = nabavna * (1 + pdv / 100)
if (osnovica <= 0) return // bez nabavne cene marža se ne može izvesti
const prodajna = parseFloat(s.prodajna) || 0
s.marza = Math.round(((prodajna / osnovica) - 1) * 100 * 100) / 100
},
// raspodela zavisi od svih stavki — promena troška/metoda/količine/cene preračunava sve
preracunajSve() {
this.stavke.forEach(s => this.izracunajProdajnu(s))
},
dodajTrosak() {
this.troskovi.push({naziv: '', iznos: 0})
},
ukloniTrosak(i) {
this.troskovi.splice(i, 1)
this.preracunajSve()
},
ukloniStavku(i) {
if (this.stavke.length > 1) this.stavke.splice(i, 1)
this.preracunajSve()
},
ukupnoStavke(s) {
return (parseFloat(s.kolicina) * parseFloat(s.cena) || 0).toFixed(2)
+6 -6
View File
@@ -195,19 +195,19 @@
{{if index .Dozvole "podesavanja.pregled"}}
<div>
<button type="button" data-podmeni-dugme
class="nav-stavka {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-pdv-stope") (eq .Stranica "dozvole")}}aktivan{{end}}"
class="nav-stavka {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "dozvole")}}aktivan{{end}}"
style="width:100%;background:none;border:none;cursor:pointer;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06-.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<span>Podešavanja</span>
<span class="nav-strelica">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
style="transition:transform 0.2s;transform:{{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-pdv-stope") (eq .Stranica "dozvole")}}rotate(180deg){{else}}rotate(0deg){{end}}">
style="transition:transform 0.2s;transform:{{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "dozvole")}}rotate(180deg){{else}}rotate(0deg){{end}}">
<polyline points="6 9 12 15 18 9"/>
</svg>
</span>
<span class="nav-tooltip">Podešavanja</span>
</button>
<div class="nav-podmeni {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-pdv-stope") (eq .Stranica "dozvole")}}otvoren{{end}}">
<div class="nav-podmeni {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "dozvole")}}otvoren{{end}}">
<a href="/admin/podesavanja/opste" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-opste"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V7a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14"/><path d="M9 21v-5h6v5"/><path d="M9 9h.01M15 9h.01M9 13h.01M15 13h.01"/></svg>
<span>Opšte</span>
@@ -223,10 +223,10 @@
<span>Sistem</span>
<span class="nav-tooltip">Sistem</span>
</a>
<a href="/admin/podesavanja/pdv-stope" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-pdv-stope"}}aktivan{{end}}">
<a href="/admin/podesavanja/kalkulacija-pdv" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-kalkulacija-pdv"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="5" x2="5" y2="19"/><circle cx="6.5" cy="6.5" r="2.5"/><circle cx="17.5" cy="17.5" r="2.5"/></svg>
<span>PDV stope</span>
<span class="nav-tooltip">PDV stope</span>
<span>Kalkulacija i PDV</span>
<span class="nav-tooltip">Kalkulacija i PDV</span>
</a>
{{if or (eq .KorisnikUloga "superadmin") (eq .KorisnikUloga "admin")}}
<a href="/admin/dozvole" class="nav-stavka nav-podstavka {{if eq .Stranica "dozvole"}}aktivan{{end}}">
@@ -122,6 +122,30 @@
</div>
</div>
<!-- zavisni troškovi -->
{{if .Troskovi}}
<div class="kartica detalji-kartica animiraj" style="padding:0;overflow:hidden;">
<div style="padding:14px 16px;border-bottom:0.5px solid var(--ivica);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;">
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Zavisni troškovi</span>
<span class="pomocni-tekst">
Raspodela: {{if eq .Nabavka.MetodRaspodele "kolicina"}}po količini{{else}}po vrednosti stavke{{end}}
</span>
</div>
<div style="padding:8px 16px;">
{{range .Troskovi}}
<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:0.5px solid var(--ivica);">
<span style="font-size:14px;color:var(--tekst-glavni);">{{.Naziv}}</span>
<span style="font-size:14px;color:var(--tekst-sporedni);">{{printf "%.2f" .Iznos}} din</span>
</div>
{{end}}
<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;">
<span style="font-size:13px;font-weight:500;color:var(--tekst-sporedni);">Ukupno troškovi:</span>
<span style="font-size:15px;font-weight:600;color:var(--tekst-glavni);">{{printf "%.2f" .UkupanTrosak}} din</span>
</div>
</div>
</div>
{{end}}
<!-- zona za brisanje -->
<div class="kartica detalji-kartica animiraj" style="border-color:#dc262633;">
<div style="display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;">
+54 -8
View File
@@ -16,7 +16,7 @@
{{define "sadrzaj"}}
<!-- lista artikala kao JSON — bezbedno serijalizovana na serveru -->
<script>var _ntechArtikli = {{.ArtikliJSON}}; var _ntechMarza = {{.Marza}};</script>
<script>var _ntechArtikli = {{.ArtikliJSON}}; var _ntechMarza = {{.Marza}}; var _ntechPdvObveznik = {{.PdvObveznik}};</script>
<div style="width:100%;" x-data="nabavkaForma">
@@ -87,7 +87,7 @@
<th style="padding:8px 10px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Artikal</th>
<th style="padding:8px 10px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:90px;">Količina</th>
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:130px;">Cena/kom (din)</th>
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:90px;">Marža %</th>
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:120px;">Marža %</th>
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:130px;">Prodajna/kom (din)</th>
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:110px;">Ukupno</th>
<th style="width:40px;"></th>
@@ -107,11 +107,12 @@
</td>
<td style="padding:8px 10px;">
<input type="number" :name="'kolicina[]'" x-model="stavka.kolicina"
@input="preracunajSve()"
min="1" :disabled="isMobile" style="width:100%;text-align:center;">
</td>
<td style="padding:8px 10px;">
<input type="number" :name="'cena_po_komadu[]'" x-model="stavka.cena"
@input="izracunajProdajnu(stavka)"
@input="preracunajSve()"
min="0" step="0.01" :disabled="isMobile" style="width:100%;text-align:right;">
</td>
<td style="padding:8px 10px;">
@@ -121,6 +122,7 @@
</td>
<td style="padding:8px 10px;">
<input type="number" :name="'prodajna[]'" x-model="stavka.prodajna"
@input="izracunajMarzu(stavka)"
min="0" step="0.01" :disabled="isMobile" style="width:100%;text-align:right;">
</td>
<td style="padding:8px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
@@ -149,8 +151,8 @@
</table>
</div>
<!-- mobilne kartice stavki -->
<div class="stavke-kartice" style="display:none;flex-direction:column;gap:10px;">
<!-- mobilne kartice stavki (prikaz/skrivanje vodi .stavke-kartice u main.css) -->
<div class="stavke-kartice">
<template x-for="(stavka, i) in stavke" :key="i">
<div style="border:0.5px solid var(--ivica);border-radius:8px;padding:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
@@ -176,11 +178,11 @@
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<div>
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Količina</label>
<input type="number" :name="'kolicina[]'" x-model="stavka.kolicina" min="1" :disabled="!isMobile" style="width:100%;">
<input type="number" :name="'kolicina[]'" x-model="stavka.kolicina" @input="preracunajSve()" min="1" :disabled="!isMobile" style="width:100%;">
</div>
<div>
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Cena/kom (din)</label>
<input type="number" :name="'cena_po_komadu[]'" x-model="stavka.cena" @input="izracunajProdajnu(stavka)" min="0" step="0.01" :disabled="!isMobile" style="width:100%;">
<input type="number" :name="'cena_po_komadu[]'" x-model="stavka.cena" @input="preracunajSve()" min="0" step="0.01" :disabled="!isMobile" style="width:100%;">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
@@ -190,7 +192,7 @@
</div>
<div>
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Prodajna/kom (din)</label>
<input type="number" :name="'prodajna[]'" x-model="stavka.prodajna" min="0" step="0.01" :disabled="!isMobile" style="width:100%;">
<input type="number" :name="'prodajna[]'" x-model="stavka.prodajna" @input="izracunajMarzu(stavka)" min="0" step="0.01" :disabled="!isMobile" style="width:100%;">
</div>
</div>
<div style="text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
@@ -205,6 +207,50 @@
</div>
</div>
<!-- zavisni troškovi -->
<div class="kartica forma-kartica animiraj" style="margin-bottom:16px;">
<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:8px;">
<span style="font-size:16px;font-weight:500;color:var(--tekst-glavni);">Zavisni troškovi</span>
<button type="button" @click="dodajTrosak()" class="btn-primarno" style="font-size:13px;padding:6px 14px;">
+ Dodaj trošak
</button>
</div>
<template x-if="troskovi.length > 0">
<div class="kolona" style="gap:10px;">
<!-- metod raspodele -->
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
<label class="polje-labela" style="margin:0;">Raspodela na stavke:</label>
<select name="metod_raspodele" x-model="metodRaspodele" @change="preracunajSve()" style="max-width:240px;">
<option value="vrednost">po vrednosti stavke</option>
<option value="kolicina">po količini</option>
</select>
</div>
<!-- redovi troškova -->
<template x-for="(t, i) in troskovi" :key="i">
<div style="display:flex;gap:8px;align-items:center;">
<input type="text" :name="'trosak_naziv[]'" x-model="t.naziv"
placeholder="npr. Prevoz, Carina, Špedicija" style="flex:1;">
<input type="number" :name="'trosak_iznos[]'" x-model="t.iznos"
@input="preracunajSve()" min="0" step="0.01"
placeholder="iznos (din)" style="width:140px;text-align:right;">
<button type="button" @click="ukloniTrosak(i)"
style="background:none;border:none;cursor:pointer;color:#dc2626;font-size:18px;line-height:1;padding:2px 6px;border-radius:4px;"
title="Ukloni trošak">×</button>
</div>
</template>
<div style="text-align:right;font-size:13px;color:var(--tekst-sporedni);">
Ukupno troškovi: <strong x-text="ukupanTrosak().toFixed(2) + ' din'"></strong>
</div>
</div>
</template>
<template x-if="troskovi.length === 0">
<div style="font-size:13px;color:var(--tekst-sporedni);">
Nema zavisnih troškova. Dodaj trošak (prevoz, carina…) — raspodeliće se na stavke i ući u kalkulativnu nabavnu cenu.
</div>
</template>
</div>
<!-- dugmad forme -->
<div style="display:flex;justify-content:flex-end;gap:10px;">
<a href="/nabavke" class="btn-sekundarno">Odustani</a>
+1 -1
View File
@@ -47,7 +47,7 @@
{{range .Zapisi}}
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:10px 12px;white-space:nowrap;">{{.DatumPrometa.Format "02.01.2006."}}</td>
<td style="padding:10px 12px;">{{.BrojDokumenta}}</td>
<td style="padding:10px 12px;">{{.BrojDokumenta}}{{if .Uvoz}}<div style="display:inline-block;margin-top:2px;font-size:10px;font-weight:600;color:var(--sb-akcent);border:0.5px solid var(--sb-akcent);border-radius:4px;padding:0 5px;">UVOZ</div>{{end}}</td>
<td style="padding:10px 12px;">{{.DobavljacNaziv}}{{if .DobavljacPib}}<div style="font-size:11px;color:var(--tekst-sporedni);">{{.OznakaPoreskogBroja}}: {{.DobavljacPib}}</div>{{end}}</td>
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .OsnovicaOpsta}}</td>
<td style="padding:10px 12px;text-align:right;">{{printf "%.2f" .PdvOpsta}}</td>
+10
View File
@@ -102,6 +102,16 @@
<input type="text" name="napomena" style="width:100%;padding:8px 12px;border:0.5px solid var(--ivica);border-radius:8px;font-size:14px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
</div>
<div style="margin-top:14px;">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;color:var(--tekst-glavni);">
<input type="checkbox" name="uvoz" value="1" style="width:16px;height:16px;cursor:pointer;">
Uvoz (JCI)
</label>
<div class="pomocni-tekst" style="margin-top:4px;">
Označi ako je račun uvoz po carinskom dokumentu (JCI) — PDV ide u polja 006/106 obrasca PPPDV.
</div>
</div>
<div style="display:flex;justify-content:flex-end;gap:10px;margin-top:20px;">
<a href="/pdv/kpr" class="btn-sekundarno" style="font-size:14px;padding:10px 20px;">Odustani</a>
<button type="submit" style="background:var(--sb-akcent);color:#fff;border:none;padding:10px 24px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;">Sačuvaj</button>
+26 -1
View File
@@ -1,10 +1,35 @@
{{template "base" .}}
{{define "naslov"}}Podešavanja — PDV stope — NTech{{end}}
{{define "naslov"}}Podešavanja — Kalkulacija i PDV — NTech{{end}}
{{define "sadrzaj"}}
<div class="stranica-stack" style="width:100%;max-width:100%;">
<!-- kalkulacija: podrazumevana marža za formiranje prodajne cene pri nabavci -->
<div class="kartica animiraj" style="margin-bottom:16px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Kalkulacija</span>
</div>
<form method="POST" action="/podesavanja/sacuvaj">
<input type="hidden" name="_next" value="/admin/podesavanja/kalkulacija-pdv">
<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-end;">
<div>
<label for="kalkulacija_marza" class="polje-labela">Podrazumevana marža (%)</label>
<input type="number" id="kalkulacija_marza" name="kalkulacija_marza" min="0" max="1000" step="0.01" value="{{.KalkulacijaMarza}}"
style="width:140px;padding:8px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-glavni);font-size:14px;">
</div>
<button type="submit"
style="padding:9px 18px;background:var(--sb-akcent);border:none;border-radius:8px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;">
Sačuvaj
</button>
</div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:8px;">
Početna marža za formiranje prodajne cene pri nabavci. Po stavci je možeš promeniti.
</div>
</form>
</div>
<!-- postojeće stope: svaki red je forma za izmenu + zasebna forma za arhiviranje -->
<div class="kartica animiraj" style="margin-bottom:16px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
@@ -56,27 +56,6 @@
</form>
</div>
<div style="margin-top:16px;border-top:0.5px solid var(--ivica);padding-top:16px;">
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);margin-bottom:10px;">Kalkulacija</div>
<form method="POST" action="/podesavanja/sacuvaj">
<input type="hidden" name="_next" value="/admin/podesavanja/sistem">
<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-end;">
<div>
<label for="kalkulacija_marza" class="polje-labela">Podrazumevana marža (%)</label>
<input type="number" id="kalkulacija_marza" name="kalkulacija_marza" min="0" max="1000" step="0.01" value="{{.KalkulacijaMarza}}"
style="width:140px;padding:8px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-glavni);font-size:14px;">
</div>
<button type="submit"
style="padding:9px 18px;background:var(--sb-akcent);border:none;border-radius:8px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;">
Sačuvaj
</button>
</div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:8px;">
Početna marža za formiranje prodajne cene pri nabavci. Po stavci je možeš promeniti.
</div>
</form>
</div>
<!-- panel sa listom backupa -->
<div id="backup-panel" style="display:none;margin-top:16px;border-top:0.5px solid var(--ivica);padding-top:16px;">
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);margin-bottom:10px;">Dostupne rezervne kopije</div>