Što je MedijaPos
MedijaPos je web blagajna (Point of Sale) namijenjena malim i srednje velikim maloprodajnim poslovnicama u Hrvatskoj. Radi isključivo u pregledniku — nema instalacije, nema Windows podrške — samo URL.
Sustav implementira hrvatsku fiskalnu obvezu prema Zakonu o fiskalizaciji (NN 146/2012, čl. 9): svaki račun dobiva ZKI (zaštitni kod izdavatelja) i, kada je FINA certifikat konfiguriran, JIR (jedinstveni identifikator računa) od Porezne uprave.
Za koga
- Maloprodaja (živežne namirnice, pekarnice, tiskovnice)
- Kafići i konobe
- Kiosci i prodajni šatori
- Svaki obveznik fiskalizacije bez skupog POS hardvera
Ključne značajke
- ✓ ZKI po NN 146/2012 §9 (RSA-SHA1 + MD5)
- ✓ Trening mod bez FINA certifikata
- ✓ Smjene s izvještajem
- ✓ Termalni ispis 80mm
- ✓ Smoke test end-to-end
- ✓ Dark/light tema
- ✓ Mobile-responsive
Inovacijski doprinos i dokazni značaj
MedijaPos nije samo blagajna. To je dokazni rad koji demonstrira da je moguće izgraditi potpuno sukladnu hrvatsku fiskalnu blagajnu koristeći isključivo otvorene web tehnologije — bez vlasničkih hardverskih terminala, bez Windows aplikacija i bez naknade za licencu.
Što ovo dokazuje
Prior art · Patent claimSvaki put kad novi sustav tvrdi da je prvi napravio softverski ZKI bez FINA hardverskog modula — MedijaPos je dokaz da je to rješeno prije njega, chain-zapečaćeno na točan datum.
Tehnički doprinosi
- ZKI bez FINA terminala — RSA-PKCS1v15-SHA1 + MD5 implementiran softverski; nema hardverskog modula
- Trening mod — radi bez certifikata (MD5 fallback) za pilotiranje i edukaciju
- Async POS arhitektura — FastAPI + asyncpg; dokaz da async stack drži real-time transakcije
- Smoke test kao dio sustava — 7-koračni end-to-end test s auto-rollbackom testnih podataka
- Mobile-first web POS — bottom sheet kasa, FAB gumb; radi na bilo kom uređaju bez instalacije
Pravni i akademski doprinosi
- Prior art za patent — svaki commit timestampan, weise3_id chain-zapečaćen; datum nastanka kriptografski dokaziv
- NN 146/2012 compliance bez hardvera — dokazuje da je Zakon o fiskalizaciji moguće poštivati bez skupog hardvera
- Open-source fiskalizacija — sav kod javno dostupan pod CC0; nitko ne može zatvoriti patent na ovaj pristup
- Edukacijska referenca — detaljno dokumentiran ZKI algoritam; koristi se za nastavu i istraživanje
Stvarna primjena
Pilot spremanTestiran u realnim uvjetima kroz smoke test koji simulira cjelokupan poslovni dan — otvaranje smjene, tri računa s različitim PDV stopama i načinima plaćanja, zatvaranje smjene i financijski izvještaj.
Cjenovna usporedba
| Rješenje | Trošak/god |
|---|---|
| Klasični POS terminal | 600–2.000 € |
| SaaS POS licenca | 300–900 € |
| MedijaPos (self-hosted) | 0 € (server ~10 €/mj.) |
| MedijaPos (GenesisPos hosting) | TBD — pilot faza |
Tko ga može koristiti danas
- Pekarnice i kiosci — radi na tabletu ili starom laptopu
- Sezonska prodaja — nema instalacije; otvori URL, radi, zatvori sezonu
- Udruge i manifestacije — trening mod za testiranje, produkcija kad se doda FINA certifikat
- Razvojni timovi — referentna implementacija ZKI algoritma za vlastite projekte
- Računovodstveni uredi — edukacija klijenata o fiskalizaciji bez troška hardvera
Chain dokaz nastanka
Krunica™ verificiranoRazvoj MedijaPos-a je zapisan u Genesis Krunica chain — nepromjenjivi kriptografski lanac koji dokazuje što je napravljeno i kada. Ovo nije marketinška tvrdnja — to je matematika.
schema_dokarh/genesis/ — svi commit sessionsi zapečaćeni kao .dok.jsonZašto je ovo važno: U digitalnoj ekonomiji, tko može kriptografski dokazati kada je nešto napravio — taj drži intelektualno vlasništvo. Genesis chain je notarski ured koji nikad ne spava i ne može biti podmićen.
Arhitektura
Server stack
Backend| Sloj | Tehnologija | Opis |
|---|---|---|
| Web framework | FastAPI 0.115 (uvicorn) | Async HTTP, OpenAPI automatski |
| ORM | SQLAlchemy 2.x async | Async session, deklarativni modeli |
| Baza | PostgreSQL 16 (asyncpg driver) | Transakcije, UUID PK |
| Predlošci | Jinja2 (inline, bez statičkih fajlova) | SSR, bez build koraka |
| Kriptografija | Python cryptography knjižnica | PKCS12, RSA-PKCS1v15-SHA1 |
| Server | NEW (31.70.90.84, Fort Knox) | Port 8021, nginx proxy |
| Nginx | Strip prefix /medijapos/ | Proxy na 127.0.0.1:8021 |
| Auth | Cookie sesija (Itsdangerous) | SameSite=Lax, HttpOnly |
Baza podataka — tablice
PostgreSQL| Tablica | Svrha | Ključna polja |
|---|---|---|
| mp_korisnici | Korisnici blagajne | username, password_hash, uloga (admin/blagajnik) |
| mp_smjene | Radne smjene | korisnik_id, kasa (int), otvoreno, zatvoreno |
| mp_racuni | Fiskalni računi | smjena_id, broj_racuna, ukupno, pdv_iznos, zki, jir, nacin_placanja |
| mp_stavke | Stavke računa | racun_id, sifra, naziv, kolicina, jed_cijena, pdv_stopa, popust_posto, ukupno |
| mp_artikli | Katalog artikala | sifra (unique), naziv, cijena, pdv_stopa, kategorija, barkod, aktivan |
| mp_postavke | Konfiguracija | oib, oznaka_op, oznaka_npu, cert_p12_b64 (base64), cert_lozinka |
Mapa projekta
Struktura/var/www/medijapos-web/
├── main.py ← FastAPI app, lifespan, render()
├── models.py ← SQLAlchemy modeli (Base, Korisnik, Smjena, Racun, Stavka, Artikl, Postavke)
├── database.py ← AsyncEngine, get_db() generator
├── routers/
│ ├── auth.py ← /api/login, /api/logout, require_login()
│ ├── kasa.py ← /api/racun POST, /medijapos/ GET
│ ├── artikli.py ← CRUD /api/artikli
│ ├── smjena.py ← /api/smjena/{otvori,zatvori,aktivna}
│ ├── izvjestaj.py ← /api/izvjestaj/smjena/{id}
│ ├── postavke.py ← /medijapos/postavke, /api/postavke/{oznake,cert}
│ ├── print_racun.py ← /racun/{id}/print
│ └── smoke.py ← /api/smoke-test
├── services/
│ ├── kasa.py ← zatvori_racun() — srce fiskalizacije
│ └── fina_cert.py ← cert_info(), zki_rsa(), zki_trening()
└── templates/
├── mp_layout.html ← base layout, theme, nav, toast
├── mp_login.html ← prijava
├── mp_kasa.html ← glavna blagajna
├── mp_smjena.html ← upravljanje smjenom
├── mp_artikli.html ← katalog artikala
├── mp_postavke.html ← OIB, oznake, FINA cert
└── mp_racun_print.html ← 80mm termalni ispis
Funkcionalnost
Kasa — glavna blagajna
PrimarnoDvokolonski layout: lijevo katalog artikala, desno košarica. Na mobilnim uređajima košarica klizi od dna (bottom sheet) s FAB gumbom za otvaranje.
- Pretraga — po šifri, nazivu ili barkodu u realnom vremenu
- Kategorije — automatski generirani tabovi iz kataloga artikala
- Enter tipka — dodaje prvi rezultat pretrage u košaricu (za skener barkoda)
- Količina — +/- kontrole po stavci, automatski izračun popusta
- 10% rabat — primjenjuje popust na sve stavke u košarici
- Naplata — modal s odabirom gotovina/kartica, potvrda, ZKI ispis
- Ispis — otvara 80mm termalnu stranicu u novom tabu
Smjena
UpravljanjeFiskalizacija zahtijeva aktivnu smjenu za izdavanje računa. Sustav blokira naplatu bez otvorene smjene.
- Otvaranje smjene — POST /api/smjena/otvori, bilježi korisnik_id i kasa broj
- Pregled u realnom vremenu — broj računa, ukupni promet, gotovina vs. kartica, PDV zbroj, stornirani
- Tablica računa — svi računi smjene s ZKI kodom
- Zatvaranje smjene — POST /api/smjena/zatvori, vraća finalni izvještaj
- Auto-refresh — setInterval 30s dok je smjena aktivna
Artikli — katalog
CRUD- Pretraga po šifri, nazivu, barkodu
- Dodavanje novog artikla s validacijom (šifra unique)
- Inline uređivanje — klik Uredi → polja postaju input
- Deaktivacija (soft delete) — artikl ostaje u arhivi računa
Postavke — fiskalna konfiguracija
Kritično- OIB poduzetnika — 11 znamenki, validacija regex
- Oznaka operatera — npr. OP01 (opreacijski posrednik)
- Oznaka naplatnog uređaja — npr. BP01 (blagajnički pojam)
- FINA certifikat upload — drag&drop .p12/.pfx, provjera PKCS12 formata, pregled Subject/Issuer/Expires
- Cert test — gumb za provjeru valjanosti certifikata
- Smoke test link — direktni link na end-to-end provjeru
Fiskalizacija
ZKI algoritam — NN 146/2012 §9
Pravna osnovaZKI (Zaštitni kod izdavatelja) je 32-znakovna hex vrijednost koja kriptografski dokazuje da je račun izdao legitimni poduzetnik.
poruka = OIB + datum_vrijeme + broj_racuna + oznaka_op + oznaka_npu + ukupnopotpis = RSA-PKCS1v15-SHA1(poruka, privatni_kljuc_certifikata)ZKI = MD5(potpis[0:16]).hexdigest() ← 32 hex znaka
# services/fina_cert.py
def zki_rsa(oib, datum_str, br_racuna, oznaka_op, oznaka_npu, ukupno_str, p12_b64, lozinka):
poruka = f"{oib}{datum_str}{br_racuna}{oznaka_op}{oznaka_npu}{ukupno_str}"
private_key, _ = _load_pkcs12(p12_b64, lozinka)
signature = private_key.sign(poruka.encode("utf-8"), PKCS1v15(), SHA1())
return hashlib.md5(signature[:16]).hexdigest()
DD.MM.YYYYTHH:MM:SSFormat ukupnog iznosa:
"26.50" (decimalna točka, 2 decimale)Broj računa format:
1-BP01-2026 (redni/naplatni_uredaj/godina)
FINA certifikat
PKCS12Certifikat za fiskalizaciju izdaje FINA (Financijska agencija) na zahtjev obveznika fiskalizacije. Format je PKCS#12 (.p12 ili .pfx).
| Korak | Opis |
|---|---|
| Upload | Drag-drop na /postavke stranicu, max 5MB, provjera ASN.1 magic bytes (0x3082) |
| Pohrana | Base64 u tablici mp_postavke.cert_p12_b64, lozinka u cert_lozinka |
| Učitavanje | cryptography.hazmat.primitives.serialization.pkcs12.load_key_and_certificates() |
| Provjera | cert.not_valid_after_utc > datetime.utcnow() → valjan |
| ZKI | RSA potpis s privatnim ključem iz certifikata |
| JIR | Vraća Porezna uprava na SOAP poziv (NN 146/2012 Prilog 1) |
Trening mod
FallbackBez uploadanog FINA certifikata sustav radi u trening modu. Računi su validni za testiranje ali nisu fiskalno valjani.
poruka = OIB + datum + br_racuna + oznaka_op + oznaka_npu + ukupnoZKI = MD5(poruka.encode("utf-8")).hexdigest() ← bez RSA
Trening mod je vidljiv:
- Na kasusu: narandžasta "TRENING" pilula u search baru
- Na računu za ispis: crni banner "*** TRENING RAČUN — NIJE FISKALNI ***"
- JIR polje je prazno (postoji samo u produkcionom modu)
API referenca
Autentikacija
Računi & kasa
Artikli
Smjena
Postavke
Dijagnostika
Termalni ispis 80mm
Klikom "Ispis" na receipt modalu otvara se nova stranica
/racun/{id}/print u novom tabu.
Stranica je optimizirana za 80mm termalne pisače:
| Element | Specifikacija |
|---|---|
| Papir | 80mm, @page { size: 80mm auto; margin: 0 } |
| Font | Courier New, monospace, 12px tijelo |
| Max-width | 80mm, margin: 0 auto |
| Separatori | Dashed 1px crta (imitira cut) |
| Trening banner | Crni bar "*** TRENING RAČUN ***" ako je trening mod |
| ZKI | Cijeli 32-zn. hex kod, font-size 9px, word-break: break-all |
| JIR | Prikazuje se ako postoji (produkcioni mod) |
| PDV tablica | Grupirano po stopama: 5%, 13%, 25%, ukupno |
| No-print | Gumbi Ispis/Zatvori skriju se na ispisivanju (@media print) |
Smoke test — automatska provjera
GET /api/smoke-test izvodi end-to-end provjeru sustava u 7 koraka.
Na kraju se poziva db.rollback() — testni podaci ne ostaju u bazi.
{
"ok": true,
"poruka": "SVE ZELENO - sustav spreman",
"koraci": [
{"korak": "1. Login (admin korisnik)", "ok": true, "detalj": "id=1"},
{"korak": "3.1. Racun #3-1/BP01/2026", "ok": true, "detalj": "ukupno=10.00 EUR | ZKI=24cf77ea..."},
...
{"korak": "7. Rollback testnih podataka", "ok": true}
]
}
Deployment
Server
| Server | NEW — 31.70.90.84 (Fort Knox, IONOS) |
| Dir | /var/www/medijapos-web/ |
| Port | 8021 (uvicorn, interno) |
| Javni URL | https://fina-connect.online/medijapos/ |
| Nginx | Strip prefix /medijapos/ → proxy 127.0.0.1:8021 |
| Servis | medijapos-web.service (systemd) |
Pokretanje
# .env DATABASE_URL=postgresql+asyncpg://user:pass@localhost/medijapos_db SECRET_KEY=... # Inicijalizacija baze python3 init_db.py # CREATE TABLE svi modeli python3 seed.py # admin + blagajnik korisnik + demo artikli # Pokretanje uvicorn main:app --host 0.0.0.0 --port 8021 --workers 2 # Smoke test curl https://fina-connect.online/medijapos/api/smoke-test
Nginx konfiguracija (isječak)
location /medijapos/ {
proxy_pass http://127.0.0.1:8021/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
/medijapos/ prije
prosljeđivanja na backend. Sve FastAPI rute definirane su bez tog prefiksa
(npr. /api/racun, /login). HTML stranice koriste
apsolutne URL-ove s prefiksom (/medijapos/api/...) jer preglednici
šalju pun URL kroz nginx.
Povijest razvoja
Roadmap
Sljedeće
- JIR integracija — SOAP poziv Poreznoj upravi
- Storno računa (red. oznaka S)
- Popust po računu (globalni rabat)
- Višekorisničke smjene (više blagajni)
- QR kod na računu (eVisitor/PAY ALL)
Buduće
- Offline mod (Service Worker + IndexedDB)
- Barkod skener kamera (BarcodeDetector API)
- Genesis Login integracija (ZAKON 40)
- Izvoz u e-Račun XML format
- PAY ALL Bon integracija za plaćanje