Nabavka: broj računa, datum računa, PDV iznos + KPR backfill za stare nabavke

This commit is contained in:
2026-06-30 20:25:44 +02:00
parent ac7c9df2eb
commit 82a2484c10
14 changed files with 321 additions and 46 deletions
+26
View File
@@ -320,6 +320,7 @@ document.addEventListener('alpine:init', () => {
stavke: [{artikal_id: '', kolicina: 1, cena: 0, marza: 0, prodajna: 0}],
artikliOpcije: [],
dobavljacId: '', // izabrani dobavljač nabavke — filtrira listu artikala
brojRacuna: '', // broj računa dobavljača — obavezan za PDV KPR
prikaziSveArtikle: false, // true = prikaži sve artikle, ne samo dobavljačeve
marzaDefault: 0,
troskovi: [], // zavisni troškovi {naziv, iznos}
@@ -357,6 +358,21 @@ document.addEventListener('alpine:init', () => {
window.matchMedia('(max-width: 768px)').addEventListener('change', e => {
this.isMobile = e.matches
})
const resetujNedostupne = () => {
if (this.prikaziSveArtikle) return
const dostupni = new Set(this.artikliZaDobavljaca().map(a => String(a.id)))
this.stavke.forEach(s => {
if (s.artikal_id && !dostupni.has(String(s.artikal_id))) {
s.artikal_id = ''
s.cena = 0
s.marza = this.marzaDefault
s.prodajna = 0
}
})
this.preracunajSve()
}
this.$watch('dobavljacId', resetujNedostupne)
this.$watch('prikaziSveArtikle', resetujNedostupne)
},
dodajStavku() {
// ne dozvoli novu stavku dok poslednja nema izabran artikal
@@ -389,6 +405,10 @@ document.addEventListener('alpine:init', () => {
if (a.marza != null) s.marza = a.marza
else if (a.kategorija_marza != null) s.marza = a.kategorija_marza
else s.marza = this.marzaDefault
} else {
s.cena = 0
s.marza = this.marzaDefault
s.prodajna = 0
}
this.izracunajProdajnu(s)
this.preracunajSve()
@@ -437,6 +457,11 @@ document.addEventListener('alpine:init', () => {
this.stavke.forEach(s => this.izracunajProdajnu(s))
},
dodajTrosak() {
const poslednji = this.troskovi[this.troskovi.length - 1]
if (poslednji && !poslednji.naziv.trim()) {
if (window.ntechToast) window.ntechToast('Prvo unesi naziv poslednjeg troška.', 'greska')
return
}
this.troskovi.push({naziv: '', iznos: 0})
},
ukloniTrosak(i) {
@@ -571,6 +596,7 @@ document.addEventListener('alpine:init', () => {
opcija.textContent = novi.naziv
this.$refs.selDobavljac.appendChild(opcija)
this.$refs.selDobavljac.value = novi.id
this.dobavljacId = String(novi.id)
this.zatvoriModalDobavljac()
} catch {
this.modalDobGreska = 'Greška pri komunikaciji sa serverom.'
+16 -1
View File
@@ -17,6 +17,9 @@
{{if .Sacuvano}}
<div class="poruka-uspeh poruka-animacija">Nabavka je uspešno sačuvana.</div>
{{end}}
{{if .Greska}}
<div class="poruka-greska greska-animacija">{{.Greska}}</div>
{{end}}
<!-- nazad dugme -->
<a href="/nabavke" class="nazad-link" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
@@ -45,6 +48,18 @@
{{if .DobavljacNaziv}}{{.DobavljacNaziv}}{{else}}—{{end}}
</div>
</div>
<div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Broj računa</div>
<div style="font-size:14px;color:var(--tekst-glavni);">
{{if .Nabavka.BrojRacuna}}{{.Nabavka.BrojRacuna}}{{else}}—{{end}}
</div>
</div>
<div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Datum računa</div>
<div style="font-size:14px;color:var(--tekst-glavni);">
{{if .Nabavka.DatumRacuna}}{{.Nabavka.DatumRacuna.Format "02.01.2006."}}{{else}}—{{end}}
</div>
</div>
<div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Napomena</div>
<div style="font-size:14px;color:var(--tekst-glavni);">
@@ -173,7 +188,7 @@
Storno vraća količine artikala na stanje u magacinu. Nabavka ostaje u evidenciji sa oznakom stornirana. Nabavna cena se ne menja.
</div>
</div>
<form method="POST" action="/nabavke/storno/{{.Nabavka.ID}}" style="display:flex;flex-direction:column;gap:8px;align-items:flex-end;">
<form method="POST" action="/nabavke/storno/{{.Nabavka.ID}}" data-full-reload style="display:flex;flex-direction:column;gap:8px;align-items:flex-end;">
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
<input type="text" name="razlog" placeholder="Razlog storniranja (opciono)" style="width:240px;">
<button type="submit" class="btn-upozorenje"
+32 -1
View File
@@ -60,7 +60,33 @@
<div x-show="dobavljacId && prikaziSveArtikle" class="pomocni-tekst" style="margin-top:4px;">
Napomena: izabrani artikal koji nije od ovog dobavljača biće mu dodat pri čuvanju nabavke.
</div>
{{if .PdvObveznik}}
<div x-show="!dobavljacId" style="display:flex;align-items:center;gap:6px;margin-top:8px;padding:8px 10px;background:rgba(234,179,8,0.1);border:0.5px solid rgba(234,179,8,0.4);border-radius:8px;font-size:12px;color:#ca8a04;">
<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" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
Bez dobavljača KPR zapis neće imati PIB — što nije ispravno za PDV evidenciju.
</div>
{{end}}
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;">
<div>
<label class="polje-labela">Broj računa dobavljača{{if .PdvObveznik}} <span style="color:#dc2626;">*</span>{{end}}</label>
<input type="text" name="broj_racuna" x-model="brojRacuna" placeholder="npr. 2024/00123"
style="width:100%;">
</div>
<div>
<label class="polje-labela">Datum računa</label>
<input type="date" name="datum_racuna" value="{{.Danas}}" style="width:100%;">
</div>
</div>
{{if .PdvObveznik}}
<div>
<label class="polje-labela">PDV sa računa dobavljača (din)</label>
<input type="number" name="pdv_iznos" min="0" step="0.01"
placeholder="Ostavite prazno za automatski obračun iz stope"
style="width:100%;">
<div class="pomocni-tekst" style="margin-top:4px;">Ako unesete iznos, koristiće se umesto aproksimacije iz stope artikla u KPR.</div>
</div>
{{end}}
<div>
<label class="polje-labela">Napomena</label>
<textarea name="napomena" rows="2"
@@ -265,7 +291,12 @@
<!-- dugmad forme -->
<div style="display:flex;justify-content:flex-end;gap:10px;">
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/nabavke" class="btn-sekundarno">Odustani</a>
<button type="submit" class="btn-primarno">Sačuvaj nabavku</button>
<button type="submit" class="btn-primarno"
:disabled="pdvObveznik && (!dobavljacId || !brojRacuna.trim())"
@click="if(pdvObveznik && !dobavljacId){ $event.preventDefault(); window.ntechToast('Izaberi dobavljača — PDV evidencija zahteva PIB.', 'greska'); }
else if(pdvObveznik && !brojRacuna.trim()){ $event.preventDefault(); window.ntechToast('Unesi broj računa dobavljača — obavezan je za KPR.', 'greska'); }">
Sačuvaj nabavku
</button>
</div>
</form>
+12 -1
View File
@@ -20,7 +20,18 @@
<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;">Prikaži</button>
{{if or .Od .Do}}<a href="/pdv/kpr" class="nazad-link" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" style="margin-bottom:0;">Poništi filter</a>{{end}}
</form>
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/pdv/kpr/nova" class="btn-primarno" style="font-size:13px;padding:8px 16px;white-space:nowrap;">+ Nov ulazni račun</a>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
{{if index .Dozvole "pdv.dodaj"}}
<form method="POST" action="/pdv/kpr/backfill-nabavke" data-full-reload>
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
<button type="submit" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;white-space:nowrap;"
data-potvrda="Uvesti sve nabavke koje još nisu u KPR-u?">
Uvezi iz nabavki
</button>
</form>
{{end}}
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/pdv/kpr/nova" class="btn-primarno" style="font-size:13px;padding:8px 16px;white-space:nowrap;">+ Nov ulazni račun</a>
</div>
</div>
</div>