MedijaPos — Web blagajna

Hrvatska fiskalna web blagajna izgrađena na Genesis POS infrastrukturi. FastAPI backend, PostgreSQL, ZKI po NN 146/2012, FINA certifikat, smjene, artikli, termalni ispis — sve u pregledniku.

● LIVE — fina-connect.online/medijapos/ FastAPI 0.115 Python 3.12 PostgreSQL 16 SQLAlchemy async Jinja2 NN 146/2012 §9

Š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 claim

Svaki 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 spreman

Testiran 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 terminal600–2.000 €
SaaS POS licenca300–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™ verificirano

Razvoj 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.

Chain entry
schema_dokarh/genesis/ — svi commit sessionsi zapečaćeni kao .dok.json
Hash algoritam
SHA3-256 s FENIX prefiksom — determinističan, bez centralnog registratora
Datum dokaza
2026-04-19 — Genesis Block #0, Ivan Brtan, Vladislavci, HR
Verifikacija
Što se dokazuje
Autor, datum i mjesto nastanka — i da ga nitko nije mogao retroaktivno izmijeniti
Pravna snaga
Prior art za patent claim · akademski citat · dokaz autorstva za licenciranje

Zaš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
SlojTehnologijaOpis
Web frameworkFastAPI 0.115 (uvicorn)Async HTTP, OpenAPI automatski
ORMSQLAlchemy 2.x asyncAsync session, deklarativni modeli
BazaPostgreSQL 16 (asyncpg driver)Transakcije, UUID PK
PredlošciJinja2 (inline, bez statičkih fajlova)SSR, bez build koraka
KriptografijaPython cryptography knjižnicaPKCS12, RSA-PKCS1v15-SHA1
ServerNEW (31.70.90.84, Fort Knox)Port 8021, nginx proxy
NginxStrip prefix /medijapos/Proxy na 127.0.0.1:8021
AuthCookie sesija (Itsdangerous)SameSite=Lax, HttpOnly

Baza podataka — tablice

PostgreSQL
TablicaSvrhaKljučna polja
mp_korisniciKorisnici blagajneusername, password_hash, uloga (admin/blagajnik)
mp_smjeneRadne smjenekorisnik_id, kasa (int), otvoreno, zatvoreno
mp_racuniFiskalni računismjena_id, broj_racuna, ukupno, pdv_iznos, zki, jir, nacin_placanja
mp_stavkeStavke računaracun_id, sifra, naziv, kolicina, jed_cijena, pdv_stopa, popust_posto, ukupno
mp_artikliKatalog artikalasifra (unique), naziv, cijena, pdv_stopa, kategorija, barkod, aktivan
mp_postavkeKonfiguracijaoib, 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

Primarno

Dvokolonski 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
PDV: Svaka stavka nosi svoju PDV stopu (0%, 5%, 13%, 25%). Iznosi se prikazuju grupirani po stopi u totals sekciji.

Smjena

Upravljanje

Fiskalizacija 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 osnova

ZKI (Zaštitni kod izdavatelja) je 32-znakovna hex vrijednost koja kriptografski dokazuje da je račun izdao legitimni poduzetnik.

Formula:
poruka = OIB + datum_vrijeme + broj_racuna + oznaka_op + oznaka_npu + ukupno
potpis = 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()
Format datuma u poruci: DD.MM.YYYYTHH:MM:SS
Format ukupnog iznosa: "26.50" (decimalna točka, 2 decimale)
Broj računa format: 1-BP01-2026 (redni/naplatni_uredaj/godina)

FINA certifikat

PKCS12

Certifikat za fiskalizaciju izdaje FINA (Financijska agencija) na zahtjev obveznika fiskalizacije. Format je PKCS#12 (.p12 ili .pfx).

KorakOpis
UploadDrag-drop na /postavke stranicu, max 5MB, provjera ASN.1 magic bytes (0x3082)
PohranaBase64 u tablici mp_postavke.cert_p12_b64, lozinka u cert_lozinka
Učitavanjecryptography.hazmat.primitives.serialization.pkcs12.load_key_and_certificates()
Provjeracert.not_valid_after_utc > datetime.utcnow() → valjan
ZKIRSA potpis s privatnim ključem iz certifikata
JIRVraća Porezna uprava na SOAP poziv (NN 146/2012 Prilog 1)

Trening mod

Fallback

Bez uploadanog FINA certifikata sustav radi u trening modu. Računi su validni za testiranje ali nisu fiskalno valjani.

