Projekt

Általános

Profil

Műveletek

NavDaV

NAV Online Számla → SQLite akkumulátor → on-demand xlsx render → WebDAV → Nextcloud External Storage

Tartalom

  1. Mire való ez a rendszer?
  2. Architektúra
  3. Adatfolyam — egy fájlmegnyitás
  4. SQLite séma
  5. NAV bridge — delta + state
  6. Throttle és render-cache
  7. xlsx renderer
  8. WebDAV provider
  9. Production deploy és üzemeltetés
  10. Nextcloud bekötés
  11. Fájlok és tech stack
  12. 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-invoice Rust 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

  1. 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.
  2. 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.
  3. xlsx renderer (render_xlsx.py) — SQLite → in-memory bytes (xlsxwriter). Két lap, autofilter, freeze pane, dátum/szám-formázás.
  4. 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:

  1. Auth — HTTP Basic, NAVDAV_USER / NAVDAV_PASSWORD env-ből
    Hibás → 401, válasz nincs
  2. Throttle gatemaybe_refresh_delta() egy module-szintű threading.Lock-ot vesz fel
    Ha last_check_ts < 60s → skip (nincs NAV-hívás)
  3. 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ú
  4. 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
  5. JSON parse + UPSERTtransaction_id mint PK, ON CONFLICT DO UPDATE
    Számla előtti tételsorok törlése, friss insert (idempotens)
  6. 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
  7. Render-cache döntés — ha a state-beli last_success_ts egyezik a cache-belivel → cache HIT
    Egyébként render_xlsx.render(conn, BytesIO) , MD5 számolás, cache frissítés
  8. 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 json kimenetben nem volt sem batch_index, sem stabil esemény-PK. A NAV-projekt #2 issue-ja erre épült: a Rust oldal hozzáadta a transaction_id, batch_index, modification_index, original_invoice_number mező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_address
customer_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_date
invoice_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
    • _refresh_lock release

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_ts vs 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.scheduler logger), 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ásikban 4 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ó és Bruttó (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 a Nettó + ÁFA összegként számolható. Ugyanez vonatkozik az ÁFA-tartalom oszlopra: 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 renderer Decimal()-en keresztül konvertál float-tá az xlsx cella számértékéhez (lebegőpontos hiba ellen). A vat_percentage 0.27 jön — Excel 0% 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

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:

  1. Előfeltétel-ellenőrzés (docker, compose, daemon)
  2. Deploy mappa: ~/navdav
  3. docker-compose.yml letöltése Gitea-ról (curl, nincs git-clone — kevesebb host-csomag)
  4. 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
  5. .env — random 24-karakteres NavDaV-jelszó + GITEA_TOKEN (build-időre, a NAV CLI klónozásához)
  6. docker compose build --pull + up -d --force-recreate
  7. Healthcheck-megvárás (max 60s)
  8. 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-invoice crate. A NavDaV minden subprocess-hívást ennek a release build-jére intéz: /home/petitan/nav/target/release/nav-online-invoice. Az .env ott található (NAV credential-ok), ezért a subprocess cwd=/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), és tetelek -d <dir> -f X -t Y a 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üli core.xml postprocess).

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_number oszlopok) 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 &amp; 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ó