Popis: zakonske kolone (nabavna cena, vrednost, manjak/višak), tip popisa, datum i sačinilac

This commit is contained in:
2026-06-30 15:36:22 +02:00
parent c583472e3e
commit 28f7e30a3d
2 changed files with 185 additions and 35 deletions
+23 -6
View File
@@ -316,9 +316,11 @@ func (h *Handler) StanjeZalihaIzvestaj(w http.ResponseWriter, r *http.Request) {
// PodaciPopisa su podaci za stranicu popisa // PodaciPopisa su podaci za stranicu popisa
type PodaciPopisa struct { type PodaciPopisa struct {
model.PodaciStranice model.PodaciStranice
Artikli []model.ArtikalSaKategorijom Artikli []model.ArtikalSaKategorijom
Sacuvano bool Sacuvano bool
Greska string Greska string
TipPopisa string // godišnji | vanredni | preuzimanje
DatumPopisa string // formatiran datum za prikaz
} }
// Popis prikazuje formu za unos stvarnog stanja (inventuru) // Popis prikazuje formu za unos stvarnog stanja (inventuru)
@@ -327,7 +329,7 @@ func (h *Handler) Popis(w http.ResponseWriter, r *http.Request) {
return return
} }
artikli, err := h.Artikli.Lista(r.Context(), appdbPkg.ArtikalFilter{}) artikli, err := h.Artikli.Lista(r.Context(), appdbPkg.ArtikalFilter{Tip: model.TipProizvod})
if err != nil { if err != nil {
http.Error(w, "Greška pri učitavanju artikala", http.StatusInternalServerError) http.Error(w, "Greška pri učitavanju artikala", http.StatusInternalServerError)
return return
@@ -342,6 +344,8 @@ func (h *Handler) Popis(w http.ResponseWriter, r *http.Request) {
PodaciStranice: ps, PodaciStranice: ps,
Artikli: artikli, Artikli: artikli,
Sacuvano: r.URL.Query().Get("sacuvano") == "1", Sacuvano: r.URL.Query().Get("sacuvano") == "1",
TipPopisa: "godišnji",
DatumPopisa: time.Now().Format("02.01.2006."),
}) })
} }
@@ -357,15 +361,26 @@ func (h *Handler) SacuvajPopis(w http.ResponseWriter, r *http.Request) {
return return
} }
artikli, err := h.Artikli.Lista(r.Context(), appdbPkg.ArtikalFilter{}) artikli, err := h.Artikli.Lista(r.Context(), appdbPkg.ArtikalFilter{Tip: model.TipProizvod})
if err != nil { if err != nil {
http.Error(w, "Greška pri učitavanju artikala", http.StatusInternalServerError) http.Error(w, "Greška pri učitavanju artikala", http.StatusInternalServerError)
return return
} }
tipPopisa := r.FormValue("tip_popisa")
if tipPopisa == "" {
tipPopisa = "godišnji"
}
napomena := r.FormValue("napomena") napomena := r.FormValue("napomena")
if napomena == "" { if napomena == "" {
napomena = "Godišnji popis" switch tipPopisa {
case "vanredni":
napomena = "Vanredni popis"
case "preuzimanje":
napomena = "Popis pri preuzimanju dužnosti"
default:
napomena = "Godišnji popis"
}
} }
var greskaBroj int var greskaBroj int
@@ -413,6 +428,8 @@ func (h *Handler) SacuvajPopis(w http.ResponseWriter, r *http.Request) {
PodaciStranice: ps, PodaciStranice: ps,
Artikli: artikli, Artikli: artikli,
Greska: fmt.Sprintf("Došlo je do greške pri upisivanju %d artikala.", greskaBroj), Greska: fmt.Sprintf("Došlo je do greške pri upisivanju %d artikala.", greskaBroj),
TipPopisa: tipPopisa,
DatumPopisa: time.Now().Format("02.01.2006."),
}) })
return return
} }
+162 -29
View File
@@ -5,7 +5,7 @@
{{define "dodatni-css"}} {{define "dodatni-css"}}
<style> <style>
.popis-input { .popis-input {
width: 80px; width: 90px;
text-align: right; text-align: right;
font-family: monospace; font-family: monospace;
font-size: 14px; font-size: 14px;
@@ -15,7 +15,7 @@
border-color: #f97316; border-color: #f97316;
background: rgba(249,115,22,0.06); background: rgba(249,115,22,0.06);
} }
.popis-razlika { .popis-razlika, .popis-vrednost-razlika {
font-family: monospace; font-family: monospace;
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
@@ -23,6 +23,17 @@
text-align: right; text-align: right;
} }
.red-kriticno td { background: rgba(220,38,38,0.04); } .red-kriticno td { background: rgba(220,38,38,0.04); }
.popis-suma {
display: flex;
gap: 24px;
flex-wrap: wrap;
font-size: 13px;
}
.popis-suma-stavka { display: flex; flex-direction: column; gap: 2px; }
.popis-suma-labela { color: var(--tekst-slabi); font-size: 12px; }
.popis-suma-vrednost { font-weight: 600; font-family: monospace; font-size: 14px; }
.boja-manjak { color: #dc2626; }
.boja-visak { color: #22c55e; }
</style> </style>
{{end}} {{end}}
@@ -40,11 +51,15 @@
<div class="kartica animiraj" style="padding:14px 18px;"> <div class="kartica animiraj" style="padding:14px 18px;">
<div style="display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;"> <div style="display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;">
<div style="flex:1;min-width:260px;"> <div style="flex:1;min-width:260px;">
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);margin-bottom:4px;">Godišnji popis magacina</div> <div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);margin-bottom:4px;">Popis magacina</div>
<div style="font-size:13px;color:var(--tekst-slabi);line-height:1.6;"> <div style="font-size:13px;color:var(--tekst-slabi);line-height:1.6;">
Unesite stvarno prebrojano stanje za svaki artikal. Polja sa razlikom biće istaknuta narandžasto. Unesite stvarno prebrojano stanje za svaki artikal. Polja sa razlikom biće istaknuta narandžasto.
Samo artikli sa izmenjenom količinom biće korigovani — ostali ostaju nepromenjeni. Samo artikli sa izmenjenom količinom biće korigovani — ostali ostaju nepromenjeni.
</div> </div>
<div style="font-size:12px;color:var(--tekst-slabi);margin-top:6px;">
Datum popisa: <strong style="color:var(--tekst-glavni);">{{.DatumPopisa}}</strong>
&nbsp;|&nbsp; Sačinio: <strong style="color:var(--tekst-glavni);">{{.KorisnikIme}}</strong>
</div>
</div> </div>
<a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/izvestaji" class="btn-sekundarno" style="white-space:nowrap;">← Izveštaji</a> <a hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML" href="/izvestaji" class="btn-sekundarno" style="white-space:nowrap;">← Izveštaji</a>
</div> </div>
@@ -58,9 +73,17 @@
<div style="padding:12px 20px;border-bottom:0.5px solid var(--ivica);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;"> <div style="padding:12px 20px;border-bottom:0.5px solid var(--ivica);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;">
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Artikli</span> <span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Artikli</span>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;"> <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
<div>
<label class="polje-labela" style="display:inline;margin-right:6px;">Tip:</label>
<select name="tip_popisa" id="tip-popisa" style="width:auto;height:auto;font-size:13px;padding:5px 28px 5px 8px;" onchange="azurirajNapomenu()">
<option value="godišnji"{{if eq .TipPopisa "godišnji"}} selected{{end}}>Godišnji</option>
<option value="vanredni"{{if eq .TipPopisa "vanredni"}} selected{{end}}>Vanredni</option>
<option value="preuzimanje"{{if eq .TipPopisa "preuzimanje"}} selected{{end}}>Preuzimanje dužnosti</option>
</select>
</div>
<div> <div>
<label class="polje-labela" style="display:inline;margin-right:6px;">Napomena:</label> <label class="polje-labela" style="display:inline;margin-right:6px;">Napomena:</label>
<input type="text" name="napomena" value="Godišnji popis" style="width:200px;font-size:13px;padding:5px 8px;"> <input type="text" name="napomena" id="napomena-polje" value="Godišnji popis" style="width:220px;font-size:13px;padding:5px 8px;">
</div> </div>
<button type="submit" class="btn-primarno">Sačuvaj popis</button> <button type="submit" class="btn-primarno">Sačuvaj popis</button>
</div> </div>
@@ -72,15 +95,19 @@
</div> </div>
{{else}} {{else}}
<div style="overflow-x:auto;"> <div style="overflow-x:auto;">
<table class="tabela"> <table class="tabela" id="popis-tabela">
<thead> <thead>
<tr> <tr>
<th>Artikal</th> <th>Artikal</th>
<th>Šifra</th> <th>Šifra</th>
<th>Kategorija</th> <th>Kategorija</th>
<th style="text-align:right;">Knjižno stanje</th> <th style="text-align:right;">JM</th>
<th style="text-align:right;">Stvarno stanje</th> <th style="text-align:right;">Nab. cena</th>
<th style="text-align:right;">Razlika</th> <th style="text-align:right;">Knj. kol.</th>
<th style="text-align:right;">Knj. vrednost</th>
<th style="text-align:right;">Stvarno kol.</th>
<th style="text-align:right;">Razlika kol.</th>
<th style="text-align:right;">Razlika vrednost</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -89,7 +116,10 @@
<td style="font-weight:500;">{{.Naziv}}</td> <td style="font-weight:500;">{{.Naziv}}</td>
<td style="font-family:monospace;font-size:12px;color:var(--tekst-slabi);">{{if .Sifra}}{{.Sifra}}{{else}}—{{end}}</td> <td style="font-family:monospace;font-size:12px;color:var(--tekst-slabi);">{{if .Sifra}}{{.Sifra}}{{else}}—{{end}}</td>
<td style="font-size:12px;color:var(--tekst-slabi);">{{if .KategorijaNaziv}}{{.KategorijaNaziv}}{{else}}—{{end}}</td> <td style="font-size:12px;color:var(--tekst-slabi);">{{if .KategorijaNaziv}}{{.KategorijaNaziv}}{{else}}—{{end}}</td>
<td style="text-align:right;font-size:12px;color:var(--tekst-slabi);">{{if .JedinicaMere}}{{.JedinicaMere}}{{else}}kom{{end}}</td>
<td style="text-align:right;font-family:monospace;font-size:13px;">{{dinariCeli .NabavnaCena}}</td>
<td style="text-align:right;font-family:monospace;font-weight:600;" id="knjizno-{{.ID}}">{{.Kolicina}}</td> <td style="text-align:right;font-family:monospace;font-weight:600;" id="knjizno-{{.ID}}">{{.Kolicina}}</td>
<td style="text-align:right;font-family:monospace;font-size:13px;" id="knjvred-{{.ID}}"></td>
<td style="text-align:right;"> <td style="text-align:right;">
<input type="number" <input type="number"
class="popis-input" class="popis-input"
@@ -98,18 +128,54 @@
value="{{.Kolicina}}" value="{{.Kolicina}}"
min="0" min="0"
data-knjizno="{{.Kolicina}}" data-knjizno="{{.Kolicina}}"
data-cena="{{printf "%.4f" .NabavnaCena}}"
data-id="{{.ID}}" data-id="{{.ID}}"
oninput="azurirajRazliku(this)"> oninput="azurirajRed(this)">
</td> </td>
<td style="text-align:right;"> <td style="text-align:right;">
<span class="popis-razlika" id="razlika-{{.ID}}"></span> <span class="popis-razlika" id="razlika-{{.ID}}"></span>
</td> </td>
<td style="text-align:right;">
<span class="popis-vrednost-razlika" id="vrazlika-{{.ID}}"></span>
</td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>
<tfoot>
<tr style="font-weight:600;background:var(--pozadina);">
<td colspan="5" style="text-align:right;font-size:12px;color:var(--tekst-slabi);">UKUPNO:</td>
<td style="text-align:right;font-family:monospace;" id="suma-knjizno-kol"></td>
<td style="text-align:right;font-family:monospace;" id="suma-knjizno-vred"></td>
<td style="text-align:right;font-family:monospace;" id="suma-stvarno-kol"></td>
<td style="text-align:right;font-family:monospace;" id="suma-razlika-kol"></td>
<td style="text-align:right;font-family:monospace;" id="suma-razlika-vred"></td>
</tr>
</tfoot>
</table> </table>
</div> </div>
<!-- sumarni pregled manjkova/viškova -->
<div style="padding:14px 20px;border-top:0.5px solid var(--ivica);">
<div class="popis-suma">
<div class="popis-suma-stavka">
<span class="popis-suma-labela">Manjkovi (kom)</span>
<span class="popis-suma-vrednost boja-manjak" id="sum-manjak-kol">0</span>
</div>
<div class="popis-suma-stavka">
<span class="popis-suma-labela">Manjkovi (vrednost)</span>
<span class="popis-suma-vrednost boja-manjak" id="sum-manjak-vred">0,00</span>
</div>
<div class="popis-suma-stavka">
<span class="popis-suma-labela">Viškovi (kom)</span>
<span class="popis-suma-vrednost boja-visak" id="sum-visak-kol">0</span>
</div>
<div class="popis-suma-stavka">
<span class="popis-suma-labela">Viškovi (vrednost)</span>
<span class="popis-suma-vrednost boja-visak" id="sum-visak-vred">0,00</span>
</div>
</div>
</div>
<!-- dugme na dnu --> <!-- dugme na dnu -->
<div style="padding:14px 20px;border-top:0.5px solid var(--ivica);display:flex;justify-content:flex-end;"> <div style="padding:14px 20px;border-top:0.5px solid var(--ivica);display:flex;justify-content:flex-end;">
<button type="submit" class="btn-primarno">Sačuvaj popis</button> <button type="submit" class="btn-primarno">Sačuvaj popis</button>
@@ -121,33 +187,100 @@
</div> </div>
<script> <script>
function azurirajRazliku(input) { var napomePoTipu = {
var id = input.dataset.id; "godišnji": "Godišnji popis",
"vanredni": "Vanredni popis",
"preuzimanje": "Popis pri preuzimanju dužnosti"
};
function azurirajNapomenu() {
var tip = document.getElementById('tip-popisa').value;
document.getElementById('napomena-polje').value = napomePoTipu[tip] || "Godišnji popis";
}
function fmt(n) {
return n.toLocaleString('sr-RS', {minimumFractionDigits: 2, maximumFractionDigits: 2});
}
function azurirajRed(input) {
var id = input.dataset.id;
var knjizno = parseInt(input.dataset.knjizno, 10); var knjizno = parseInt(input.dataset.knjizno, 10);
var cena = parseFloat(input.dataset.cena) || 0;
var stvarno = parseInt(input.value, 10); var stvarno = parseInt(input.value, 10);
var span = document.getElementById('razlika-' + id); var spanKol = document.getElementById('razlika-' + id);
var spanVred = document.getElementById('vrazlika-' + id);
if (isNaN(stvarno)) { if (isNaN(stvarno)) {
span.textContent = '—'; spanKol.textContent = '—'; spanVred.textContent = '—';
span.style.color = ''; spanKol.style.color = ''; spanVred.style.color = '';
input.classList.remove('izmenjeno'); input.classList.remove('izmenjeno');
return;
}
var razlika = stvarno - knjizno;
if (razlika === 0) {
span.textContent = '—';
span.style.color = '';
input.classList.remove('izmenjeno');
} else if (razlika > 0) {
span.textContent = '+' + razlika;
span.style.color = '#22c55e';
input.classList.add('izmenjeno');
} else { } else {
span.textContent = razlika; var razlika = stvarno - knjizno;
span.style.color = '#dc2626'; if (razlika === 0) {
input.classList.add('izmenjeno'); spanKol.textContent = '—'; spanVred.textContent = '—';
spanKol.style.color = ''; spanVred.style.color = '';
input.classList.remove('izmenjeno');
} else {
var boja = razlika > 0 ? '#22c55e' : '#dc2626';
spanKol.textContent = (razlika > 0 ? '+' : '') + razlika;
spanVred.textContent = (razlika > 0 ? '+' : '') + fmt(razlika * cena);
spanKol.style.color = boja; spanVred.style.color = boja;
input.classList.add('izmenjeno');
}
} }
azurirajSumu();
} }
function azurirajSumu() {
var inputs = document.querySelectorAll('.popis-input');
var knjiznoKol = 0, knjiznoVred = 0, stvarnoKol = 0;
var manjKol = 0, manjVred = 0, visakKol = 0, visakVred = 0;
inputs.forEach(function(inp) {
var kn = parseInt(inp.dataset.knjizno, 10) || 0;
var cena = parseFloat(inp.dataset.cena) || 0;
var st = parseInt(inp.value, 10);
if (isNaN(st)) st = kn;
knjiznoKol += kn;
knjiznoVred += kn * cena;
stvarnoKol += st;
var r = st - kn;
if (r < 0) { manjKol += Math.abs(r); manjVred += Math.abs(r) * cena; }
if (r > 0) { visakKol += r; visakVred += r * cena; }
});
var razlikaKol = stvarnoKol - knjiznoKol;
var razlikaVred = visakVred - manjVred;
var el = function(id) { return document.getElementById(id); };
if (el('suma-knjizno-kol')) el('suma-knjizno-kol').textContent = knjiznoKol;
if (el('suma-knjizno-vred')) el('suma-knjizno-vred').textContent = fmt(knjiznoVred);
if (el('suma-stvarno-kol')) el('suma-stvarno-kol').textContent = stvarnoKol;
if (el('suma-razlika-kol')) {
el('suma-razlika-kol').textContent = (razlikaKol >= 0 ? '+' : '') + razlikaKol;
el('suma-razlika-kol').style.color = razlikaKol === 0 ? '' : (razlikaKol > 0 ? '#22c55e' : '#dc2626');
}
if (el('suma-razlika-vred')) {
el('suma-razlika-vred').textContent = (razlikaVred >= 0 ? '+' : '') + fmt(razlikaVred);
el('suma-razlika-vred').style.color = razlikaVred === 0 ? '' : (razlikaVred > 0 ? '#22c55e' : '#dc2626');
}
if (el('sum-manjak-kol')) el('sum-manjak-kol').textContent = manjKol;
if (el('sum-manjak-vred')) el('sum-manjak-vred').textContent = fmt(manjVred);
if (el('sum-visak-kol')) el('sum-visak-kol').textContent = visakKol;
if (el('sum-visak-vred')) el('sum-visak-vred').textContent = fmt(visakVred);
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.popis-input').forEach(function(inp) {
var id = inp.dataset.id;
var kn = parseInt(inp.dataset.knjizno, 10) || 0;
var cena = parseFloat(inp.dataset.cena) || 0;
var el = document.getElementById('knjvred-' + id);
if (el) el.textContent = fmt(kn * cena);
});
azurirajSumu();
});
</script> </script>
{{end}} {{end}}