Dashboard/izveštaji: ispravka obračuna prihoda + KIR servisa, testovi
- Prihod meseca: izbačeni stornirani nalozi, servis preko naplaceno+avans - Servis KIR: neto cena + PDV po stvarnoj stopi (PdvKir.DodajNeto), bez hardkodovanih 20% - TopKlijenti: samo preuzeti nalozi sa naplatom/avansom - Izveštaji: mesečni ključevi sidreni na 1. u mesecu (bez preliva dana) - BUG-04: konačna cena servisa bez dvostrukog računanja delova - Dashboard: uklonjena kartica „Prihod ovog meseca" - Default port 3000 - Dodati testovi: prihod/storno/delovi/nivelacija/KIR/mesečni ključevi
This commit is contained in:
@@ -56,6 +56,26 @@ func TestRasporediTroskove(t *testing.T) {
|
||||
metod: "kolicina",
|
||||
ocekuje: []float64{50},
|
||||
},
|
||||
{
|
||||
// negativan trošak (odobreni popust) — cena ostaje nepromenjena
|
||||
naziv: "negativan trosak",
|
||||
stavke: stavke,
|
||||
trosak: -100,
|
||||
metod: "vrednost",
|
||||
ocekuje: []float64{100, 200},
|
||||
},
|
||||
{
|
||||
// trošak 10 na 3 stavke po kolicini (9 kom ukupno) → 10/9/3 ≈ 1.111/kom
|
||||
naziv: "zaokruzivanje na 2 decimale",
|
||||
stavke: []StavkaNabavke{
|
||||
{Kolicina: 3, CenaPoKomadu: 100},
|
||||
{Kolicina: 3, CenaPoKomadu: 200},
|
||||
{Kolicina: 3, CenaPoKomadu: 50},
|
||||
},
|
||||
trosak: 10,
|
||||
metod: "kolicina",
|
||||
ocekuje: []float64{101.11, 201.11, 51.11},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range slucajevi {
|
||||
|
||||
@@ -80,6 +80,30 @@ func SumirajKir(zapisi []PdvKir) PdvKirSume {
|
||||
return s
|
||||
}
|
||||
|
||||
// dodajStavku razvrstava jednu stavku (već izračunata osnovica + PDV) po stopi u
|
||||
// odgovarajuće kolone KIR-a i uvećava ukupan (bruto) iznos.
|
||||
func (k *PdvKir) dodajStavku(osnovica, pdv, stopa float64) {
|
||||
switch stopa {
|
||||
case 20:
|
||||
k.OsnovicaOpsta += osnovica
|
||||
k.PdvOpsta += pdv
|
||||
case 10:
|
||||
k.OsnovicaPosebna += osnovica
|
||||
k.PdvPosebna += pdv
|
||||
default:
|
||||
// 0% / oslobođeno — osnovica u oslobođen promet sa pravom na odbitak
|
||||
k.OslobodenSaPravom += osnovica
|
||||
}
|
||||
k.Ukupno += osnovica + pdv
|
||||
}
|
||||
|
||||
// DodajNeto dodaje stavku zadatu NETO osnovicom i PDV stopom — za izvore gde je
|
||||
// cena bez PDV-a (servisni rad/deo: cena_komada je neto, PDV se dodaje naviše).
|
||||
// PDV se izvodi kao osnovica × stopa/100, a ukupno postaje bruto (osnovica + PDV).
|
||||
func (k *PdvKir) DodajNeto(osnovica, stopa float64) {
|
||||
k.dodajStavku(osnovica, osnovica*stopa/100, stopa)
|
||||
}
|
||||
|
||||
// KirIzProdaje gradi KIR zapis iz prodaje: stavke se grupišu po PDV stopi
|
||||
// (20→opšta, 10→posebna, ostalo→oslobođeno). CenaPoKomadu je prodajna cena SA PDV,
|
||||
// pa se osnovica izvodi deljenjem sa (1 + stopa/100).
|
||||
@@ -101,19 +125,7 @@ func KirIzProdaje(nalog ProdajniNalog, stavke []StavkaProdaje, kupacNaziv, kupac
|
||||
if s.PdvStopa > 0 {
|
||||
osnovica = ukupnoLinija / (1 + s.PdvStopa/100)
|
||||
}
|
||||
pdv := ukupnoLinija - osnovica
|
||||
switch s.PdvStopa {
|
||||
case 20:
|
||||
k.OsnovicaOpsta += osnovica
|
||||
k.PdvOpsta += pdv
|
||||
case 10:
|
||||
k.OsnovicaPosebna += osnovica
|
||||
k.PdvPosebna += pdv
|
||||
default:
|
||||
// 0% / oslobođeno — osnovica bez PDV-a u oslobođen promet sa pravom na odbitak
|
||||
k.OslobodenSaPravom += osnovica
|
||||
}
|
||||
k.Ukupno += ukupnoLinija
|
||||
k.dodajStavku(osnovica, ukupnoLinija-osnovica, s.PdvStopa)
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
@@ -41,6 +41,35 @@ func TestKirIzProdaje(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPdvKirDodajNeto: osnovica zadata kao NETO (servisni rad/deo) se NE deli,
|
||||
// PDV se dodaje naviše po stvarnoj stopi i razvrstava u opštu/posebnu/oslobođen.
|
||||
func TestPdvKirDodajNeto(t *testing.T) {
|
||||
var k PdvKir
|
||||
|
||||
// rad 1000 neto @ 20% → osnovica 1000 (NE 833.33!), PDV 200, ukupno bruto 1200
|
||||
k.DodajNeto(1000, 20)
|
||||
if !blizu(k.OsnovicaOpsta, 1000) || !blizu(k.PdvOpsta, 200) {
|
||||
t.Errorf("20%%: osnovica=%v pdv=%v, očekivano 1000/200 (neto se ne deli)", k.OsnovicaOpsta, k.PdvOpsta)
|
||||
}
|
||||
|
||||
// deo 1000 neto @ 10% → posebna osnovica 1000, PDV 100
|
||||
k.DodajNeto(1000, 10)
|
||||
if !blizu(k.OsnovicaPosebna, 1000) || !blizu(k.PdvPosebna, 100) {
|
||||
t.Errorf("10%%: osnovica=%v pdv=%v, očekivano 1000/100", k.OsnovicaPosebna, k.PdvPosebna)
|
||||
}
|
||||
|
||||
// usluga 500 neto @ 0% → oslobođen promet, bez PDV
|
||||
k.DodajNeto(500, 0)
|
||||
if !blizu(k.OslobodenSaPravom, 500) {
|
||||
t.Errorf("0%%: oslobođeno=%v, očekivano 500", k.OslobodenSaPravom)
|
||||
}
|
||||
|
||||
// ukupno (bruto) = 1200 + 1100 + 500 = 2800
|
||||
if !blizu(k.Ukupno, 2800) {
|
||||
t.Errorf("ukupno=%v, očekivano 2800 (1200+1100+500)", k.Ukupno)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKprIzNabavke(t *testing.T) {
|
||||
nabavka := Nabavka{ID: 3, Napomena: "test", Datum: time.Date(2026, 6, 2, 0, 0, 0, 0, time.UTC)}
|
||||
stavke := []NabavkaStavkaPdv{
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package model
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestServisniNalog_PreostaloZaNaplatu(t *testing.T) {
|
||||
slucajevi := []struct {
|
||||
naziv string
|
||||
konacna *float64
|
||||
avans *float64
|
||||
ocekivano *float64
|
||||
}{
|
||||
{
|
||||
naziv: "cena_konacna nil → nil (nije uneta)",
|
||||
konacna: nil,
|
||||
avans: nil,
|
||||
ocekivano: nil,
|
||||
},
|
||||
{
|
||||
naziv: "bez avansa → puna cena",
|
||||
konacna: ptr(2000),
|
||||
avans: nil,
|
||||
ocekivano: ptr(2000),
|
||||
},
|
||||
{
|
||||
naziv: "avans manji od cene → razlika",
|
||||
konacna: ptr(2000),
|
||||
avans: ptr(500),
|
||||
ocekivano: ptr(1500),
|
||||
},
|
||||
{
|
||||
naziv: "avans jednak ceni → 0",
|
||||
konacna: ptr(2000),
|
||||
avans: ptr(2000),
|
||||
ocekivano: ptr(0),
|
||||
},
|
||||
{
|
||||
naziv: "avans veći od cene → 0 (ne ide u minus)",
|
||||
konacna: ptr(1000),
|
||||
avans: ptr(1500),
|
||||
ocekivano: ptr(0),
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range slucajevi {
|
||||
t.Run(s.naziv, func(t *testing.T) {
|
||||
n := ServisniNalog{CenaKonacna: s.konacna, Avans: s.avans}
|
||||
dobijeno := n.PreostaloZaNaplatu()
|
||||
if s.ocekivano == nil {
|
||||
if dobijeno != nil {
|
||||
t.Errorf("dobijeno %v, očekivano nil", *dobijeno)
|
||||
}
|
||||
return
|
||||
}
|
||||
if dobijeno == nil {
|
||||
t.Fatalf("dobijeno nil, očekivano %.2f", *s.ocekivano)
|
||||
}
|
||||
if !jednako(*dobijeno, *s.ocekivano) {
|
||||
t.Errorf("dobijeno %.2f, očekivano %.2f", *dobijeno, *s.ocekivano)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServisniDeo_Ukupno(t *testing.T) {
|
||||
d := ServisniDeo{Kolicina: 3, CenaKomada: 150}
|
||||
if !jednako(d.Ukupno(), 450) {
|
||||
t.Errorf("Ukupno = %.2f, očekivano 450", d.Ukupno())
|
||||
}
|
||||
}
|
||||
|
||||
func TestServisniDeoSaArtiklom_UkupnoSaPdv(t *testing.T) {
|
||||
d := ServisniDeoSaArtiklom{
|
||||
ServisniDeo: ServisniDeo{Kolicina: 2},
|
||||
CenaSaPdv: 600,
|
||||
}
|
||||
if !jednako(d.UkupnoSaPdv(), 1200) {
|
||||
t.Errorf("UkupnoSaPdv = %.2f, očekivano 1200", d.UkupnoSaPdv())
|
||||
}
|
||||
}
|
||||
|
||||
func TestServisniRad_UkupnoIUkupnoSaPdv(t *testing.T) {
|
||||
r := ServisniRad{Kolicina: 2, CenaKomada: 1000, PdvStopa: 20, CenaSaPdv: 1200}
|
||||
if !jednako(r.Ukupno(), 2000) {
|
||||
t.Errorf("Ukupno = %.2f, očekivano 2000", r.Ukupno())
|
||||
}
|
||||
if !jednako(r.UkupnoSaPdv(), 2400) {
|
||||
t.Errorf("UkupnoSaPdv = %.2f, očekivano 2400", r.UkupnoSaPdv())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user