Compare commits
10 Commits
30db396ee6
...
fc2743e98d
| Author | SHA1 | Date | |
|---|---|---|---|
|
fc2743e98d
|
|||
|
e3f42e64a8
|
|||
|
ae7fd7b5e2
|
|||
|
4d81c576cd
|
|||
|
6444e19808
|
|||
|
6ee9eefdd0
|
|||
|
100915d453
|
|||
|
42c74a725a
|
|||
|
c7470ebbc9
|
|||
|
803e1f6341
|
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user