Podešavanja — adresa, telefon, PIB firme; priznanica sa podacima firme i klijenta; ispravka prihoda na dashboardu
This commit is contained in:
@@ -94,6 +94,7 @@ func main() {
|
||||
r.Get("/prodaja/nova", h.NovaProdaja)
|
||||
r.Post("/prodaja/nova", h.SacuvajProdaju)
|
||||
r.Post("/prodaja/obrisi/{id}", h.ObrisiProdaju)
|
||||
r.Get("/prodaja/{id}/stampa", h.StampaProdaje)
|
||||
r.Get("/prodaja/{id}", h.DetaljiProdaje)
|
||||
|
||||
log.Printf("NTech pokrenut na portu %s", port)
|
||||
|
||||
@@ -38,7 +38,7 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err := h.DB.QueryRowContext(ctx, `
|
||||
SELECT COALESCE(SUM(ukupno), 0) FROM prodajni_nalozi
|
||||
WHERE strftime('%Y-%m', datum) = strftime('%Y-%m', 'now', 'localtime')`,
|
||||
WHERE substr(datum, 1, 7) = strftime('%Y-%m', 'now', 'localtime')`,
|
||||
).Scan(&prihodOvogMeseca); err != nil {
|
||||
log.Printf("dashboard: prihod ovog meseca: %v", err)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ type PodaciPodesavanja struct {
|
||||
model.PodaciStranice
|
||||
NazivFirme string
|
||||
Podnazlov string
|
||||
Adresa string
|
||||
Telefon string
|
||||
PIB string
|
||||
LogoTip string
|
||||
LogoPutanja string
|
||||
Tema string
|
||||
@@ -42,6 +45,9 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) {
|
||||
},
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
Adresa: podesavanja["adresa"],
|
||||
Telefon: podesavanja["telefon"],
|
||||
PIB: podesavanja["pib"],
|
||||
LogoTip: podesavanja["logo_tip"],
|
||||
LogoPutanja: podesavanja["logo_putanja"],
|
||||
Tema: podesavanja["tema"],
|
||||
@@ -75,6 +81,9 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
||||
polja := map[string]string{
|
||||
"naziv_firme": r.FormValue("naziv_firme"),
|
||||
"podnazlov": r.FormValue("podnazlov"),
|
||||
"adresa": r.FormValue("adresa"),
|
||||
"telefon": r.FormValue("telefon"),
|
||||
"pib": r.FormValue("pib"),
|
||||
"logo_tip": r.FormValue("logo_tip"),
|
||||
"tema": r.FormValue("tema"),
|
||||
}
|
||||
|
||||
@@ -44,6 +44,18 @@ type PodaciDetaljiProdaje struct {
|
||||
Sacuvano bool
|
||||
}
|
||||
|
||||
// PodaciStampeProdaje su podaci za stranicu za štampanje priznanice
|
||||
type PodaciStampeProdaje struct {
|
||||
Nalog model.ProdajniNalog
|
||||
Stavke []model.StavkaProdajeSaArtiklom
|
||||
KlijentNaziv string
|
||||
NazivFirme string
|
||||
Podnazlov string
|
||||
Adresa string
|
||||
Telefon string
|
||||
PIB string
|
||||
}
|
||||
|
||||
// artikalUJSONSaCenom pretvara listu artikala u template.JS vrednost sa prodajnom cenom i stanjem
|
||||
func artikalUJSONSaCenom(artikli []model.ArtikalSaKategorijom) template.JS {
|
||||
type stavka struct {
|
||||
@@ -288,6 +300,68 @@ func (h *Handler) DetaljiProdaje(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// StampaProdaje renderuje print-friendly stranicu za dati prodajni nalog
|
||||
func (h *Handler) StampaProdaje(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := parseID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
nalog, err := h.ProdajaRepo.DohvatiID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Nalog nije pronađen", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
stavke, err := h.ProdajaRepo.DohvatiStavke(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju stavki", 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)
|
||||
return
|
||||
}
|
||||
|
||||
klijentNaziv := ""
|
||||
if nalog.KlijentID != nil {
|
||||
klijent, err := h.KlijentiRepo.DohvatiID(r.Context(), *nalog.KlijentID)
|
||||
if err == nil {
|
||||
if klijent.NazivFirme != "" {
|
||||
klijentNaziv = klijent.NazivFirme
|
||||
} else {
|
||||
klijentNaziv = strings.TrimSpace(klijent.Ime + " " + klijent.Prezime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
podaci := PodaciStampeProdaje{
|
||||
Nalog: *nalog,
|
||||
Stavke: stavke,
|
||||
KlijentNaziv: klijentNaziv,
|
||||
NazivFirme: podesavanja["naziv_firme"],
|
||||
Podnazlov: podesavanja["podnazlov"],
|
||||
Adresa: podesavanja["adresa"],
|
||||
Telefon: podesavanja["telefon"],
|
||||
PIB: podesavanja["pib"],
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles("web/templates/stranice/prodaja_stampa.html")
|
||||
if err != nil {
|
||||
log.Printf("greška pri učitavanju šablona za štampu: %v", err)
|
||||
http.Error(w, "Greška pri učitavanju stranice", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := tmpl.ExecuteTemplate(w, "prodaja_stampa.html", podaci); err != nil {
|
||||
log.Printf("greška pri renderovanju štampe: %v", err)
|
||||
http.Error(w, "Greška pri prikazu stranice", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// ObrisiProdaju prima POST zahtev, vraća stanje na magacin i briše nalog
|
||||
func (h *Handler) ObrisiProdaju(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := parseID(chi.URLParam(r, "id"))
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('adresa', '');
|
||||
INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('telefon', '');
|
||||
INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('pib', '');
|
||||
@@ -35,6 +35,28 @@
|
||||
placeholder="npr. Servis računara">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Adresa</label>
|
||||
<input type="text" name="adresa" value="{{.Adresa}}"
|
||||
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;"
|
||||
placeholder="npr. Ulica i broj, Grad">
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;">
|
||||
<div>
|
||||
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Telefon</label>
|
||||
<input type="text" name="telefon" value="{{.Telefon}}"
|
||||
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;"
|
||||
placeholder="npr. +381 11 123 4567">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">PIB</label>
|
||||
<input type="text" name="pib" value="{{.PIB}}"
|
||||
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;"
|
||||
placeholder="npr. 123456789">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:8px;">Logo zona</label>
|
||||
<div style="display:flex;gap:10px;">
|
||||
|
||||
@@ -40,10 +40,16 @@
|
||||
|
||||
<!-- zaglavlje naloga -->
|
||||
<div class="kartica detalji-kartica">
|
||||
<div style="margin-bottom:16px;padding-bottom:14px;border-bottom:0.5px solid var(--ivica);">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px;margin-bottom:16px;padding-bottom:14px;border-bottom:0.5px solid var(--ivica);">
|
||||
<span style="font-size:20px;font-weight:600;color:var(--tekst-glavni);font-family:monospace;">
|
||||
{{.Nalog.BrojNaloga}}
|
||||
</span>
|
||||
<a href="/prodaja/{{.Nalog.ID}}/stampa" target="_blank"
|
||||
style="display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:var(--kartica);border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;color:var(--tekst-sporedni);text-decoration:none;transition:background 0.2s;"
|
||||
onmouseover="this.style.background='var(--pozadina)'" onmouseout="this.style.background='var(--kartica)'">
|
||||
<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"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
|
||||
Štampaj
|
||||
</a>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(140px, 1fr));gap:16px;">
|
||||
<div>
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="sr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Priznanica {{.Nalog.BrojNaloga}}</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-size: 14px;
|
||||
color: #111;
|
||||
background: #f5f5f5;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
|
||||
.stranica {
|
||||
background: #fff;
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.zaglavlje {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.firma-naziv {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.firma-podnazlov {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.firma-kontakt {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.nalog-info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.nalog-broj {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: monospace;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.nalog-datum {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.meta-stavka label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #9ca3af;
|
||||
display: block;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.meta-stavka span {
|
||||
font-size: 14px;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
thead tr {
|
||||
border-bottom: 1.5px solid #111;
|
||||
}
|
||||
|
||||
thead th {
|
||||
padding: 8px 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #6b7280;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
thead th:not(:first-child) { text-align: right; }
|
||||
|
||||
tbody tr {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
tbody td {
|
||||
padding: 10px 10px;
|
||||
font-size: 14px;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
tbody td:not(:first-child) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
tfoot tr {
|
||||
border-top: 1.5px solid #111;
|
||||
}
|
||||
|
||||
tfoot td {
|
||||
padding: 12px 10px;
|
||||
}
|
||||
|
||||
.ukupno-label {
|
||||
text-align: right;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.ukupno-iznos {
|
||||
text-align: right;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.ekran-dugmad {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 9px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primarni {
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-sekundarni {
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body { background: #fff; padding: 0; }
|
||||
.stranica { box-shadow: none; border-radius: 0; padding: 20px; max-width: 100%; }
|
||||
.ekran-dugmad { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="stranica">
|
||||
|
||||
<div class="zaglavlje">
|
||||
<div>
|
||||
<div class="firma-naziv">{{.NazivFirme}}</div>
|
||||
{{if .Podnazlov}}<div class="firma-podnazlov">{{.Podnazlov}}</div>{{end}}
|
||||
{{if .Adresa}}<div class="firma-kontakt">{{.Adresa}}</div>{{end}}
|
||||
{{if .Telefon}}<div class="firma-kontakt">{{.Telefon}}</div>{{end}}
|
||||
{{if .PIB}}<div class="firma-kontakt">PIB: {{.PIB}}</div>{{end}}
|
||||
</div>
|
||||
<div class="nalog-info">
|
||||
<div class="nalog-broj">{{.Nalog.BrojNaloga}}</div>
|
||||
<div class="nalog-datum">{{.Nalog.Datum.Format "02.01.2006. u 15:04"}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .KlijentNaziv}}
|
||||
<div class="meta">
|
||||
<div class="meta-stavka">
|
||||
<label>Klijent</label>
|
||||
<span>{{.KlijentNaziv}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Artikal</th>
|
||||
<th>Kol.</th>
|
||||
<th>Cena/kom</th>
|
||||
<th>Ukupno</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Stavke}}
|
||||
<tr>
|
||||
<td>{{.ArtikalNaziv}}</td>
|
||||
<td>{{.Kolicina}}</td>
|
||||
<td>{{printf "%.2f" .CenaPoKomadu}} din</td>
|
||||
<td>{{printf "%.2f" .Ukupno}} din</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3" class="ukupno-label">Ukupno za naplatu:</td>
|
||||
<td class="ukupno-iznos">{{printf "%.2f" .Nalog.Ukupno}} din</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
{{if .Nalog.Napomena}}
|
||||
<div style="margin-top:20px;padding-top:16px;border-top:1px solid #e5e7eb;">
|
||||
<div style="font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:0.05em;color:#9ca3af;margin-bottom:4px;">Napomena</div>
|
||||
<div style="font-size:13px;color:#374151;">{{.Nalog.Napomena}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="ekran-dugmad">
|
||||
<button class="btn btn-sekundarni" onclick="window.close()">Zatvori</button>
|
||||
<button class="btn btn-primarni" onclick="window.print()">Štampaj</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>window.onload = function() { window.print(); };</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user