NavDaV¶
NAV Online Számla → SQLite akkumulátor → on-demand xlsx render → WebDAV → Nextcloud External Storage
Tartalom
- Mire való ez a rendszer?
- Architektúra
- Adatfolyam — egy fájlmegnyitás
- SQLite séma
- NAV bridge — delta + state
- Throttle és render-cache
- xlsx renderer
- WebDAV provider
- Production deploy és üzemeltetés
- Nextcloud bekötés
- Fájlok és tech stack
- Iteratív állás és lezárt issue-k
Mire való ez a rendszer?¶
Production status (2026-05-04): Docker-image multi-stage Rust + Python build, healthcheck, persistent volume; egysoros telepítő Gitea-ról (
install-from-gitea.sh), idempotens frissítéssel; LAN-os Nextcloud External Storage bekötés, kanonikus üzemeltetési parancsokkal. Code-stasi review 36 találatából 35 lezárva (1 halasztott: Docker BuildKit secret mount). NAV CLI v1.5.1 alapszinten — 9 lezárt nav-projekt-issue, 33 strukturált top-level mező + 25 line-mező + ÁFA-rátarendszer-bontás (vat_rate_summaries). Magyar számviteli terminológia + ISO 80000-1 szerinti formázás.
A NAV Online Számla rendszerből minden bejövő és kimenő elektronikus számla automatikusan elérhető API-n keresztül. Az adat hasznos, de a NAV felülete nem alkalmas napi munkára: nincs gyors szűrés, exportálás, sem áttekintő nézet.
A NavDaV egyetlen virtuális Excel-fájlt szolgál ki — szamlak.xlsx — ami minden megnyitáskor friss NAV-adatot tartalmaz, és a Nextcloudba van bekötve External Storage-ként. A user csak megnyitja a fájlt böngészőben (Collabora) vagy desktop kliensből, és aktuális állapotot lát.
1. Lekér
Megnyitáskor a háttérben meghívja a NAV API-t (a
nav-online-invoiceRust binárison keresztül) — csak a delta-ablakot, nem a teljes történetet.
2. Akkumulál
Az új eseményeket egy SQLite-ba upsertálja. A teljes történet a DB-ben, nincs az adat „elveszhet egy lekérdezés között" helyzet.
3. Renderel
Az SQLite-ból in-memory xlsx-et készít két lappal (Számlák, Tételek), autofilterrel, és visszaadja a HTTP válaszban.
5802 számla | 13549 tételsor | 28 hónap | 60s throttle | 2.2MB xlsx | 6 xlsx-lap
Architektúra¶
Egyetlen Python processz fut localhoston — egy WSGI WebDAV szerver (wsgidav + cheroot). A Nextcloud ezt látja távoli WebDAV-erőforrásként és úgy kezeli, mintha egy könyvtár lenne benne egyetlen fájllal.
A négy réteg
- Rust binary (
nav-online-invoice) — a NAV Online Számla v3.0 API kliens. 35 napos chunk-szeleteléssel, retry-jal, JSON kimenettel. A NavDaV csak subprocess-szel hívja, nem érti az XML-t.- NAV bridge (
nav_bridge.py) — a Rust binary köré fonott Python réteg: subprocess hívás, JSON parse, SQLite UPSERT, delta-ablak, state-management, throttle.- xlsx renderer (
render_xlsx.py) — SQLite → in-memory bytes (xlsxwriter). Két lap, autofilter, freeze pane, dátum/szám-formázás.- WebDAV provider (
webdav_server.py) — egyetlen virtuális fájlt expose-ál, Basic auth, render-cache, ETag/Last-Modified.
A két állapottároló¶
SQLite — perzisztens akkumulátor
navdav.db. Ide kerül minden NAV-tól kapott számla, idő múlásával csak nő (törlés nincs). Egy számla törlésével „elveszett" eseményt nem ismerünk — ez audit-szintű séma, nem aktív-állapot mirror.
Process-memória — render-cache
A legutóbb renderelt xlsx-bytes + a render alapjául szolgáló
last_success_ts. Amíg az SQLite nem változott, ugyanaz a bytes szolgálja ki az összes GET-et. Restart után újrarenderel.
Adatfolyam — egy fájlmegnyitás¶
A felhasználó megnyitja szamlak.xlsx-et a Nextcloudban → a Nextcloud egy HTTP GET-et küld a WebDAV szerverünknek. Innentől:
-
Auth — HTTP Basic,
NAVDAV_USER/NAVDAV_PASSWORDenv-ből
Hibás → 401, válasz nincs -
Throttle gate —
maybe_refresh_delta()egy module-szintűthreading.Lock-ot vesz fel
Ha last_check_ts < 60s → skip (nincs NAV-hívás) -
Delta — `valtozas --since last_valtozas_ts`
insDate (NAV-érkezés) szerinti szűrés — késedelmes-beküldés-immunis. A `valtozas` parancs (NAV CLI v1.2.0) `tetelek`-szerű 30+ mezős JSON-t ad. Bootstrap (full sync) marad `tetelek`-alapú -
Két subprocess-hívás — kimenő + bejövő irány külön
nav-online-invoice -p -q valtozas --since <ts> -d kimeno -o json --file ... majd ugyanaz -d bejovo -val. Exit-100 (nincs új) is sikernek számít, üres listát ad. Bootstrap módban (`--full` vagy `--from/--to`) helyette tetelek -d kimeno -f X -t Y -
JSON parse + UPSERT —
transaction_idmint PK,ON CONFLICT DO UPDATE
Számla előtti tételsorok törlése, friss insert (idempotens) -
State frissítés — siker →
last_success_date,last_success_ts; bármi →last_check_ts
NAV-hiba esetén csak a last_check_ts frissül, hogy a throttle ne hammerezze a hibás API-t -
Render-cache döntés — ha a state-beli
last_success_tsegyezik a cache-belivel → cache HIT
Egyébként render_xlsx.render(conn, BytesIO) , MD5 számolás, cache frissítés -
HTTP válasz — Content-Length, ETag, Last-Modified, Content-Type, body
A Nextcloud kliens ETag-alapján dönti el, letöltse-e újra (sok user, kevés sávszél)
SQLite séma¶
Három tábla: invoices (egy sor = egy NAV-esemény), invoice_lines (számlatételek, FK-on lóg), state (kulcs-érték a delta-ablak és throttle számára).
invoices — primary key¶
A PK transaction_id — a NAV <transactionId> tagjéből, ami minden beküldési eseményhez egyedi köteg-ID. Egy CREATE → egy transaction_id; egy MODIFY/STORNO → másik transaction_id. Tehát az audit-lánc minden lépése külön sorba kerül a DB-ben.
Történeti megjegyzés: az eredeti tervben content-hash dedupe szerepelt, mert a
tetelek -o jsonkimenetben nem volt sembatch_index, sem stabil esemény-PK. A NAV-projekt #2 issue-ja erre épült: a Rust oldal hozzáadta atransaction_id,batch_index,modification_index,original_invoice_numbermezőket. Ezekkel a content-hash workaround feleslegessé vált.
Mezők¶
| Mező | Forrás | Megjegyzés |
|---|---|---|
transaction_id |
NAV <transactionId>
|
PK. Beküldési köteg ID, számlánként egyedi |
invoice_number |
NAV | Az emberek számára olvasható számlaszám (pl. SZ/2026/00567) |
direction |
(NavDaV) |
OUTBOUND / INBOUND — a kimeno / bejovo Rust-irányból |
invoice_operation |
NAV |
CREATE / MODIFY / STORNO
|
modification_index |
NAV <modificationIndex>
|
Több MODIFY ugyanahhoz a számlához → 1, 2, …; CREATE-nél NULL
|
original_invoice_number |
NAV <originalInvoiceNumber>
|
MODIFY/STORNO esetén az eredeti CREATE számlaszáma |
batch_index |
NAV <index>
|
Pozíció a NAV-beküldési kötegen belül (audit-info) |
supplier_*, customer_* |
NAV digest XML | Iránytól függően az egyik a partner, a másik mi |
customer_vat_status |
NAV per-számla XML <customerVatStatus>
|
DOMESTIC / OTHER (EU-s) / PRIVATE_PERSON. A digest-ben nincs, csak a per-számla XML-ben — a tetelek path tölti, a mind/kimeno/bejovo nem |
invoice_net_amount(_huf)invoice_vat_amount(_huf)invoice_gross_amount(_huf)
|
NAV (digest a net+vat-ot, per-számla XML a gross-t) | String-ként tárolva (decimal pontosság miatt). SIMPLIFIED-en a net+vat NULL — a NAV nem ad áfa-bontást, csak gross-t |
invoice_accounting_delivery_date |
NAV per-számla XML | Számviteli teljesítés napja (gyakran ≠ invoice_delivery_date) |
payment_date |
NAV per-számla XML <paymentDate>
|
Fizetési határidő — utalás-tervezéshez |
payment_method |
NAV per-számla XML <paymentMethod>
|
TRANSFER / CASH / CARD / VOUCHER / OTHER |
supplier_addresscustomer_address
|
NAV per-számla XML | Összevont string (pl. HU 6727 Szeged, Algyői út 19 A.) — partner-nyilvántartás bővítéshez |
supplier_bank_account_number |
NAV per-számla XML | A kibocsátó bankszámlaszáma — irányfüggetlen (NAV csak a supplier-é). Bejövőn fizetési cél, kimenőn a saját |
community_vat_number |
NAV per-számla XML | EU adóazonosító (pl. HU24696371). A kibocsátóé. INBOUND-on csak EU-s/külföldi szállítóknál van kitöltve |
exchange_rate |
NAV per-számla XML | Pénznem-árfolyam HUF-ra. "1" HUF-számláknál |
invoice_appearance |
NAV per-számla XML | ELECTRONIC / PAPER / EDI / UNKNOWN — Sztv. tárolási előírás |
completeness_indicator |
NAV per-számla XML | NAV digital invoice teljesszerűségi flag (string "true"/"false") |
invoice_issue_dateinvoice_delivery_date
|
NAV | ISO dátum string |
currency |
NAV |
HUF, EUR, … |
raw_json |
(NavDaV) | A teljes JSON sorrendezett alakja — debug és későbbi remap miatt |
ins_ts |
(NavDaV) | UTC timestamp az első insert-kor (UPDATE nem módosítja) |
state — kulcs-érték¶
| Kulcs | Mit jelent |
|---|---|
last_check_ts |
UTC ISO timestamp az utolsó NAV-hívási kísérletről (siker és hiba is). Throttle bemenete. |
last_valtozas_ts |
UTC ISO timestamp — az utolsó sikeres delta-refresh indítási ideje. A következő valtozas --since ettől szűr. Insdate-alapú delta (késedelmes-beküldés-immunis). |
last_success_date |
Visszafelé kompatibilitásra a `--full`/`--from`/`--to` (tetelek-alapú) refresh-hez. |
last_success_ts |
UTC ISO timestamp az utolsó sikeres refresh-ről. A render-cache invalidáció bemenete. |
NAV bridge — delta + state¶
nav_bridge.py mind library, mind CLI. A library oldalt a WebDAV provider importálja, a CLI cron-ból futtatható ha érdemesnek látjuk.
Public API¶
| Függvény | Mit csinál |
|---|---|
init_db(path) |
SQLite kapcsolat + séma (schema.sql) végrehajtás |
call_nav(direction, frm, to, ...) |
Subprocess-hívás a Rust binary-ra, JSON visszaadás |
upsert_invoices(conn, invoices, direction) |
UPSERT az invoices + tételsor-újraírás |
compute_delta_window(conn) |
(frm, to) — vagy last_success_date − 2, vagy 2024-01-01 ha üres |
refresh(conn, frm, to, ...) |
A teljes lánc: két irány fetch + upsert + state update. Hibát feldob |
maybe_refresh_delta(conn) |
Throttled, lock-olt verzió. Ezt hívja a WebDAV provider. |
maybe_refresh_delta — döntési fa¶
- GET érkezik a /szamlak.xlsx-re
-
_refresh_lock acquire
- last_check_ts létezik és < 60s?
- IGEN → throttled, return {throttled: True} (nincs NAV-hívás)
- NEM → refresh() futtatás
- SIKER → last_success_date + last_success_ts + last_check_ts frissítés
- HIBA → record_check_failure() → csak last_check_ts frissül
- last_check_ts létezik és < 60s?
- _refresh_lock release
-
_refresh_lock acquire
A lock + state-tábla kombináció így működik 5 párhuzamos GET esetén: az 1. szál bejut, futtatja a refresh-t (~1-2 mp), kilép. A 2-5. szálak közben a lockon várnak; mire bejutnak, a last_check_ts már friss → throttled. Egyetlen NAV-roundtrip per cluster.
Hibakezelés filozófia¶
Stale > nincs. Ha a NAV API hibázik, a NavDaV nem dob 5xx-et. A DB-ben tárolt utolsó ismert állapotot rendereli, és serve-eli a stale xlsx-et. A
last_check_tsígy is frissül, hogy a throttle ne tegye végtelen ciklusba a hibázó API-t.
Throttle és render-cache¶
Két különböző cache van, ne keverjük össze:
Throttle (NAV-hívás cache)
Cél: a NAV API ne kapjon 60 másodpercen belül több hívást.
Bemenet:
state.last_check_ts(DB-ben, restart-tűrő).Ablak: 60 mp (
THROTTLE_SECONDS).Ha hit: a kód a régi DB-tartalmat fogja renderelni — friss API-hívás nem történik.
Render-cache (xlsx-bytes cache)
Cél: ne renderelje újra az xlsx-et minden GET-re ha az adat nem változott.
Bemenet:
state.last_success_tsvs cache-ben tárolt érték.Ablak: nincs idő — amíg a state nem mozdul, hit.
Ha hit: ugyanazt a bytes-objektumot adjuk vissza, ETag is azonos → Nextcloud nem tölti újra.
Tipikus szekvencia (két párhuzamos GET 5 mp különbséggel):
| Idő | Esemény | NAV-hívás | Render | Bytes-méret |
|---|---|---|---|---|
| T+0 | 1. GET | igen | igen (state változott) | 1.45 MB |
| T+5 | 2. GET | nem (throttle) | nem (cache hit) | 1.45 MB (cached) |
| T+90 | 3. GET | igen | függ — ha új esemény jött, igen; egyébként nem | — |
Beépített delta-scheduler¶
A NavDaV szerver indít egy daemon thread-et (delta_scheduler.DeltaScheduler), amely periodikusan meghívja a nav_bridge.maybe_refresh_delta-t, függetlenül attól hogy érkezett-e kliens-GET. Cél: hosszú inaktivitás után az első kliens-megnyitás ne találkozzon nagy NAV-hátralékkal és ezért percekig tartó delta-fetch + render láncolattal.
| Paraméter | Érték | Forrás |
|---|---|---|
| Default periódus | 1800 s (30 perc) | delta_scheduler.DEFAULT_INTERVAL_SECONDS |
| CLI flag | --delta-interval <sec> |
webdav_server.py |
| Env var | NAVDAV_DELTA_INTERVAL_SECONDS |
docker-compose.yml-ben állítható |
| Letiltás | --delta-interval=0 |
például manuális cron mellett |
| Min. érték | 60 s |
DeltaScheduler.__init__ ValueError-t dob ennél kisebbre |
A scheduler tick a meglévő throttle-on át megy: ha közben kliens-GET már lefuttatta a delta-frissítést a 60s-os ablakon belül, a tick throttled-et kap és kihagy. Tehát a scheduler nem duplázza a NAV-hívást aktív klienshasználat mellett — csak inaktivitás idején tartja "melegen" a DB-t.
Hibakezelés. A scheduler tick minden Exception-t a logba ír (
navdav.schedulerlogger), majd a következő tick-en újrapróbálja. Egyetlen tick-hiba nem állítja le a folyamatos frissítést — ez a daemon "fail-soft" működés.
xlsx renderer¶
render_xlsx.py. xlsxwriter backend, in-memory mód ({"in_memory": True}), BytesIO output. Két lap, autofilter, freeze pane.
Lap: Számlák (egy sor = egy NAV-esemény)¶
28 könyvelői oszlop (issue #6 megoldása után). Technikai mezők (transaction_id, batch_index, modification_index, ins_ts) az SQLite-ban maradnak, de az xlsx-be nem kerülnek.
| # | Oszlop | Forrás | Formátum |
|---|---|---|---|
| 1 | Irány | direction |
magyar címke (Bejövő / Kimenő) |
| 2 | Számlaszám | invoice_number |
— |
| 3 | Művelet | invoice_operation |
CREATE / MODIFY / STORNO |
| 4 | Eredeti számla | original_invoice_number |
STORNO/MODIFY-nál a CREATE száma |
| 5 | Utólagos művelet | subquery: ugyanezen invoice_number-re hivatkozó STORNO/MODIFY | pl. STORNO SZ/2026/00405; láncolat esetén pontosvesszővel |
| 6 | Kelt | invoice_issue_date |
yyyy.mm.dd. |
| 7 | Teljesítés | invoice_delivery_date |
yyyy.mm.dd. |
| 8 | Számv. teljesítés | invoice_accounting_delivery_date |
yyyy.mm.dd. — Sztv. szerinti könyvelési teljesítés napja |
| 9 | Fizetési határidő | payment_date |
yyyy.mm.dd. |
| 10–11 | Partner / Adószám | iránytól függő (supplier vagy customer) | — |
| 12 | Közösségi adószám | community_vat_number |
Áfa tv. szerinti közösségi adóazonosító — a kibocsátóé |
| 13 | Partner címe | iránytól függő (supplier_address vagy customer_address) | összevont string |
| 14 | Bankszámlaszám | supplier_bank_account_number |
A kibocsátóé — INBOUND-on a fizetési cél, OUTBOUND-on a saját |
| 15 | Adóalany státusza | customer_vat_status |
DOMESTIC / OTHER / PRIVATE_PERSON |
| 16 | Bizonylattípus | invoice_category |
NORMAL / SIMPLIFIED / AGGREGATE — Sztv. és Áfa tv. szerinti számla-típus |
| 17 | Megjelenési forma | invoice_appearance |
ELECTRONIC / PAPER / EDI / UNKNOWN — NAV hivatalos terminus |
| 18 | Teljességi mutató | completeness_indicator |
NAV XSD-ből: true ha az adatszolgáltatás maga a számla; false ha külön számladokumentum létezik |
| 19 | Pénznem | currency |
ISO 4217 kód |
| 20 | Árfolyam | exchange_rate |
HUF-ra; 1 HUF-számláknál |
| 21 | Fizetési mód | payment_method |
TRANSFER / CASH / CARD / VOUCHER / OTHER |
| 22–23 | Nettó / ÁFA (eredeti pénznem) |
invoice_net_amount / invoice_vat_amount
|
per-row: HUF→#,##0\ "Ft";[Red]-…;-, egyéb→#,##0.00;[Red]-…;-
|
| 24 | ÁFA-kulcs | tételsorok homogén-rátája |
0% homogén esetben; vegyes string vegyes ráták esetén; üres ha nincs ráta-adat |
| 25 | Bruttó (eredeti pénznem) |
invoice_gross_amount vagy net+vat
|
mint Nettó/ÁFA — per-row HUF/deviza |
| 26–28 | Nettó / ÁFA / Bruttó (Ft) | NAV HUF mezők |
#,##0\ "Ft";[Red]-#,##0\ "Ft";- — ISO 80000-1 szerint a mértékegység jel az érték után, szóközzel |
Autofilter, freeze az 1. sor + 2 oszlop, sorrendezés: invoice_issue_date DESC, invoice_number DESC.
Pénzformátum konzisztencia (per-row dinamika): ugyanaz a számérték minden helyen ugyanúgy formázódik. A „eredeti pénznem" oszlopok per-row választanak: HUF-számlán integer „Ft" formátum (mint a HUF oszlopok), nem-HUF számlán 2-tizedes deviza formátum. Tilos ugyanazt a HUF értéket az egyik oszlopban
4 245 000 Ft, a másikban4 245 000.00-ként mutatni.
Könyvelői munkafájl-szemlélet — az xlsx pontosan azt tartalmazza amit a NAV ad. SIMPLIFIED számláknál a NAV nem közöl nettó/ÁFA bontást (sem invoice-szinten, sem tételsoronként), csak bruttót — ezeknél a Nettó és ÁFA üresen marad, és a Kategória oszlop jelzi az okot. Silent fallback (pl. „feltételezzünk 27%-ot") tilos: hibás könyvelési adatot adna.
Lap: Tételek (egy sor = egy számlatétel)¶
26 oszlop (NAV CLI v1.5.1, 11+ új tétel-szintű mezővel): számla-szintű kontextus (irány, számlaszám, kelt, tétel-teljesítés, partner) → tételsor (sor#, megnevezés, cikkszám, jelleg, mennyiség, mértékegység, saját mértékegység) → összegek (egységár, nettó, ÁFA-kulcs, ÁFA, bruttó, ÁFA-tartalom) → pénznem és HUF-mezők (Nettó/ÁFA/Bruttó (Ft)) → flag-ek (Előleg, Kedvezmény %, Kedvezmény, Közvetített). 13 549 sor; rendezés: invoice_issue_date DESC, invoice_number DESC, line_number ASC. A pénzformátum ugyanaz a per-row HUF/deviza választás mint a Számlák lapon. A mennyiség formátuma #,##0.###.
Bruttó / ÁFA-tartalom oszlopok jelenlétének NAV-szabályos eloszlása: a
BruttóésBruttó (Ft)oszlopok csak a SIMPLIFIED kategóriájú számlák tételsorain vannak kitöltve — ennek oka, hogy a NAV az egyszerűsített számláknál tételsor szinten csak bruttót közöl (nincs nettó/ÁFA-bontás). NORMAL és AGGREGATE számláknál a NAV tételsoronként nettó + ÁFA-t ad (és nem bruttót), így a sor-bruttó ezeknél aNettó + ÁFAösszegként számolható. Ugyanez vonatkozik azÁFA-tartalomoszlopra: ez egyszerűsített számláknál a bruttóba foglalt áfa-arány, NORMAL/AGGREGATE-en üres. Nem exporthiány, hanem a magyar Áfa tv. és a NAV digital invoice szerkezet közvetlen leképezése.
Lap: ÁFA-bontás (egy sor = egy ÁFA-rátarendszer egy számlán)¶
A NAV `<summaryByVatRate>` strukturált bontása: minden számlához ÁFA-rátánként egy sor. Vegyes-rátás számláknál (pl. 27% és 5% külön) egy számlára több sor jut. A NavDaV az invoice-szintű vat_rate_summaries JSON-tömbből bontja ki — kötelező az Áfa-bevallás előkészítéséhez (ÁFA-soronkénti összesítés). Oszlopok: Irány, Számlaszám, Kelt, Partner, ÁFA-kulcs, Nettó, ÁFA, Bruttó (eredeti pénznem) + Nettó/ÁFA/Bruttó HUF + Pénznem. 5 800 számla → 5 931 sor (98,5% kitöltöttség, vegyes-rátás eseteknél több sor).
Lap: Duplikáció-gyanú (egy sor = egy gyanús csoport)¶
Azokat a számla-csoportokat sorolja fel, ahol azonos partner, azonos kelt és azonos bruttó (Ft) mellett ≥2 élő számla szerepel — vagyis CREATE vagy MODIFY események, amelyekhez nincs utólagos STORNO. A stornózott CREATE-eket és magukat a STORNO-eseményeket a szűrő kihagyja, így a listában csak azok az esetek maradnak, ahol valódi (nem-stornózott) duplikáció gyanúja áll fenn.
Oszlopok: Db, Irány, Partner, Kelt, Bruttó (Ft), Számlaszámok (csoportosítva pontosvesszővel), Művelet(ek), Pénznem. Rendezés: bruttó abszolút érték desc, db desc.
Lap: Időbeli kérdés (egy sor = egy számla)¶
Azon számlák, ahol payment_date < invoice_issue_date — a fizetési határidő a kelt elé esik. Oszlopok: Irány, Számlaszám, Művelet, Kelt, Fiz. határidő, Eltérés (nap, negatív), Partner, Bruttó (Ft), Pénznem. Rendezés: az eltérés nap-ban (legnagyobb negatív → legkisebb negatív). Lehetséges okok: utólag kibocsátott számla, dátumelírás, vagy STORNO/MODIFY ahol a NAV az eredeti számla payment_date-jét visszaadja.
Lap: Partner-egyezés (egy sor = egy csoport)¶
Két irányú adózóazonosítási inkonzisztencia: 1 adószám → több név (ugyanannak a partnernek többféle névírása), és 1 név → több adószám (ugyanaz a név alatt két különböző cég, pl. jogutódlás vagy hiba). A saját adószám (24696371) tipikusan dominál — a partnerek a saját adószámunkat 40+ formában szerepeltetik a számlán (rövidítések, kis/nagybetű, cégforma-rövidítés stb.). Oszlopok: Típus (1 adószám → több név / 1 név → több adószám), Csoport (adószám vagy név), Variáns(ok), Db előfordulás.
Számformátum: a NAV JSON minden numerikus értéket string-ként ad (
"104700"). A rendererDecimal()-en keresztül konvertál float-tá az xlsx cella számértékéhez (lebegőpontos hiba ellen). Avat_percentage0.27 jön — Excel0%formátum 27%-ként mutatja.
WebDAV provider¶
webdav_server.py. wsgidav 4.x DAV provider + cheroot WSGI szerver, kötés localhost-only (127.0.0.1:8080). Read-only.
Erőforrás-fa¶
/ ← RootCollection (DAVCollection)
└── szamlak.xlsx ← XlsxResource (DAVNonCollection)
A NavDavProvider.get_resource_inst(path, environ) dönti el, melyik osztályt példányosítja a kért útvonalra. Bármi más → 404.
PROPFIND vs GET — fontos distinkció¶
A Nextcloud (és minden WebDAV kliens) gyakran küld PROPFIND-okat metaadat-frissítésre, anélkül hogy a tartalmat letöltené. Ezek nem trigger-elhetnek NAV-hívást, különben a NAV API-t ártatlan könyvtárlistázás miatt is hammerelnénk.
- XlsxResource.__init__(path, environ)
- REQUEST_METHOD == "GET"?
- IGEN → _trigger_refresh() — maybe_refresh_delta() hívás
- NEM (PROPFIND, HEAD, OPTIONS) → refresh skip
- _ensure_cache() — render xlsx ha cache üres vagy state mozdult
- REQUEST_METHOD == "GET"?
A HEAD esetén — bár headers-only választ kérünk — szintén nem indul refresh; a méret/ETag a cache-ből jön. Ha a cache még üres (első kérés a szerver indulása óta), _ensure_cache() renderel egyet — de NAV-hívás akkor is kimarad.
HTTP fejlécek¶
| Fejléc | Forrás | Példa |
|---|---|---|
| Content-Type | fix | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
| Content-Length | len(_cache["bytes"]) |
1451511 |
| ETag | md5(content) | 3ab628c5d57e138082b71b167d68d5af |
| Last-Modified |
last_success_ts → unix → HTTP dátum |
Thu, 30 Apr 2026 20:06:55 GMT |
Authentication¶
HTTP Basic, SimpleDomainController egy user-mappinggel. A user/jelszó környezeti változókból:
NAVDAV_USER=<username> # default: navdav
NAVDAV_PASSWORD=<password> # KÖTELEZŐ — nélküle a szerver nem indul
Digest auth kikapcsolva, csak Basic. Localhost-only kötés miatt elfogadható (TLS-t a Nextcloud bekötés sem igényel ezen a szakaszon).
Bootstrap és üzemeltetés¶
Két támogatott deploy-mód: Docker (production-ready, ajánlott) és natív (fejlesztés / sandbox VM). Mindkettő idempotens — ismételt futtatás esetén frissítés.
Production deploy — egysoros, Gitea-ról¶
Előfeltételek a target gépen: Docker engine + docker compose plugin, a futtató user a docker csoport tagja, curl elérhető. Semmi más rendszer-csomag (Python, Rust, git, sqlite) nem szükséges — minden a konténerben.
curl -fsSL "http://kpeter:<TOKEN>@192.168.2.210:3000/kpeter/navdav/raw/branch/master/install-from-gitea.sh" | \
GITEA_TOKEN=<TOKEN> bash
A install-from-gitea.sh 8 lépésben telepít:
- Előfeltétel-ellenőrzés (docker, compose, daemon)
- Deploy mappa:
~/navdav -
docker-compose.ymlletöltése Gitea-ról (curl, nincs git-clone — kevesebb host-csomag) -
nav.env— interaktív tty-n bekérdezi az 5 NAV credential-t; pipe esetén utasítást ad kézi feltöltésre -
.env— random 24-karakteres NavDaV-jelszó +GITEA_TOKEN(build-időre, a NAV CLI klónozásához) -
docker compose build --pull+up -d --force-recreate - Healthcheck-megvárás (max 60s)
- Bootstrap, ha üres a DB (idempotens — második futtatáson kihagyva)
Záróképernyő: a Nextcloud External Storage-konfigurációhoz szükséges 3 érték (URL, user, password). A deploy mappában (~/navdav) marad: docker-compose.yml, nav.env, .env. A SQLite DB és cached xlsx a navdav-data Docker volume-ban perzisztál.
Frissítés — ugyanaz a parancs¶
Az install-from-gitea.sh ismételt futtatása frissítésnek minősül: friss docker-compose.yml letöltődik, image rebuild (Docker layer cache miatt gyors), konténer --force-recreate-tel újraindul. nav.env, .env és a navdav-data volume megmarad.
Docker-image szerkezete¶
| Réteg | Tartalom |
|---|---|
Stage 1 — rust:slim-bookworm
|
NAV CLI Rust-bináris fordítása (build-context-ben klónozva a Gitea-ról; csak a végbinárist viszi tovább) |
Stage 2 — python:3.12-slim-bookworm
|
Python runtime + xlsxwriter + wsgidav + cheroot + sqlite3; a NavDaV kód a build-context-ből másolva |
| Volume |
/data — SQLite DB, cached xlsx |
| Bind mount |
./nav.env → /opt/nav/.env (RO) — NAV credentials a Rust binárisnak |
| Env-file |
./.env — NavDaV credentials + GITEA_TOKEN (csak build-time) |
| Hálózat | port 8081, kötés 0.0.0.0:8081 → conténer 8081 (LAN-elérhetőség Nextcloud-szerver felé) |
| Healthcheck | 30s interval, TCP socket connect-test localhost:8081 |
| Restart policy |
unless-stopped — daemon-restartolásnál is feláll automatikusan |
Kanonikus üzemeltetési parancsok¶
| Cél | Parancs |
|---|---|
| Status | docker compose ps |
| Logok | docker compose logs -f --tail=30 |
| Manuális bootstrap (ha kell) | docker compose exec navdav python bootstrap_import.py --from 2024-01-01 --to "$(date -I)" |
| Manuális delta-refresh (CLI, throttled) | docker compose exec navdav python nav_bridge.py |
| Restart | docker compose restart |
| Stop (volume megmarad) | docker compose down |
| Stop + adat-törlés |
docker compose down -v ⚠️ a teljes navdav-data volume is törlődik |
| Smoke test | `curl -s -u "navdav:$PASS" -I http://localhost:8081/szamlak.xlsx |
Backup¶
A navdav-data Docker volume tartalmazza a teljes történeti NAV-adatot. Backup tipikusan crontab-ból:
# Minden éjjel 02:00-kor — SQLite online backup, file-konzisztens snapshot
0 2 * * * docker compose -f $HOME/navdav/docker-compose.yml exec -T navdav \
sqlite3 /data/navdav.db ".backup '/data/backups/navdav-$(date +\%Y\%m\%d).db'"
A volume-mount-on belül a /data/backups/ mappa a host-on perzisztál. Ha az adat elveszne, a bootstrap_import.py egy parancs alatt visszaállítja a NAV-ból (~3-5 perc).
Natív telepítés (alternatíva, fejlesztés)¶
Ha Docker nem elérhető vagy fejlesztői változó-tesztek kellenek: a deploy.sh script a Rust toolchain-t és Python venv-et közvetlenül a host-ra telepíti, systemd-unit-tal indít.
cd ~/navdav
sudo -E bash deploy.sh # admin-jog kell apt + systemd-unit miatt
Részletek: a script kommentjeiben. Production-ra a Docker-út ajánlott.
Nextcloud bekötés¶
A Nextcloud admin felületen External Storage (Settings → Administration → External storages):
| Mező | Érték |
|---|---|
| Name |
NAV számlák (vagy bármi) |
| External storage | WebDAV |
| URL |
http://192.168.2.211:8081/ (a NavDaV-VM, „web", LAN-os bekötés) |
| Username |
NAVDAV_USER értéke |
| Password |
NAVDAV_PASSWORD értéke |
| Available for | érintett user vagy csoport |
| Read-only | igen — a NavDaV nem fogad el írást |
Megnyitási módok¶
| Hogyan nyitja a user | Mit lát |
|---|---|
| Web UI → Collabora / OnlyOffice | A Nextcloud szerver olvas a WebDAV-ról minden megnyitáskor → mindig friss, tényleg minden alkalommal NAV-fetch (vagy throttled) |
| Desktop sync + Excel | A kliens ETag-alapján dönt — ha a hash ugyanaz, csak cached fájlt nyit; ha új render, újra letölti |
| Virtual files | Ugyanaz mint a desktop — ETag-vezérelt |
Fájlok és tech stack¶
| Fájl | Mit tartalmaz | Technológia |
|---|---|---|
schema.sql |
SQLite séma — invoices, invoice_lines, state, indexek |
SQL WAL mode, FK on |
nav_bridge.py |
NAV subprocess + UPSERT + state + throttle/lock + CLI | Python stdlib only |
render_xlsx.py |
SQLite → in-memory xlsx, két lap, autofilter, freeze | Python + xlsxwriter
|
webdav_server.py |
WebDAV provider (DAVProvider, DAVCollection, DAVNonCollection) + render-cache + Basic auth | Python + wsgidav + cheroot
|
bootstrap_import.py |
Vékony wrapper a nav_bridge.refresh() körül, default 2024-01-01 → ma
|
Python |
requirements.txt |
Csak runtime deps: xlsxwriter, wsgidav, cheroot
|
— |
Dockerfile |
Multi-stage build: Rust-stage NAV CLI + Python-stage NavDaV runtime; env-paraméteres path-ok | Docker |
docker-compose.yml |
Production deploy konfig: remote Gitea-context build, healthcheck, volumes, port mapping, restart policy | Compose |
install-from-gitea.sh |
Egysoros telepítő (curl | bash); idempotens; nav.env+.env generálás, build, healthcheck, bootstrap |
Bash |
deploy.sh |
Natív (Docker nélküli) deploy alternatíva — Rust + Python venv + systemd | Bash |
navdav.db (vagy Docker volume) |
SQLite akkumulátor — nincs Git-ben. Docker módban a navdav-data volume-ban |
— |
Külső függőség (nem ebben a repóban)¶
/home/petitan/nav (Rust binary)
Rust A
nav-online-invoicecrate. A NavDaV minden subprocess-hívást ennek a release build-jére intéz:/home/petitan/nav/target/release/nav-online-invoice. Az.envott található (NAV credential-ok), ezért a subprocesscwd=/home/petitan/nav-val indul.A NavDaV két subcommand-ot használ:
valtozas -d <dir> --since <ts>a delta-frissítéshez (insDate-alapú szűrés, késedelmes-beküldés-immunis), éstetelek -d <dir> -f X -t Ya bootstrap (teljes történeti betöltés) céljára. NAV CLI minimum verzió: v1.2.0 (a `valtozas` 32 mezős teljes outputja az ISSUES #7 megoldásával jött).
Nyitott pontok és iteratív állás¶
Iteratív implementációs terv¶
| # | Lépés | Állapot |
|---|---|---|
| 1 | Bootstrap-import script (Rust JSON → SQLite) | kész |
| 2 | xlsx render (SQLite → xlsx, CLI-tesztelhető) | kész |
| 3 | NAV bridge (delta + state, cron-kompatibilis) | kész |
| 4 | Throttle + lock | kész |
| 5 | WebDAV provider, Basic auth, ETag/mtime | kész |
| 6 | Nextcloud External Storage bekötés (LAN-bekötés) | kész |
| 7 | Docker-image (multi-stage Rust+Python), docker-compose, healthcheck | kész |
| 8 | Egysoros telepítő Gitea-ról (install-from-gitea.sh) |
kész |
| 9 | Code review (code-stasi 36 találat) — silent fallback eliminálás, FK PRAGMA, throttle CLI-n, sheet konzisztencia | kész (1 elem halasztva: Docker token-leak — BuildKit secret mount) |
Megfigyelt apróságok¶
1. Nem-determinisztikus xlsx bytes. Két egymás utáni render eltérő MD5-öt és 1 byte-os méretkülönbséget produkál ugyanazon adat mellett. Az xlsx zip belső metadatája (xlsxwriter által beágyazott creation timestamp) okozza, nem az adat. Hatás: a Nextcloud kliens minden render után egyszer újratölti a fájlt. Ha zavaró, a renderer determinizálható (xlsxwriter
set_properties()+ fix időbélyeg, vagy a zip-en belülicore.xmlpostprocess).
2. Cross-window duplikátumok. A Rust binary 35 napos chunk-szeleteléséből egyes számlák két szomszédos ablakban is megjelennek a határnál. Az UPSERT a
transaction_id-vel ezeket helyesen deduplikálja — nem hiba, ezt akarjuk.
3. MODIFY/STORNO az éles adatban (2024-01..2026-05) nem volt. A séma (
modification_index,original_invoice_numberoszlopok) felkészült rá, de az audit-lánc xlsx-ben való vizuális elrendezése csak akkor finomítható tovább, ha lesz is rajta tényleges esemény.
NAV projektben felvett és lezárt issue-k¶
A NavDaV közvetlenül a nav-online-invoice Rust binárisra épül. A fejlesztés során három egymást követő issue került felvételre és megoldásra a NAV projektben — mindegyik közvetlenül érintett egy NavDaV mezőt vagy működést.
| # | Probléma | NavDaV-hatás | Megoldás |
|---|---|---|---|
#2 |
tetelek JSON-ból hiányzott stabil esemény-PK |
content-hash workaround vagy gyenge (invoice_number, op) kulcs |
lezárva 2026-04-30 — négy új mező: transaction_id, batch_index, modification_index, original_invoice_number
|
#3 |
tetelek üres customer_name-et adott amikor a NAV XML-ben & volt |
31 OUTBOUND számla partner-mezője üres maradt (~0,8% a 28 hónapos adatra) | lezárva 2026-04-30 — block-rekonstrukcióban a raw bytes megmarad, az XML-escape-ek életben maradnak az újra-parsolásig. Regression unit teszt hozzáadva |
#4 |
tetelek JSON-ból hiányzott a customer_vat_status
|
nem lehetett egyetlen lekérdezésből kiszűrni a 12 EU-s + magánszemély false-positive-ot a downstream hibalistákban | lezárva 2026-04-30 — pontosítás: a NAV digest-ben nincs ez a tag, csak a per-számla XML-ben → új mező az InvoiceWithLines-on (analóg az invoice_delivery_date-hez) |
#5 |
tetelek üres net+vat SIMPLIFIED-en, hiányzó invoice_gross_amount mindenhol |
87 SIMPLIFIED + 24 AGGREGATE számla összegei üresek voltak az xlsx-ben | lezárva 2026-05-01 — két új mező: invoice_gross_amount, invoice_gross_amount_huf. SIMPLIFIED-en a net+vat továbbra is NULL (a NAV maga nem ad), de a gross most ki van töltve |
#6 |
tetelek-ből hiányzott 10 könyvelői alapmező (payment_date, payment_method, exchange_rate, invoice_appearance, supplier/customer címek, invoice_accounting_delivery_date, community_vat_number, completeness_indicator, supplier_bank_account_number) |
könyvelői munkafájlból hiányzott a 10 alapmező — utalás-tervezés, főkönyvi feladás, partner-nyilvántartás bővítés ellehetetlenült | lezárva 2026-05-01 — új helper extract_invoice_metadata() a per-számla XML-ből 14 mezőt egyszerre kinyer; extract_address() a címekhez (silent fallback elkerülése). 10 új mező a tetelek -o json-ban + 1 mező (supplier_bank_account_number) a szamla -o json-ban. JSON ↔ CSV minden bővülés egy commitban |
#7 |
valtozas JSON csak digest-szintű 17 mezőt ad — nem használható tetelek-helyettesítőként |
insDate-alapú késedelmes-beküldés-immunis delta-szinkron nem indítható helyileg adatvesztés nélkül | lezárva 2026-05-03 (v1.2.0) — közös enrich_digests_with_invoice_data() helper a tetelek és valtozas között, JSON kulcskészlet bit-pontosan azonos (33 mező + lines) |
#8 |
tetelek/valtozas JSON-ból hiányzik a tétel-szintű <advanceData> (előleg-jelölés) |
351 előleg-tétel csak szöveg-alapon (`line_description LIKE '%Előleg%'`) volt szűrhető, strukturált megkülönböztetés nélkül | lezárva 2026-05-03 (v1.3.0) — advance_indicator minden tételsoron + audit-helper |
#9 |
11 új tétel-szintű mező + invoice-szintű <summaryByVatRate>
|
könyvelői xlsx-ben tételsoronkénti bruttó (NORMAL+SIMPLIFIED), kedvezmény, termékkód, jelleg, tétel-teljesítés; ÁFA-bevalláshoz rátarendszer-bontás | lezárva 2026-05-03 (v1.4.0+v2.0.0 fázis 4) — proaktív audit-bővítés és vat_rate_summaries |
Az issue-k megoldása után bootstrap_import.py --from 2024-01-01 backfilleli minden korábbi adatot:
| Mérőszám | Előtte | Utána |
|---|---|---|
Üres OUTBOUND customer_name adószámmal |
31 | 0 |
&-tartalmú partner-nevek (28 hó, mindkét irány) |
0 (silent fail) | 51 |
customer_vat_status kitöltöttség |
nem létezett | 5798/5798 (DOMESTIC: 5786, OTHER: 5, PRIVATE_PERSON: 7) |
| SIMPLIFIED+AGGREGATE számlák Bruttó kitöltöttsége | 0 / 111 | 111 / 111 |
Fiz. határidő (payment_date) kitöltöttség |
0 / 5798 | 5569 / 5798 (a 229 hiányzó NAV-tól sem jön — pl. készpénzes vagy régi) |
Számlavezető bank (supplier_bank_account_number) |
0 / 5798 | 4861 / 5798 (1105 INBOUND + 3756 OUTBOUND) |
Cím (supplier_address, customer_address) |
0 / 5798 | 5798 / 5798 supplier_address; 5791 / 5798 customer_address |
A NavDaV pipeline maga (Python, SQLite, xlsxwriter) az ellenőrzés során minden lépésben helyesen kezelte az & karaktert — a hiba a Rust digest-XML extractor block-rekonstrukciós ágában volt, a tetelek -o json emisszió forrásánál. A szamla -o json path mindvégig korrektül dekódolta ugyanazokat a számlákat.
NavDaV — NAV Online Számla → Nextcloud Excel híd · Petitan Kft.
Módosította Zoltán Lehotai 3 napja · 1 revízió