Files
GoNtech/BUG.md
T
Dasko 99ae077082 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
2026-06-27 09:41:07 +02:00

235 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Bug lista — Dashboard "Prihod ovog meseca"
Istraživanje logike `PrihodTekuciMesec` i sve komponente koje utiču na prikazanu cifru.
---
## BUG-01 — Stornirani prodajni nalozi ulaze u prihod ⚠️ KRITIČAN
**Fajl:** `internal/db/sqlite/izvestaj.go:3948`
**Problem:** SQL upit koji računa prihod ne filtrira stornirane naloge:
```sql
SELECT SUM(ukupno) FROM prodajni_nalozi
WHERE substr(datum, 1, 7) = strftime('%Y-%m', 'now', 'localtime')
-- ❌ nema: AND stornirano = 0
```
Kolona `stornirano INTEGER NOT NULL DEFAULT 0` postoji od migracije `035_pos_faza1.sql`. Kada se nalog stornira, `stornirano = 1` i magacin se vraća, ali iznos ostaje u prihodu.
**Posledica:** Svaki stornirani nalog uvećava prikazani mesečni prihod za iznos koji nije stvarno naplaćen. Na kraju meseca razlika može biti značajna ako ima više storniranja.
**Isti propust na još tri mesta u istom fajlu:**
- `MesecniPrihodProdaja` (l.146) — grafikon prihoda po mesecima
- `PoslednjeProdaje` (l.104) — lista poslednjih prodaja na dashboardu
- `TopKlijenti` (l.220) — rang lista kupaca po vrednosti
**Ispravka:** Dodati `AND stornirano = 0` u sve četiri SQL SELECT-e.
---
## BUG-02 — Mešanje PDV-a: prodaja bruto, servis neto ⚠️ KRITIČAN
**Fajl:** `internal/db/sqlite/izvestaj.go:3948`
**Problem:** Zbir spaja dve veličine koje nisu uporedive:
| Izvor | Kolona | PDV |
|---|---|---|
| `prodajni_nalozi.ukupno` | `kolicina × cena_po_komadu` | **SA PDV-om** (bruto) |
| `servisni_nalozi.cena_konacna` | `dijagnostika + radovi + delovi` | **BEZ PDV-a** (neto) |
Prodajna cena se unosi kao maloprodajna (bruto) cena — to potvrđuje `handler/prodaja.go:188191`:
```go
ukupno += float64(s.Kolicina) * s.CenaPoKomadu // bruto
nalog.Ukupno = ukupno
```
`cena_konacna` za servis se auto-računa koristeći `rad.Ukupno()` i `deo.Ukupno()` (model `servis.go:77,127`) koji vraćaju `kolicina × cena_komada` — neto cenu bez PDV-a.
**Posledica:** Prikazana cifra nije ni ukupan prihod sa PDV-om ni ukupan neto prihod. Za firme sa PDV-om (stopa 20%) to može biti razlika od 20% na delu koji dolazi iz servisa.
**Ispravka:** Odlučiti se za jedinstven standard (preporučeno: **sa PDV-om**) i koristiti `cena_sa_pdv` kolone / `UkupnoSaPdv()` metode konzistentno.
---
## BUG-03 — Delovi servisa ne ulaze u prihod kada je cena ručno uneta ⚠️ VAŽAN
**Fajl:** `internal/db/sqlite/izvestaj.go:4448`, `internal/handler/servis.go:14981500`
**Problem:** Šablon servisa tretira `cena_konacna` kao cenu rada (bez delova):
```go
// servis.go:14981500
} else if nalog.CenaKonacna != nil {
ukupnoSve = *nalog.CenaKonacna + ukupnoDelovi // delovi se DODAJU na cenu_konacnu
```
Kada korisnik ručno unese `cena_konacna` (polje "Cena rada"), u bazu ide samo vrednost rada — delovi su odvojeni. SQL za prihod uzima samo `cena_konacna`:
```sql
SELECT SUM(cena_konacna) FROM servisni_nalozi WHERE status = 'Preuzeto' ...
```
**Posledica:** Vrednost svih ugrađenih delova na servisnim nalozima sa ručno unetom `cena_konacna` **ne ulazi u prikazani prihod**. Prihod je manji od stvarnog.
**Napomena:** Kada je status prelaz na "Preuzeto" i `cena_konacna` je bila `NULL`, auto-izračun (`servis.go:20412048`) uključuje delove u `cena_konacna`. Dakle ponašanje zavisi od toga da li je korisnik uneo cenu ručno ili je sistem auto-izračunao — nedoslednost u definiciji polja.
---
## BUG-04 — Duplo računanje delova u naplaćenom iznosu pri auto-izračunu ⚠️ VAŽAN
**Fajl:** `internal/handler/servis.go:20342083`
**Problem:** Kada servisni nalog nema unesenu `cena_konacna` i prelazi u status "Preuzeto", dešava se:
1. Auto-izračun (l.20372049): `cena_konacna = dijagnostika + radovi + **delovi**` → snima u DB
2. Izračun naplaćenog iznosa (l.20642073): čita novu `cena_konacna` iz DB, pa dodaje `ukupnoDelovi` još jednom:
```go
nalog.CenaKonacna = &ukupno // ukupno već sadrži delove (l.2049)
// ...
nalog, _ = h.ServisRepo.DohvatiID(...) // čita iz DB — cena_konacna uključuje delove
iznos = *nalog.CenaKonacna + ukupnoDelovi // ← delovi se broje DVAPUT
```
**Posledica:** Iznos koji se fiskalizuje i beleži kao naplaćen veći je od stvarno dugovanog (delovi duplo). Ovo je greška u fiskalnom računu.
**Reprodukcija:** Napraviti servisni nalog, dodati deo (npr. 1.000 din), ne unositi cenu rada, promeniti status u "Preuzeto" bez popunjavanja forme. Naplaćeni iznos biće 2.000 umesto 1.000.
---
## BUG-05 — CSS skriva cifru prihoda — UX propust
**Fajl:** `web/templates/stranice/dashboard.html:1623`
**Problem:** Cifra prihoda ima `opacity: 0` po defaultu i prikazuje se tek posle **1 sekunde hover-a**:
```css
.prihod-cifra {
opacity: 0;
transition: opacity 0.3s ease;
}
.dash-stat:hover .prihod-cifra {
opacity: 1;
transition-delay: 1s; /* ← 1 sekunda čekanja */
}
```
Labela "Prihod ovog meseca" je vidljiva, ali cifra nije. Na mobilnim uređajima (bez hover) cifra je **trajno nevidljiva**.
**Posledica:** Korisnik ne može pročitati prihod na mobilnom uređaju. Na desktopu mora da zna da treba da čeka sekund na kartici.
---
## Sažetak
| # | Opis | Ozbiljnost | Fajl |
|---|---|---|---|
| BUG-01 | Stornirani nalozi u prihodu | Kritičan | `izvestaj.go:4143` |
| BUG-02 | Prodaja SA PDV / servis BEZ PDV | Kritičan | `izvestaj.go:3948` |
| BUG-03 | Delovi servisa izostaju kod ručne cene | Važan | `izvestaj.go:4448` |
| BUG-04 | Duplo računanje delova u naplaćenom iznosu | Važan | `servis.go:20342083` |
| BUG-05 | CSS skriva cifru na mobilnom | UX | `dashboard.html:1623` |
> BUG-01 do BUG-05 su ISPRAVLJENI u ovoj sesiji.
---
# Novi nalazi — pregled koda 2026-06-27
Drugi krug pregleda, posle ispravki BUG-01..05.
> **BUG-06 do BUG-10 su ISPRAVLJENI i pokriveni testovima** (2026-06-27).
>
> | # | Ispravka | Test |
> |---|---|---|
> | 06/07 | `PdvKir.DodajNeto` — neto osnovica + PDV po stvarnoj stopi; servis KIR ga koristi | `TestPdvKirDodajNeto` |
> | 08 | `TopKlijenti` servis: `naplaceno+avans WHERE status='Preuzeto'` | `TestTopKlijenti_SamoPreuzetiNalozi` |
> | 09 | `kljuceviMeseci` sidri na 1. u mesecu | `TestKljuceviMeseci` |
> | 10 | prihod servisa = `SUM(naplaceno + COALESCE(avans,0))`, filter `(naplaceno>0 OR avans>0)` | `TestPrihodTekuciMesec_ServisSaAvansom`, `...PotpunoAvansiran`, `...GarancijaNeUlazi` |
## BUG-06 — Servis KIR: neto cena tretirana kao bruto ⚠️ KRITIČAN (poreski)
**Fajl:** `internal/handler/servis.go:21142133` (auto-upis u KIR pri prelasku u „Preuzeto")
**Problem:** Kod računa osnovicu i PDV deljenjem sa 1.2, tretirajući `r.Ukupno()` kao bruto:
```go
osnovica := r.Ukupno() / 1.2 // 20% PDV
pdv := r.Ukupno() - osnovica
kir.Ukupno += r.Ukupno()
```
Ali `cena_komada` rada/dela je **NETO** (bez PDV-a) — potvrđeno u `servisni_radovi.go:40`:
```go
rad.CenaSaPdv = rad.CenaKomada * (1 + rad.PdvStopa/100)
```
Dakle `r.Ukupno() = kolicina × cena_komada` je već osnovica (neto). Deljenjem neto vrednosti sa 1.2 dobija se **premala osnovica**, a PDV se računa na pogrešnu (umanjenu) bazu. `kir.Ukupno` je zapravo neto, iako kolona „Ukupno" treba da bude bruto sa PDV-om.
**Primer (rad 1000 din neto, 20%):**
- Tačno: osnovica 1000, PDV 200, ukupno 1200
- Kod daje: osnovica 833.33, PDV 166.67, ukupno 1000
**Poređenje:** `KirIzProdaje` (`model/pdv_evidencija.go:98116`) to radi **ispravno** — ali tamo je `cena_po_komadu` bruto, pa je deljenje opravdano. Servis je samo prekopirao obrazac bez korekcije za neto cenu.
**Ispravka:** Za servis koristiti `r.UkupnoSaPdv()` kao bruto, ili direktno: `osnovica = r.Ukupno()`, `pdv = r.Ukupno() * stopa/100`.
---
## BUG-07 — Servis KIR: hardkodovana stopa 20%, ignoriše PdvStopa ⚠️ VAŽAN (poreski)
**Fajl:** `internal/handler/servis.go:2118, 2128`
**Problem:** Stopa je fiksirana na `/ 1.2` (20%) iako i rad i deo imaju polje `PdvStopa`. Sve stavke se sabiraju u `OsnovicaOpsta`/`PdvOpsta`, bez razvrstavanja na opštu (20%), posebnu (10%) i oslobođen promet.
`KirIzProdaje` to radi ispravno preko `switch s.PdvStopa { case 20 … case 10 … default … }`. Servisni KIR ne. Usluga/deo sa 10% ili 0% PDV biće pogrešno evidentiran.
**Ispravka:** Razvrstati po `rad.PdvStopa` / `deo.PdvStopa` kao u `KirIzProdaje`.
---
## BUG-08 — TopKlijenti: servis bez statusa + neto cena, nedosledno ⚠️ SREDNJI
**Fajl:** `internal/db/sqlite/izvestaj.go:228231`
**Problem:** Podupit za servis i dalje koristi:
```sql
SELECT klijent_id, SUM(cena_konacna) FROM servisni_nalozi
WHERE cena_konacna IS NOT NULL GROUP BY klijent_id
```
- **Nema filtera statusa** → broji i naloge koji još nisu preuzeti („U popravci", „Čeka delove"), pa i one koji nikad neće biti naplaćeni.
- Koristi **neto** `cena_konacna` (bez delova, posle BUG-04 fixa), dok dashboard i mesečni grafikon sada koriste **bruto** `naplaceno`.
Posledica: isti klijent ima različitu „ukupnu vrednost" na različitim ekranima.
**Ispravka:** Uskladiti sa ostatkom: `SUM(naplaceno) WHERE status='Preuzeto' AND naplaceno > 0`. (Prodajni podupit je već usklađen sa `stornirano = 0`.)
---
## BUG-09 — Izveštaji: 12-mesečni grafikon preskače mesece na kraju meseca ⚠️ LATENTAN
**Fajl:** `internal/handler/izvestaji.go:117119`
**Problem:** Petlja gradi ključeve meseci preko `sada.AddDate(0, -i, 0)`. Go-ov `AddDate` normalizuje prelivanje dana: npr. 31. mart 1 mesec = „31. februar" → 3. mart. Na 29/30/31. u mesecu neki mesec se **duplira**, a drugi (npr. februar) dobije ključ koji se nikad ne generiše → prikaže 0.
Ne manifestuje se danas (27.), ali se javlja svakog 2931. u mesecu.
**Ispravka:** Računati ključ preko prvog u mesecu, npr. `time.Date(god, mesec, 1, …)` sa ručnim oduzimanjem meseci, ili `AddDate` nad `BeginningOfMonth`.
---
## BUG-10 — Avans se ne uračunava u prihod (regresija od fixa BUG-02/03) ⚠️ VAŽAN
**Fajl:** `internal/db/sqlite/izvestaj.go:4649` (i `MesecniPrihodServis`)
**Problem:** Prihod od servisa sada je `SUM(naplaceno)`. Ali `naplaceno` je iznos naplaćen **pri preuzimanju** = `(cena_konacna + delovi) avans` (`servis.go:20692073`). Avans je naplaćen ranije i nigde se ne evidentira kao zaseban prihod.
Dve posledice:
1. Za naloge sa avansom, deo prihoda pokriven avansom **nedostaje** iz „prihoda meseca". Stari kod (`cena_konacna`) je taj deo uključivao.
2. Filter `naplaceno > 0` (dodat da izbaci garancijske popravke) **potpuno izbacuje** naloge gde je avans pokrio ceo iznos (`naplaceno = 0`) — iako su plaćeni.
**Napomena:** Ovo je svesni kompromis mog prethodnog fixa (`naplaceno` je gotovinski tačan za naloge bez avansa). Treba odlučiti definiciju prihoda. Ako je cilj ukupan prihod: `SUM(naplaceno + COALESCE(avans, 0))` za `status='Preuzeto'`, uz uslov `(naplaceno > 0 OR avans > 0)` umesto samo `naplaceno > 0`.