Trening ZKI:
poruka = OIB + datum + br_racuna + oznaka_op + oznaka_npu + ukupno
ZKI = 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

POST/api/loginmultipart/form-data: username, password → kolačić sesije
POST/api/logoutBriše kolačić sesije

Računi & kasa

POST/api/racunBody: {nacin_placanja, stavke[]} → {racun_id, broj_racuna, ukupno, pdv_iznos, zki, jir}
GET/racun/{id}/printHTML 80mm termalni ispis za tiskanje

Artikli

GET/api/artikli?q=pretraga → lista aktivnih artikala
POST/api/artikliBody: {sifra, naziv, cijena, pdv_stopa, kategorija?, barkod?}
PUT/api/artikli/{id}Ažuriranje artikla
DELETE/api/artikli/{id}Soft delete (aktivan=false)

Smjena

POST/api/smjena/otvoriOtvori novu smjenu za trenutnog korisnika
POST/api/smjena/zatvoriZatvori aktivnu smjenu → {statistika}
GET/api/smjena/aktivna{aktivna, id, kasa, otvoreno, statistika}
GET/api/izvjestaj/smjena/{id}{smjena, racuni[], statistika}

Postavke

POST/api/postavke/oznakeBody: {oib, oznaka_op, oznaka_npu}
POST/api/postavke/certmultipart: cert_file (.p12/.pfx), lozinka → {ok, subject, expires_at, valjan}
GET/api/postavke/cert/testProvjeri valjanost uploadanog certifikata

Dijagnostika

GET/api/smoke-testEnd-to-end test 7 koraka, rollback na kraju — {ok, koraci[]}

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:

ElementSpecifikacija
Papir80mm, @page { size: 80mm auto; margin: 0 }
FontCourier New, monospace, 12px tijelo
Max-width80mm, margin: 0 auto
SeparatoriDashed 1px crta (imitira cut)
Trening bannerCrni bar "*** TRENING RAČUN ***" ako je trening mod
ZKICijeli 32-zn. hex kod, font-size 9px, word-break: break-all
JIRPrikazuje se ako postoji (produkcioni mod)
PDV tablicaGrupirano po stopama: 5%, 13%, 25%, ukupno
No-printGumbi 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.

1Admin korisnik postoji
2Otvori smjenu (kasa 99)
33 računa (PDV 25/13/5%)
4ZKI na svim računima
5Zatvori smjenu
6Izvještaj (zbroj = 26.50 EUR)
7Rollback testnih podataka
{
  "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

ServerNEW — 31.70.90.84 (Fort Knox, IONOS)
Dir/var/www/medijapos-web/
Port8021 (uvicorn, interno)
Javni URLhttps://fina-connect.online/medijapos/
NginxStrip prefix /medijapos/ → proxy 127.0.0.1:8021
Servismedijapos-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;
}
Napomena: Nginx stripa prefiks /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

Sprint 0 — Temelji
FastAPI skeleton + SQLAlchemy modeli
Definicija svih tablica, async session, Jinja2 setup, cookie auth, login/logout.
Sprint 1 — Kasa + Artikli
Kasa funkcionalnost + CRUD artikala
Košarica, kategorije, pretraga, barkod scan Enter, PDV izračun po stavci, naplata modal.
Sprint 2 — Smjena + Izvještaj
Smjene s KPI statistikom
Otvaranje/zatvaranje smjene, auto-refresh svake 30s, tablica računa, finalni izvještaj.
Sprint 3 — Fiskalizacija
ZKI po NN 146/2012, FINA cert, trening mod
RSA-PKCS1v15-SHA1 + MD5 ZKI, PKCS12 upload, cert_info(), fallback trening MD5 bez RSA. Broj računa formata 1-BP01-2026.
Sprint 4 — Ispis + Smoke test
80mm termalni ispis + end-to-end test
mp_racun_print.html s @media print 80mm, smoke test 7 koraka s rollbackom, /api/smoke-test endpoint.
Sprint 5 — UI redesign + Mobile
Kompletni vizualni redesign + mobilna prilagodba
Novi design system (deep dark + mint green), art-tile grid, bottom sheet košarica na mobilnom, FAB gumb, dark/light tema, professional POS look.

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
MedijaPos je dio Genesis POS ekosustava. Sve izmjene prate Genesis ZAKON arhitekturu (DokArh chain entry za svaki značajni commit, trojna pohrana, Bršljan broadcast).