diff --git a/BUG.md b/BUG.md
deleted file mode 100644
index a1b6b36..0000000
--- a/BUG.md
+++ /dev/null
@@ -1,234 +0,0 @@
-# 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:39–48`
-
-**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:39–48`
-
-**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:188–191`:
-```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:44–48`, `internal/handler/servis.go:1498–1500`
-
-**Problem:** Šablon servisa tretira `cena_konacna` kao cenu rada (bez delova):
-
-```go
-// servis.go:1498–1500
-} 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:2041–2048`) 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:2034–2083`
-
-**Problem:** Kada servisni nalog nema unesenu `cena_konacna` i prelazi u status "Preuzeto", dešava se:
-
-1. Auto-izračun (l.2037–2049): `cena_konacna = dijagnostika + radovi + **delovi**` → snima u DB
-2. Izračun naplaćenog iznosa (l.2064–2073): č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:16–23`
-
-**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:41–43` |
-| BUG-02 | Prodaja SA PDV / servis BEZ PDV | Kritičan | `izvestaj.go:39–48` |
-| BUG-03 | Delovi servisa izostaju kod ručne cene | Važan | `izvestaj.go:44–48` |
-| BUG-04 | Duplo računanje delova u naplaćenom iznosu | Važan | `servis.go:2034–2083` |
-| BUG-05 | CSS skriva cifru na mobilnom | UX | `dashboard.html:16–23` |
-
-> 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:2114–2133` (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:98–116`) 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:228–231`
-
-**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:117–119`
-
-**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 29–31. 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:46–49` (i `MesecniPrihodServis`)
-
-**Problem:** Prihod od servisa sada je `SUM(naplaceno)`. Ali `naplaceno` je iznos naplaćen **pri preuzimanju** = `(cena_konacna + delovi) − avans` (`servis.go:2069–2073`). 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`.
diff --git a/Koncept.md b/Koncept.md
deleted file mode 100644
index adfdfae..0000000
--- a/Koncept.md
+++ /dev/null
@@ -1,1009 +0,0 @@
-# Koncept fiskalizacije — NTech + L-PFR
-
-Dokument opisuje arhitekturu fiskalizacije prema Tehničkom vodiču za L-PFR/ESIR
-i plan implementacije za NTech + Fisk mock server.
-
----
-
-## 1. Učesnici u sistemu
-
-```
-┌──────────┐ zahtev za ┌──────────┐ potpisivanje ┌──────────┐
-│ NTech │─── fiskalizaciju ───→│ L-PFR │─── (APDU komande)──→│ Kartica │
-│ (ESIR) │ │ (mock) │ │ (BE) │
-│ │←── fiskalni račun ──│ │←── potpisani podaci─│ │
-└──────────┘ └──────────┘ └──────────┘
- │
- │ paketi za iščitavanje
- ↓
- ┌──────────┐
- │ SUF │
- │ (PU RS) │
- └──────────┘
-```
-
-| Komponenta | Šta je | Gde se nalazi |
-|---|---|---|
-| **ESIR** (NTech) | Elektronski sistem za izdavanje računa — kasa, POS | Kod obveznika |
-| **L-PFR** (mock) | Lokalni procesor fiskalnih računa — "crna kutija" | Kod obveznika |
-| **BE** (kartica) | Bezbednosni element na pametnoj kartici | Kod obveznika (fizički) |
-| **SUF** | Sistem Uprave za Fiskalizaciju — Poreska uprava | Cloud PU RS |
-
-## 2. Bezbednosni element — pametna kartica (BE)
-
-Kartica je **jedini izvor identiteta** obveznika u sistemu fiskalizacije.
-Nju izdaje Poreska uprava, personalizovana je za konkretnog obveznika
-i poslovni prostor.
-
-### 2.1 Šta se nalazi na kartici
-
-| Podatak | Opis | Primer |
-|---|---|---|
-| **JID** | Jedinstveni identifikator — 8 alfanumeričkih znakova | `TRNMOCK1` |
-| **PIB** | Poreski identifikacioni broj obveznika | `123456789` |
-| **Naziv firme** | Poslovno ime obveznika | `Dalibor DOO` |
-| **Poslovni prostor** | Adresa, grad, opština | `Beograd, Savski Venac` |
-| **Sertifikat** | Digitalni sertifikat (validan ~3 godine) | `2024-01-01 → 2027-01-01` |
-| **PIN** | Lični identifikacioni broj za pristup kartici | `1234` |
-| **Brojači** | Ukupan broj računa + po tipu transakcije | `total, pp, pr, ap, kp...` |
-| **Limit iščitavanja** | Maksimalni neisčitani iznos pre blokade | definiše PU |
-| **Trenutni neisčitani iznos** | Akumulirani iznos od poslednjeg iščitavanja | resetuje se dokazom |
-| **Ključevi** | Kriptografski ključevi za digitalno potpisivanje | par privatni/javni |
-
-### 2.2 Životni ciklus kartice
-
-1. Obveznik se uvodi u eFiskalizaciju preko portala ePorezi
-2. Obveznik zahteva izdavanje BE preko ESF (Elektronski servisi za fiskalizaciju)
-3. PU personalizuje karticu (upisuje sertifikat, PIB, JID, limit)
-4. Kartica se fizički dostavlja obvezniku
-5. Obveznik ubacuje karticu u L-PFR i unosi PIN
-6. Kartica potpisuje račune dok ne dostigne limit → potrebno iščitavanje
-7. Promena adrese poslovnog prostora → nova kartica
-8. Odjava poslovnog prostora → opoziv svih kartica za taj prostor
-
-### 2.3 Ograničenja kartice
-
-- Limit iščitavanja — kad neisčitani iznos dostigne limit, kartica **blokira** potpisivanje
-- Dokaz iščitavanja (Proof of Audit) sa SUF-a resetuje limit
-- Bez interneta: radi u oflajn režimu, akumulira neisčitane račune
-- Kartica NE može da se čita direktno — samo L-PFR komunicira sa njom
-
-## 3. L-PFR — Lokalni procesor fiskalnih računa
-
-L-PFR je softver/hardver koji radi po principu "crne kutije".
-Komunicira samo sa:
-- ESIR-om (prima zahteve, vraća fiskalne račune)
-- BE karticom (potpisivanje, brojači)
-- SUF-om (iščitavanje — internet ili lokalno)
-
-### 3.1 Odgovornosti L-PFR-a
-
-| Funkcija | Opis |
-|---|---|
-| Prijem zahteva | Prima podatke o transakciji od ESIR-a (JSON) |
-| PDV obračun | Računa poresku obavezu po stopama (Ж=20%, Ђ=10%, А=0%) |
-| Potpisivanje | Prosleđuje podatke kartici, dobija digitalni potpis |
-| Generisanje broja | Formira jedinstveni broj računa: `{JID_ESIR}-{JID_BE}-{brojač}` |
-| QR kod | Generiše verifikacioni URL i QR kod (40×40mm do 50×50mm) |
-| Čuvanje | Pamti sve račune u internoj memoriji (neizbrisivo) |
-| Iščitavanje | Šalje pakete za iščitavanje u SUF (internet ili USB/SD) |
-| Blokada | Kad BE dostigne limit, blokira izdavanje novih računa |
-
-### 3.2 Vrste L-PFR-a
-
-| Vrsta | Opis |
-|---|---|
-| **Hardverski L-PFR** | Fizički uređaj sa čitačem kartica, ekranom, štampačem |
-| **Softverski L-PFR** | Softver instaliran na računaru obveznika |
-| **Razvojni L-PFR** | Softverska simulacija za development i testiranje ESIR-a |
-
-Naš Fisk mock je **Razvojni L-PFR**.
-
-### 3.3 Tipovi računa i transakcija
-
-| Tip računa | Oznaka | Transakcija | Brojač | Fiskalni? |
-|---|---|---|---|---|
-| Normal | ПП | Sale (Prodaja) | pp | Da |
-| Normal | ПР | Refund (Refundacija) | pr | Da |
-| Advance | АП | Sale | ap | Da |
-| Advance | АР | Refund | ar | Da |
-| Copy | КП | Sale | kp | Ne |
-| Copy | КР | Refund | kr | Ne |
-| Training | ОП | Sale | op | Ne |
-| Training | ОР | Refund | or | Ne |
-
-## 4. ESIR — Elektronski sistem za izdavanje računa (NTech)
-
-ESIR je ono što koristi kasir. Njegove odgovornosti:
-
-1. Prikuplja podatke o transakciji (artikli, količine, cene, plaćanje)
-2. Formira zahtev za fiskalizaciju i šalje ga L-PFR-u
-3. Prima fiskalni račun od L-PFR-a
-4. Prikazuje/štampa račun kupcu (sa QR kodom)
-5. NE potpisuje račun — to radi kartica preko L-PFR-a
-
-### 4.1 Šta ESIR šalje L-PFR-u (InvoiceRequest)
-
-```json
-{
- "invoiceRequest": {
- "invoiceType": "Normal",
- "transactionType": "Sale",
- "cashier": "Dalibor",
- "buyerId": "10:123456789",
- "items": [
- {
- "name": "RAM 16GB DDR4",
- "labels": ["Ж"],
- "totalAmount": 7200.00,
- "unitPrice": 7200.00,
- "quantity": 1.000
- }
- ],
- "payment": [
- {
- "amount": 7200.00,
- "paymentType": "Cash"
- }
- ]
- }
-}
-```
-
-**Napomena:** Podaci o firmi (PIB, naziv, adresa) su NA KARTICI (BE), ne u ESIR-u.
-L-PFR ih čita sa kartice i stavlja u fiskalni račun. ESIR ih NE šalje.
-
-### 4.2 Šta L-PFR vraća ESIR-u (InvoiceResponse)
-
-```json
-{
- "requestedBy": "NTECH001",
- "signedBy": "TRNMOCK1",
- "sdcDateTime": "2026-06-26T13:00:00.000+02:00",
- "invoiceNumber": "NTECH001-TRNMOCK1-42",
- "invoiceCounter": "3/42ПП",
- "invoiceCounterExtension": "ПП",
- "verificationUrl": "https://sandbox.suf.purs.gov.rs/v/?vl=NTECH001-TRNMOCK1-42",
- "verificationQRCode": "iVBORw0KG...",
- "totalAmount": 7200.00,
- "totalTax": 1200.00,
- "taxItems": [
- {"label": "Ж", "categoryName": "PDV", "rate": 20.0, "amount": 1200.00}
- ],
- "messages": "Success",
- "journal": "========= FISKALNI RAČUN =========\n..."
-}
-```
-
-## 5. Tok fiskalizacije — korak po korak
-
-```
- ESIR (NTech) L-PFR (mock) Kartica (BE)
- │ │ │
- │ 1. POST /api/invoices │ │
- │ {invoiceRequest: {...}} │ │
- │────────────────────────────────→│ │
- │ │ │
- │ │ 2. Čita firmu sa kartice │
- │ │ (PIB, naziv, adresa) │
- │ │─────────────────────────────→│
- │ │ │
- │ │ 3. Provera PIN-a (ako treba)│
- │ │─────────────────────────────→│
- │ │←─────────────────────────────│
- │ │ │
- │ │ 4. Potpisivanje podataka │
- │ │ (APDU komanda) │
- │ │─────────────────────────────→│
- │ │←── potpis + novi brojač ─────│
- │ │ │
- │ │ 5. L-PFR računa PDV, │
- │ │ generiše QR, broj računa │
- │ │ │
- │ 6. Fiskalni račun + QR │ │
- │←────────────────────────────────│ │
- │ │ │
- │ 7. NTech čuva fiskalni račun │ 8. Paket za iščitavanje │
- │ prikazuje/štampa kupcu │ čeka slanje u SUF │
- │ │ │
-```
-
-## 6. Komunikacija L-PFR ↔ kartica (BE)
-
-### Kako to radi u stvarnosti
-
-U pravom sistemu L-PFR ima **fizički čitač kartica**. Kartica je **pasivna** —
-ne inicira ništa, ne "zove" nikoga. L-PFR je gospodar: šalje APDU komande
-(ISO 7816) kartici kroz čitač, kartica odgovara.
-
-```
-Pravi sistem:
-L-PFR ──APDU komanda──→ čitač kartica → kartica (BE)
-L-PFR ←────────── odgovor (bajtovi) ────── kartica (BE)
-```
-
-Kartica je u suštini **server koji čeka komande** — samo ne sluša na mreži
-nego na fizičkom interfejsu čitača.
-
-### Naš mock preslikava isti smer
-
-Kartica emulator **sluša** na TCP portu. Fisk (L-PFR mock) se **spaja** na nju
-i šalje JSON komande — isti smer kao u stvarnosti, samo TCP+JSON umesto
-fizički+APDU.
-
-Kartica emulator je implementirana unutar NTech Go binarnog fajla (goroutine)
-jer tako ima direktan pristup bazi podataka za podatke o firmi, bez deljenja
-fajlova između kontejnera.
-
-```
-┌──────────────────────────┐ HTTP :3000 ┌──────────────┐
-│ NTech (Go) │←─────────────→│ Fisk │
-│ ├── ESIR logika │ │ (L-PFR mock)│
-│ └── kartica emulator │←──────────────│ :4566 │
-│ sluša na :4567 │ TCP :4567 │ │
-└──────────────────────────┘ JSON komande └──────────────┘
-```
-
-Fisk se spaja na `ntech:4567`, šalje komandu, čeka odgovor, zatvara konekciju.
-NTech kartica emulator nikad ne inicira — samo odgovara.
-
-### 6.1 API kartice — 4 komande
-
-Sva komunikacija: JSON linija (`\n` terminated), request-response.
-
-#### `status` — pročitaj brojače, limit, status
-
-```
-→ {"command":"status"}
-← {
- "status":"ok",
- "total_counter": 42,
- "counters": {"pp":3,"pr":1,"ap":0,"ar":0,"kp":0,"kr":0,"op":0,"or":0},
- "limit": 500000,
- "unread_amount": 150000
- }
-```
-
-#### `certificate` — identitet kartice (firma, PIB, JID)
-
-```
-→ {"command":"certificate"}
-← {
- "status":"ok",
- "jid": "TRNMOCK1",
- "tin": "RS123456789",
- "tin_plain": "123456789",
- "name": "Dalibor DOO",
- "address": "Test Adresa 1",
- "city": "Beograd",
- "district": "Savski Venac",
- "business_unit_id": "BU-001",
- "location_name": "Dalibor DOO",
- "valid_from": "2024-01-01T00:00:00+01:00",
- "valid_to": "2027-01-01T00:00:00+01:00",
- "issuer": "Poreska uprava RS"
- }
-```
-
-#### `verify_pin` — provera PIN-a pre pristupa
-
-```
-→ {"command":"verify_pin", "pin":"1234"}
-← {"status":"ok"}
- // pogrešan PIN:
-← {"status":"error", "code":"2100", "message":"Pogrešan PIN"}
-```
-
-#### `sign` — potpiši račun, inkrementiraj brojač
-
-```
-→ {
- "command": "sign",
- "invoice_type": "Normal",
- "transaction_type": "Sale",
- "total_amount": 7200.00
- }
-← {
- "status": "ok",
- "counter": 42,
- "counter_extension": "ПП",
- "type_counter": 3,
- "signature": "base64...",
- "blocked": false
- }
- // ako je limit dostignut:
-← {"status":"blocked", "message":"Limit iščitavanja dostignut"}
-```
-
-### 6.2 Šta izlazi iz mocka u emulator kartice
-
-| Podatak | Gde je sad (mock) | Gde treba (kartica emulator) |
-|---|---|---|
-| `PIN_BE = "1234"` | server.py:40 | kartica emulator |
-| `BE_ID = "TRNMOCK1"` | server.py:27 | kartica emulator — `jid` |
-| `ucitaj_firmu()` (naziv, PIB, adresa...) | server.py:47-77 | kartica emulator — `certificate` |
-| `get_counter()` / `peek_counter()` | server.py:81-92 | kartica emulator — `sign` / `status` |
-| `counter_ext()` | server.py:94-107 | kartica emulator — `sign` |
-| `resp_certificate()` | server.py:221-230 | kartica emulator — `certificate` |
-| `resp_verify_pin()` | server.py:194-204 | kartica emulator — `verify_pin` |
-
-### 6.3 Šta ostaje u mocku
-
-| Funkcija | Ostaje u mocku? |
-|---|---|
-| Prijem HTTP zahteva od ESIR-a (`/api/invoices`, ...) | ✅ da |
-| `TAX_RATES`, `izracunaj_pdv()` | ✅ da |
-| Generisanje QR koda | ✅ da |
-| Generisanje `invoiceNumber` (`{ESIR_ID}-{JID}-{counter}`) | ✅ da — JID dobija od kartice |
-| Snimanje računa (JSON, txt, html, QR PNG) | ✅ da |
-| `generate_receipt()`, `generate_receipt_html()` | ✅ da |
-| CORS, logging, rutiranje | ✅ da |
-| `ucitaj_firmu()` | ❌ zamenjuje se pozivom `certificate` ka kartici |
-| Brojači | ❌ idu na karticu |
-| PIN verifikacija | ❌ ide na karticu |
-
-### 6.4 Kako mock poziva karticu (Python)
-
-```python
-import socket, json
-
-SOCKET_PATH = "/tmp/ntech-be.sock"
-
-def be_command(cmd: dict) -> dict:
- s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- s.connect(SOCKET_PATH)
- s.sendall((json.dumps(cmd) + "\n").encode())
- resp = b""
- while True:
- chunk = s.recv(4096)
- if not chunk:
- break
- resp += chunk
- if b"\n" in resp:
- break
- s.close()
- return json.loads(resp.decode())
-```
-
-### 6.5 Novi tok fiskalizacije sa emulatorom
-
-```
- ESIR (NTech) L-PFR (mock) Kartica (Go)
- │ │ │
- │ POST /api/invoices │ │
- │────────────────────→│ │
- │ │ │
- │ │ 1. be_command({ │
- │ │ "command":"certificate"│
- │ │ }) │
- │ │─────────────────────────→│
- │ │←── firma, PIB, JID ──────│
- │ │ │
- │ │ 2. be_command({ │
- │ │ "command":"sign", │
- │ │ "invoice_type":"Normal│
- │ │ "transaction_type":...│
- │ │ "total_amount":7200 │
- │ │ }) │
- │ │─────────────────────────→│
- │ │←── counter, ext, potpis ─│
- │ │ │
- │ │ 3. računa PDV, generiše │
- │ │ QR, broj računa │
- │ │ │
- │←── fiskalni račun ──│ │
- │ │ │
-```
-
-**Napomena — `verify_pin`:** PIN se proverava **jednom pri pokretanju** kartice (ili kad `isPinRequired=True` u `/api/status`), ne per-invoice. Komanda `verify_pin` postoji u API-ju kartice radi inicijalizacije i ponovnog otključavanja, ali je ne pozivamo u toku normalnog izdavanja računa.
-
-### 6.6 Šta radimo u NTech-u (Go) — emulator kartice
-
-Novi paket: `internal/be/` ili `cmd/karticab`:
-
-```
-internal/be/
-├── server.go # Unix socket listener, JSON parser
-├── kartica.go # stanje kartice: brojači, limit, PIN, firma
-└── config.go # env varijable
-```
-
-Tipovi podataka za karticu (JSON wire format):
-
-```go
-type StatusResponse struct {
- Status string `json:"status"`
- TotalCounter int `json:"total_counter"`
- Counters map[string]int `json:"counters"`
- Limit float64 `json:"limit"`
- UnreadAmount float64 `json:"unread_amount"`
-}
-
-type CertificateResponse struct {
- Status string `json:"status"`
- JID string `json:"jid"`
- TIN string `json:"tin"`
- TINPlain string `json:"tin_plain"`
- Name string `json:"name"`
- Address string `json:"address"`
- City string `json:"city"`
- District string `json:"district"`
- BusinessUnitID string `json:"business_unit_id"`
- LocationName string `json:"location_name"`
- ValidFrom string `json:"valid_from"`
- ValidTo string `json:"valid_to"`
- Issuer string `json:"issuer"`
-}
-
-type SignResponse struct {
- Status string `json:"status"`
- Counter int `json:"counter"`
- CounterExtension string `json:"counter_extension"`
- TypeCounter int `json:"type_counter"`
- Signature string `json:"signature"`
- Blocked bool `json:"blocked"`
-}
-```
-
-Pokretanje kartice (zaseban proces):
-
-```bash
-NTECH_BE_SOCKET=/tmp/ntech-be.sock \
-NTECH_BE_PIN=1234 \
-NTECH_BE_JID=TRNMOCK1 \
-NTECH_BE_FIRMA_NAZIV="Dalibor DOO" \
-NTECH_BE_PIB=123456789 \
-NTECH_BE_ADRESA="Test Adresa 1" \
-NTECH_BE_GRAD=Beograd \
-go run ./cmd/kartica/
-```
-
-### 6.7 Docker
-
-Kartica emulator je goroutine unutar NTech-a — nema zasebnog kontejnera.
-NTech expose-uje dva porta: `3000` za ESIR HTTP i `4567` za karticu.
-
-```yaml
-services:
- ntech:
- build: .
- ports:
- - "3000:3000"
- - "4567:4567" # kartica emulator (goroutine unutar NTech-a)
- environment:
- - BE_PORT=4567
- - BE_PIN=1234
- - BE_JID=TRNMOCK1
-
- fisk:
- build: ./Fisk
- ports:
- - "4566:4566"
- environment:
- - BE_HOST=ntech # hostname NTech kontejnera u Docker mreži
- - BE_PORT=4567
- - ESIR_ID=NTECH001
-```
-
-## 7. Poznati bugovi u trenutnom mocku (pre refaktora)
-
-### 7.1 QR kod u HTML računu je prazan — `receipt.py:370`
-
-```python
-# Trenutno (pogrešno):
-qr_src = f"data:image/png;base64,{inv.get('qrCode', '')}"
-
-# Ispravno — polje se zove verificationQRCode:
-qr_src = f"data:image/png;base64,{inv.get('verificationQRCode', '')}"
-```
-
-### 7.2 `invoiceCounter` prikazuje `invoiceNumber` — `receipt.py:252-253`
-
-```python
-# Trenutno (oba reda prikazuju invoiceNumber):
-lines.append(layout(..., str(inv.get("invoiceNumber", "")), W))
-lines.append(layout(..., str(inv.get("invoiceNumber", "")), W))
-
-# Ispravno — drugi red:
-lines.append(layout(m.get("sdc-invoice-counter", "Brojač računa"), str(inv.get("invoiceCounter", "")), W))
-```
-
-## 8. Napomena — podaci firme
-
-Kartica emulator je goroutine unutar NTech procesa, pa ima direktan pristup
-NTech bazi podataka. Pri pokretanju čita podatke firme iz `podesavanja` tabele
-(naziv, PIB, adresa...) — isto što `ucitaj_firmu()` radi u Python mocku danas,
-samo direktno bez deljenja fajlova između kontejnera.
-
-Ovo je ispravno modelovanje: prava kartica ima zamrznute podatke upisane pri
-personalizaciji. Ako se adresa ili naziv firme promeni u NTech podešavanjima,
-treba restartovati NTech (i kartica emulator se pokreće ponovo sa novim podacima).
-
----
-
-## 9. Funkcije u Fisk/ Python mocku
-
-### 9.1 server.py — L-PFR server
-
-| Funkcija | Linija | Opis |
-|---|---|---|
-| `ucitaj_firmu()` | ~47 | Čita naziv, PIB, adresu iz NTech SQLite-a (env `NTECH_SQLITE`); fallback na test vrednosti |
-| `get_counter(tip)` | ~81 | Inkrementira i vraća brojač iz fajla u `COUNTER_DIR`; tip = "pp", "pr", "ap"... |
-| `peek_counter(tip)` | ~87 | Čita brojač bez inkrementiranja |
-| `counter_ext(invoice_type, transaction_type)` | ~94 | Vraća ćirilični sufiks (ПП, ПР, АП...) i ključ za brojač |
-| `izracunaj_pdv(items)` | ~110 | Grupiše stavke po poreskoj oznaci; formula: `pdv = bruto * stopa / (100 + stopa)` |
-| `_build_invoice_response(req, request_id)` | ~130 | **Glavni builder** — računa PDV, poziva karticu, generiše invoiceNumber, QR, snima JSON/txt/html/PNG |
-| `resp_certificate()` | ~221 | Vraća identitet (firma, PIB, JID) — u budućnosti delegira kartici |
-| `resp_verify_pin(body)` | ~194 | Proverava PIN — u budućnosti delegira kartici |
-| `resp_status()` | ~170 | Vraća L-PFR status: `isPinRequired`, `lastInvoiceNumber`, TIN |
-| `resp_attention()` | ~165 | Health check — vraća `"status":"ok"` |
-| `resp_settings_get/post()` | ~240 | Teron podešavanja (poreske stope, locale) |
-| `resp_invoice_final()` | ~260 | Konačni račun koji zatvara avansne transakcije |
-| `resp_invoice_last/by_request/by_number/search()` | ~280+ | Pregled ranije izdatih računa |
-
-**Konstante:**
-- `TAX_RATES`: `{"Ж":20, "Ђ":10, "Е":10, "А":0, "Г":0, "З":0}` + generičke oznake
-- `BE_ID = "TRNMOCK1"` — JID bezb. elementa
-- `ESIR_ID = "NTECH001"` — ESIR identifikator
-- `PIN_BE = "1234"` — PIN kartice
-- `ROUTES` — 12 HTTP ruta, Teron API kompatibilne
-
-### 9.2 receipt.py — generator fiskalnih računa
-
-| Funkcija | Opis |
-|---|---|
-| `generate_receipt(invoice_data, lang)` | Tekstualni račun širine 48 znakova, prati `agent-invoice.vm` template |
-| `generate_receipt_html(invoice_data, lang)` | HTML za A4 štampu, monospace font, `@page size A4` |
-| `generate_report(report_data, lang)` | Dnevni/periodični izveštaj (standard-report.vm) |
-| `load_locale(lang)` | Učitava `locale_latin.properties` ili `locale_cyrillic.properties` iz `data/` |
-| `price()`, `qty()`, `amount()` | Formatiranje brojeva (2 decimale, hiljade sa tačkom) |
-| `center(text, width)` | Centriranje teksta na širini |
-| `layout(levo, desno, width)` | Red sa levim i desnim tekstom, popunjen razmacima |
-| `wrap(text, width)` | Prelom dugog teksta u više redova |
-| `separator(width)` | Crtana linija razdvajača |
-| `title(text, width)` | Naslov uokviren u `== tekst ==` |
-
-**Mapiranje tipova:**
-- `TRANSACTION_TYPES_CYR` i `TRANSACTION_TYPES_LAT` — prevod `"Normal/Sale"` → `"ПРОМЕТ ПРОДАЈЕ"` / `"PROMET PRODAJE"`
-
----
-
-## 10. Detaljan tok dobijanja fiskalnog računa
-
-### 10.1 Normalni fiskalni račun (Promet Prodaja — ПП)
-
-**Preduslov:** Kartica ubačena, PIN prihvaćen (jednom pri pokretanju).
-
-```
-ESIR (NTech) L-PFR (Fisk) BE (Kartica) SUF
- │ │ │ │
- │ 1. Kasir završava │ │ │
- │ transakciju u NTech │ │ │
- │ │ │ │
- │ 2. POST /api/invoices │ │ │
- │ {invoiceRequest} │ │ │
- │─────────────────────────→│ │ │
- │ │ │ │
- │ │ 3. certificate │ │
- │ │─────────────────────────→│ │
- │ │←── PIB, naziv, adresa ───│ │
- │ │ │ │
- │ │ 4. sign {type, amount} │ │
- │ │─────────────────────────→│ │
- │ │ BE: proverava limit │ │
- │ │ BE: ažurira brojač │ │
- │ │ BE: generiše potpis │ │
- │ │←── {counter, ext, sig} ──│ │
- │ │ │ │
- │ │ 5. računa PDV po stopama │ │
- │ │ generiše invoiceNumber│ │
- │ │ generiše QR kod │ │
- │ │ snima račun interno │ │
- │ │ │ │
- │ 6. {invoiceResponse} │ │ │
- │←─────────────────────────│ │ │
- │ │ │ │
- │ 7. prikazuje/štampa │ │ │
- │ kupcu (tekst + QR) │ │ │
- │ │ │ │
- │ │ 8. asinhro — kad ima net:│ │
- │ │ šalje paket ──────────┼────────────────→│
- │ │←──────────────────────── Proof of Audit ───│
-```
-
-### 10.2 Format ključnih polja u odgovoru
-
-| Polje | Format | Primer | Ko generiše |
-|---|---|---|---|
-| `invoiceNumber` | `{ESIR_JID}-{BE_JID}-{ukupan_br}` | `NTECH001-TRNMOCK1-42` | L-PFR |
-| `invoiceCounter` | `{br_ove_vrste}/{ukupan_br}{ext}` | `3/42ПП` | L-PFR + BE |
-| `sdcDateTime` | ISO 8601 sa zonom | `2026-06-26T13:00:00.000+02:00` | L-PFR |
-| `verificationUrl` | URL ka SUF portalu | `https://suf.purs.gov.rs/v/?vl=...` | L-PFR |
-| `verificationQRCode` | base64 PNG | `iVBORw0KG...` | L-PFR (qrcode lib) |
-| `totalTax` | float, zaokružen | `1200.00` | L-PFR |
-
-**ПФР broj (invoiceNumber) za L-PFR:** oba JID su isti (ESIR = BE su isti uređaj).
-Za V-PFR su različiti jer V-PFR ima sopstveni BE u cloudu.
-
-### 10.3 Avansni tok (AP → PP)
-
-```
-1. AP (Avanс Prodaja) — plaćen avanс
-2. AP može se ponavljati (više avansnih uplata) — svaki referencira prethodni AP
-3. PP (Promet Prodaja) — zatvaranje transakcije, referencira poslednji AP
- - PP MORA sadržati referentni broj poslednjeg AP
- - PP iznos = ukupno - plaćeni avans
-```
-
-**Lanac referenci:** AP1 ← AP2 ← PP (ili AP1 ← AR1 ← AP2 ← PP itd.)
-
-### 10.4 Refundacija (PR)
-
-```
-PR (Promet Refundacija) — MORA imati:
- - referenceDocumentNumber: PFR broj originalnog PP računa
- - buyerId: OBAVEZNO (identifikacija kupca za refundaciju)
-```
-
----
-
-## 11. Na šta treba voditi računa
-
-### 11.1 Podaci firme dolaze sa kartice, NE iz ESIR-a
-
-```
-❌ Pogrešno: ESIR šalje naziv/PIB/adresu u invoiceRequest
-✅ Ispravno: L-PFR čita sa kartice (certificate komanda), ESIR to ne zna
-```
-
-U mocku: `ucitaj_firmu()` čita iz SQLite. U emulatoru: Go goroutine čita iz iste baze.
-
-### 11.2 PDV se računa iz bruto iznosa (ne iz neto)
-
-```
-pdv = bruto * stopa / (100 + stopa)
-# za Ж (20%): pdv = 1000 * 20 / 120 = 166.67 (ne 200!)
-```
-
-Mock formula je ispravna. Greška nastaje ako se uzme `bruto * stopa / 100`.
-
-### 11.3 PIN se proverava jednom — ne per-invoice
-
-- Pin se unosi pri **ubacivanju kartice** (pokretanje L-PFR-a)
-- Ako je `isPinRequired=True` u `/api/status` — znači kartica je izvučena i ponovo ubačena
-- `verify_pin` komanda postoji ali se NE poziva za svaki račun
-
-### 11.4 Kartica se blokira kad dostigne limit iščitavanja
-
-- Limit je iznos (ne broj računa)
-- Kad se dostigne: `sign` vraća `{"status":"blocked"}`
-- Rešenje: iščitavanje (internet → SUF šalje Proof of Audit → resetuje brojač)
-- Bez interneta: lokalno iščitavanje (USB/SD → portal PU)
-- Za vreme iščitavanja: L-PFR može i dalje da izdaje račune (iščitavanje nije bloker)
-
-### 11.5 QR kod NE SME sadržati logo ni sliku
-
-Standard propisuje: QR kod za verifikaciju ne sme biti ispisан na slici ili logu,
-niti sadržati sliku ili logo unutar sebe. Minimalna veličina: 40×40mm, maksimalna: 50×50mm.
-
-### 11.6 Fiskalni dokumenti koji NISU fiskalni računi
-
-Sledeći tipovi NE registruju promet i moraju nositi napomenu **"OVO NIJE FISKALNI RAČUN"**
-(napisano duplo većim fontom):
-
-| Tip | Šta je |
-|---|---|
-| **Kopija (КП/КР)** | Kopija već izdatog računa |
-| **Obuka (ОП/ОР)** | Za trening kasira, testiranje ESIR-a |
-| **Predračun (РП/РР)** | Obaveštavanje kupca o budućem prometu |
-
-### 11.7 Referentni broj — kada je obavezan
-
-| Situacija | Ref. broj obavezan? |
-|---|---|
-| Promet Refundacija (PR) | **DA** — PFR broj originalnog PP |
-| Kopija | **DA** — PFR broj originalnog računa |
-| Promet Prodaja zatvaranje avansa | **DA** — PFR broj poslednjeg AP |
-| Avanс Prodaja (2. i naredni AP) | **DA** — PFR broj prethodnog AP |
-| Obični Promet Prodaja | NE |
-| Predračun | NE |
-
-### 11.8 Ime kasira dolazi iz ESIR-a, ne sa kartice
-
-```json
-"invoiceRequest": {
- "cashier": "Dalibor" // NTech šalje ovo
-}
-```
-
-Kartica ne zna ko je kasir — to je podatak transakcije. L-PFR kopira polje `cashier`
-iz zahteva direktno u odgovor/račun. Nije obavezno po standardu, ali korisno za evidenciju.
-
-### 11.9 ESIR broj vs PFR broj
-
-- **ESIR broj** (`buyerCostCenterId`, `referenceNumber`) — opcioni interni broj iz ESIR-a, npr. broj porudžbine
-- **PFR broj** (`invoiceNumber`) — jedinstven, nepromenljiv, kreira ga L-PFR; format: `{JID_ESIR}-{JID_BE}-{sekvenca}`
-- Referentni broj pri refundaciji = **PFR broj** originalnog, ne ESIR broj
-
-### 11.10 Bugovi u receipt.py — ✅ ispravljeni
-
-```python
-# BUG 1 — receipt.py:370 — QR kod je prazan u HTML prikazu — ISPRAVLJENO
-# Polje se zove verificationQRCode, ne qrCode
-qr_src = f"data:image/png;base64,{inv.get('verificationQRCode', '')}"
-
-# BUG 2 — receipt.py:252 — oba reda prikazuju invoiceNumber — ISPRAVLJENO
-str(inv.get("invoiceCounter", "")) # treći red sada koristi invoiceCounter
-```
-
-### 11.11 Docker — cross-container komunikacija
-
-Unix socket NE radi između Docker kontejnera bez deljenog volumena.
-Rešenje: kartica emulator sluša na TCP portu `4567` unutar NTech kontejnera.
-
-```
-✅ Fisk env: BE_HOST=ntech BE_PORT=4567
-✅ NTech expose-uje: "4567:4567"
-❌ Nemoj koristiti /tmp socket između kontejnera
-```
-
----
-
-## 12. Izgled fiskalnog računa — razlike i ispravke
-
-> **Status pregleda:** Sve GR greške su ispravljene. Videti §12.2 za detalje.
-> Dodatne ispravke (kasir, servis modal, Čeka delove) u §12.5.
-
-### 12.1 Referentni račun (OMV)
-
-Pravi fiskalni račun koji generiše Teron L-PFR (`Dokumenta/Račun.png`):
-
-```
-============ ФИСКАЛНИ РАЧУН ============
- 101987198
- OMV SRBIJA DOO BEOGRAD
- 1083363-Огранак БС Краљево
- ДУШАНА ПОПОВИЋА
- Краљево
-Касир: Radojka Vuković
-ЕСИР број: 700/11.71.0.0
------------ПРОМЕТ ПРОДАЈА-----------
- Артикли
-========================================
-Назив Цена Кол. Укупно
-OMV EP BMB 95 / l (Ђ)
- 179,07 16,570 2.967,17
-----------------------------------------
-Укупан износ: 2.967,17
-Платна картица: 2.967,17
-========================================
-Ознака Име Стопа Порез
-Ђ О-ПДВ 20,00% 494,53
-----------------------------------------
-Укупан износос пореза: 494,53
-========================================
-ПФР време: 18.12.2025. 11:28:25
-ПФР број рачуна: U92D83M9-U92D83M9-195100
-Бројач рачуна: 192838/195100ПП
-========================================
-[QR KOD — centriran, ~60mm x 60mm]
-======== КРАЈ ФИСKАЛНОГ РАЧУНА ========
-```
-
-### 12.2 Grešake i ispravke
-
-#### GR-1: Formatiranje brojeva — ✅ ISPRAVLJENO
-
-**Problem:** Python `f"{n:,.2f}"` daje američki format: `2,967.17`
-Srpski/evropski standard: `2.967,17` (tačka za hiljade, zarez za decimale)
-
-**Gde:** `receipt.py` — funkcije `price()`, `qty()`, `amount()`, `number()`
-
-**Ispravka:**
-```python
-def _sr(n, decimals=2):
- s = f"{n:,.{decimals}f}"
- return s.replace(",", "\x00").replace(".", ",").replace("\x00", ".")
-```
-Sve funkcije `price()`, `qty()`, `amount()`, `number()` sada koriste `_sr()`.
-
----
-
-#### GR-2: Pogrešno polje za Brojač računa — ✅ ISPRAVLJENO
-
-**Problem:** `receipt.py` linija 252 koristila `inv.get("invoiceNumber")` umesto `inv.get("invoiceCounter")`.
-Oba reda (PFR broj + Brojač) prikazivala isti `invoiceNumber`.
-
-**Ispravka:** Treći red sada koristi `inv.get("invoiceCounter", "")`.
-
----
-
-#### GR-3: Labele ne odgovaraju stvarnom računu — ✅ ISPRAVLJENO
-
-**Problem:** Locale fajlovi su imali pogrešne ili engleske labele.
-
-| Šta se prikazuje | Teron (stvarno) | Pre ispravke | Posle ispravke |
-|---|---|---|---|
-| Ukupan iznos | `Ukupan iznos` | `Za uplatu` | ✅ `Ukupan iznos` |
-| Platna kartica | `Platna kartica` | `Card` | ✅ `Platna kartica` |
-| Gotovina | `Gotovina` | `Cash` | ✅ `Gotovina` |
-| Čekovi | `Čekovi` | `Check` | ✅ `Čekovi` |
-| Vaučer | `Vaučer` | `Voucher` | ✅ `Vaučer` |
-| Instant plaćanje | `Instant plaćanje` | `MobileMoney` | ✅ `Instant plaćanje` |
-| Prenos na račun | `Prenos na račun` | `WireTransfer` | ✅ `Prenos na račun` |
-| Ostalo | `Ostalo` | `Drugo` | ✅ `Ostalo` |
-| PFR vreme | `PFR vreme` | `Vreme` | ✅ `PFR vreme` |
-| PFR broj računa | `PFR broj računa` | `Broj računa` | ✅ `PFR broj računa` |
-| Brojač računa | `Brojač računa` | `Brojač` | ✅ `Brojač računa` |
-
-**Ispravka:**
-- `Fisk/data/locale_latin.properties` i `locale_cyrillic.properties` — dodati `Cash=Gotovina`, `Card=Platna kartica` itd., ispraviti PFR labele
-- `receipt.py` — lookup za payment type: `p.get("paymentType", p.get("type", ""))` pa `m.get(pt, pt or "Ostalo")` (bez "Drugo" fallback-a)
-
----
-
-#### GR-4: QR kod nije centriran na računu — ✅ ISPRAVLJENO
-
-**Problem:** `StampaFiskalnog` renderovao ceo journal kao jedan `white-space:pre` blok. `` ugrađen unutar `
` ne može se centrirati CSS-om jer `white-space:pre` tretira razmake doslovno.
-
-**Gde:** `internal/handler/servis.go` — `StampaFiskalnog`
-
-**Ispravka:** Koristi se `strings.Cut(journal, "{{{{QR-KOD}}}}")` koji deli journal na deo pre i posle QR-a. Renderuje se kao:
-```html
-…tekst pre QR…
-
-
-
-…tekst posle QR…
-```
-`body` ima `max-width:max-content;margin:0 auto` da se tekst i QR centriraju kao celina.
-
----
-
-#### GR-5: QR veličina u HTML verziji receipt.py — ✅ ISPRAVLJENO
-
-**Problem:** `.qr img { width:25mm; height:25mm; }` — premalo za skeniranje.
-
-**Ispravka:** Promenjena veličina na `width:60mm; height:60mm;` u `generate_receipt_html`.
-
----
-
-#### GR-6: Jezik hardkodovan na latinicu — ✅ ISPRAVLJENO
-
-**Problem:** `server.py` uvek pozivao `generate_receipt(full_data, "latin")`.
-
-**Ispravka:**
-- Fisk čita `fiskalni_pismo` iz env var `FISKALNI_PISMO` (prioritet) ili iz NTech SQLite (`podesavanja` tabela, ključ `fiskalni_pismo`)
-- Dodana funkcija `_ucitaj_fiskalni_pismo()` pri pokretanju servera
-- Oba poziva `generate_receipt(full_data, FISKALNI_PISMO)` i `generate_receipt_html(full_data, FISKALNI_PISMO)`
-- **Restart Fisk servera obavezan** posle promene podešavanja
-
----
-
-#### GR-7: UI za izbor pisma nedostajao — ✅ ISPRAVLJENO
-
-**Ispravka:**
-- `web/templates/stranice/podesavanja_fiskalizacija.html` — dodat `