NavDaV » Történet » Verzió 1
Zoltán Lehotai, 2026-05-05 09:10
| 1 | 1 | Zoltán Lehotai | |
|---|---|---|---|
| 2 | # NavDaV |
||
| 3 | |||
| 4 | NAV Online Számla → SQLite akkumulátor → on-demand xlsx render → WebDAV → Nextcloud External Storage |
||
| 5 | |||
| 6 | **Tartalom** |
||
| 7 | |||
| 8 | 1. [Mire való ez a rendszer?](#mire-valo) |
||
| 9 | 2. [Architektúra](#architektura) |
||
| 10 | 3. [Adatfolyam — egy fájlmegnyitás](#adatfolyam) |
||
| 11 | 4. [SQLite séma](#schema) |
||
| 12 | 5. [NAV bridge — delta + state](#nav-bridge) |
||
| 13 | 6. [Throttle és render-cache](#throttle) |
||
| 14 | 7. [xlsx renderer](#xlsx) |
||
| 15 | 8. [WebDAV provider](#webdav) |
||
| 16 | 9. [Production deploy és üzemeltetés](#bootstrap) |
||
| 17 | 10. [Nextcloud bekötés](#nextcloud) |
||
| 18 | 11. [Fájlok és tech stack](#fajlok) |
||
| 19 | 12. [Iteratív állás és lezárt issue-k](#nyitott) |
||
| 20 | |||
| 21 | ## Mire való ez a rendszer? |
||
| 22 | |||
| 23 | > **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. |
||
| 24 | |||
| 25 | 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. |
||
| 26 | |||
| 27 | 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. |
||
| 28 | |||
| 29 | > **1. Lekér** |
||
| 30 | > |
||
| 31 | > 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. |
||
| 32 | |||
| 33 | > **2. Akkumulál** |
||
| 34 | > |
||
| 35 | > 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. |
||
| 36 | |||
| 37 | > **3. Renderel** |
||
| 38 | > |
||
| 39 | > 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. |
||
| 40 | |||
| 41 | **5802** számla \| **13549** tételsor \| **28** hónap \| **60s** throttle \| **2.2MB** xlsx \| **6** xlsx-lap |
||
| 42 | |||
| 43 | ## Architektúra |
||
| 44 | |||
| 45 | 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. |
||
| 46 | |||
| 47 | > **A négy réteg** |
||
| 48 | > |
||
| 49 | > 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.* |
||
| 50 | > 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. |
||
| 51 | > 3. **xlsx renderer** (`render_xlsx.py`) — SQLite → in-memory bytes (`xlsxwriter`). Két lap, autofilter, freeze pane, dátum/szám-formázás. |
||
| 52 | > 4. **WebDAV provider** (`webdav_server.py`) — egyetlen virtuális fájlt expose-ál, Basic auth, render-cache, ETag/Last-Modified. |
||
| 53 | |||
| 54 | ### A két állapottároló |
||
| 55 | |||
| 56 | > **SQLite — perzisztens akkumulátor** |
||
| 57 | > |
||
| 58 | > `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. |
||
| 59 | |||
| 60 | > **Process-memória — render-cache** |
||
| 61 | > |
||
| 62 | > 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. |
||
| 63 | |||
| 64 | ## Adatfolyam — egy fájlmegnyitás |
||
| 65 | |||
| 66 | A felhasználó megnyitja `szamlak.xlsx`-et a Nextcloudban → a Nextcloud egy HTTP `GET`-et küld a WebDAV szerverünknek. Innentől: |
||
| 67 | |||
| 68 | 1. **Auth** — HTTP Basic, `NAVDAV_USER` / `NAVDAV_PASSWORD` env-ből |
||
| 69 | *Hibás → 401, válasz nincs* |
||
| 70 | 2. **Throttle gate** — `maybe_refresh_delta()` egy module-szintű `threading.Lock`-ot vesz fel |
||
| 71 | *Ha last_check_ts \< 60s → skip (nincs NAV-hívás)* |
||
| 72 | 3. **Delta — \`valtozas --since last_valtozas_ts\`** |
||
| 73 | *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ú* |
||
| 74 | 4. **Két subprocess-hívás** — kimenő + bejövő irány külön |
||
| 75 | *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* |
||
| 76 | 5. **JSON parse + UPSERT** — `transaction_id` mint PK, `ON CONFLICT DO UPDATE` |
||
| 77 | *Számla előtti tételsorok törlése, friss insert (idempotens)* |
||
| 78 | 6. **State frissítés** — siker → `last_success_date`, `last_success_ts`; bármi → `last_check_ts` |
||
| 79 | *NAV-hiba esetén csak a last_check_ts frissül, hogy a throttle ne hammerezze a hibás API-t* |
||
| 80 | 7. **Render-cache döntés** — ha a state-beli `last_success_ts` egyezik a cache-belivel → cache HIT |
||
| 81 | *Egyébként render_xlsx.render(conn, BytesIO) , MD5 számolás, cache frissítés* |
||
| 82 | 8. **HTTP válasz** — Content-Length, ETag, Last-Modified, Content-Type, body |
||
| 83 | *A Nextcloud kliens ETag-alapján dönti el, letöltse-e újra (sok user, kevés sávszél)* |
||
| 84 | |||
| 85 | ## SQLite séma |
||
| 86 | |||
| 87 | 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). |
||
| 88 | |||
| 89 | ### invoices — primary key |
||
| 90 | |||
| 91 | 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. |
||
| 92 | |||
| 93 | > **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. |
||
| 94 | |||
| 95 | ### Mezők |
||
| 96 | |||
| 97 | <table> |
||
| 98 | <colgroup> |
||
| 99 | <col style="width: 33%" /> |
||
| 100 | <col style="width: 33%" /> |
||
| 101 | <col style="width: 33%" /> |
||
| 102 | </colgroup> |
||
| 103 | <thead> |
||
| 104 | <tr class="header"> |
||
| 105 | <th>Mező</th> |
||
| 106 | <th>Forrás</th> |
||
| 107 | <th>Megjegyzés</th> |
||
| 108 | </tr> |
||
| 109 | </thead> |
||
| 110 | <tbody> |
||
| 111 | <tr class="odd"> |
||
| 112 | <td><code>transaction_id</code></td> |
||
| 113 | <td>NAV <code><transactionId></code></td> |
||
| 114 | <td><strong>PK</strong>. Beküldési köteg ID, számlánként egyedi</td> |
||
| 115 | </tr> |
||
| 116 | <tr class="even"> |
||
| 117 | <td><code>invoice_number</code></td> |
||
| 118 | <td>NAV</td> |
||
| 119 | <td>Az emberek számára olvasható számlaszám (pl. <code>SZ/2026/00567</code>)</td> |
||
| 120 | </tr> |
||
| 121 | <tr class="odd"> |
||
| 122 | <td><code>direction</code></td> |
||
| 123 | <td>(NavDaV)</td> |
||
| 124 | <td><code>OUTBOUND</code> / <code>INBOUND</code> — a <code>kimeno</code> / <code>bejovo</code> Rust-irányból</td> |
||
| 125 | </tr> |
||
| 126 | <tr class="even"> |
||
| 127 | <td><code>invoice_operation</code></td> |
||
| 128 | <td>NAV</td> |
||
| 129 | <td><code>CREATE</code> / <code>MODIFY</code> / <code>STORNO</code></td> |
||
| 130 | </tr> |
||
| 131 | <tr class="odd"> |
||
| 132 | <td><code>modification_index</code></td> |
||
| 133 | <td>NAV <code><modificationIndex></code></td> |
||
| 134 | <td>Több MODIFY ugyanahhoz a számlához → 1, 2, …; CREATE-nél <code>NULL</code></td> |
||
| 135 | </tr> |
||
| 136 | <tr class="even"> |
||
| 137 | <td><code>original_invoice_number</code></td> |
||
| 138 | <td>NAV <code><originalInvoiceNumber></code></td> |
||
| 139 | <td>MODIFY/STORNO esetén az eredeti CREATE számlaszáma</td> |
||
| 140 | </tr> |
||
| 141 | <tr class="odd"> |
||
| 142 | <td><code>batch_index</code></td> |
||
| 143 | <td>NAV <code><index></code></td> |
||
| 144 | <td>Pozíció a NAV-beküldési kötegen belül (audit-info)</td> |
||
| 145 | </tr> |
||
| 146 | <tr class="even"> |
||
| 147 | <td><code>supplier_*, customer_*</code></td> |
||
| 148 | <td>NAV digest XML</td> |
||
| 149 | <td>Iránytól függően az egyik a partner, a másik mi</td> |
||
| 150 | </tr> |
||
| 151 | <tr class="odd"> |
||
| 152 | <td><code>customer_vat_status</code></td> |
||
| 153 | <td>NAV per-számla XML <code><customerVatStatus></code></td> |
||
| 154 | <td><code>DOMESTIC</code> / <code>OTHER</code> (EU-s) / <code>PRIVATE_PERSON</code>. A digest-ben nincs, csak a per-számla XML-ben — a <code>tetelek</code> path tölti, a <code>mind</code>/<code>kimeno</code>/<code>bejovo</code> nem</td> |
||
| 155 | </tr> |
||
| 156 | <tr class="even"> |
||
| 157 | <td><code>invoice_net_amount(_huf)</code><br /> |
||
| 158 | <code>invoice_vat_amount(_huf)</code><br /> |
||
| 159 | <code>invoice_gross_amount(_huf)</code></td> |
||
| 160 | <td>NAV (digest a net+vat-ot, per-számla XML a gross-t)</td> |
||
| 161 | <td>String-ként tárolva (decimal pontosság miatt). SIMPLIFIED-en a net+vat <code>NULL</code> — a NAV nem ad áfa-bontást, csak gross-t</td> |
||
| 162 | </tr> |
||
| 163 | <tr class="odd"> |
||
| 164 | <td><code>invoice_accounting_delivery_date</code></td> |
||
| 165 | <td>NAV per-számla XML</td> |
||
| 166 | <td>Számviteli teljesítés napja (gyakran ≠ <code>invoice_delivery_date</code>)</td> |
||
| 167 | </tr> |
||
| 168 | <tr class="even"> |
||
| 169 | <td><code>payment_date</code></td> |
||
| 170 | <td>NAV per-számla XML <code><paymentDate></code></td> |
||
| 171 | <td>Fizetési határidő — utalás-tervezéshez</td> |
||
| 172 | </tr> |
||
| 173 | <tr class="odd"> |
||
| 174 | <td><code>payment_method</code></td> |
||
| 175 | <td>NAV per-számla XML <code><paymentMethod></code></td> |
||
| 176 | <td>TRANSFER / CASH / CARD / VOUCHER / OTHER</td> |
||
| 177 | </tr> |
||
| 178 | <tr class="even"> |
||
| 179 | <td><code>supplier_address</code><br /> |
||
| 180 | <code>customer_address</code></td> |
||
| 181 | <td>NAV per-számla XML</td> |
||
| 182 | <td>Összevont string (pl. <code>HU 6727 Szeged, Algyői út 19 A.</code>) — partner-nyilvántartás bővítéshez</td> |
||
| 183 | </tr> |
||
| 184 | <tr class="odd"> |
||
| 185 | <td><code>supplier_bank_account_number</code></td> |
||
| 186 | <td>NAV per-számla XML</td> |
||
| 187 | <td>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</td> |
||
| 188 | </tr> |
||
| 189 | <tr class="even"> |
||
| 190 | <td><code>community_vat_number</code></td> |
||
| 191 | <td>NAV per-számla XML</td> |
||
| 192 | <td>EU adóazonosító (pl. <code>HU24696371</code>). A kibocsátóé. INBOUND-on csak EU-s/külföldi szállítóknál van kitöltve</td> |
||
| 193 | </tr> |
||
| 194 | <tr class="odd"> |
||
| 195 | <td><code>exchange_rate</code></td> |
||
| 196 | <td>NAV per-számla XML</td> |
||
| 197 | <td>Pénznem-árfolyam HUF-ra. <code>"1"</code> HUF-számláknál</td> |
||
| 198 | </tr> |
||
| 199 | <tr class="even"> |
||
| 200 | <td><code>invoice_appearance</code></td> |
||
| 201 | <td>NAV per-számla XML</td> |
||
| 202 | <td>ELECTRONIC / PAPER / EDI / UNKNOWN — Sztv. tárolási előírás</td> |
||
| 203 | </tr> |
||
| 204 | <tr class="odd"> |
||
| 205 | <td><code>completeness_indicator</code></td> |
||
| 206 | <td>NAV per-számla XML</td> |
||
| 207 | <td>NAV digital invoice teljesszerűségi flag (string <code>"true"</code>/<code>"false"</code>)</td> |
||
| 208 | </tr> |
||
| 209 | <tr class="even"> |
||
| 210 | <td><code>invoice_issue_date</code><br /> |
||
| 211 | <code>invoice_delivery_date</code></td> |
||
| 212 | <td>NAV</td> |
||
| 213 | <td>ISO dátum string</td> |
||
| 214 | </tr> |
||
| 215 | <tr class="odd"> |
||
| 216 | <td><code>currency</code></td> |
||
| 217 | <td>NAV</td> |
||
| 218 | <td><code>HUF</code>, <code>EUR</code>, …</td> |
||
| 219 | </tr> |
||
| 220 | <tr class="even"> |
||
| 221 | <td><code>raw_json</code></td> |
||
| 222 | <td>(NavDaV)</td> |
||
| 223 | <td>A teljes JSON sorrendezett alakja — debug és későbbi remap miatt</td> |
||
| 224 | </tr> |
||
| 225 | <tr class="odd"> |
||
| 226 | <td><code>ins_ts</code></td> |
||
| 227 | <td>(NavDaV)</td> |
||
| 228 | <td>UTC timestamp az első insert-kor (UPDATE nem módosítja)</td> |
||
| 229 | </tr> |
||
| 230 | </tbody> |
||
| 231 | </table> |
||
| 232 | |||
| 233 | ### state — kulcs-érték |
||
| 234 | |||
| 235 | | Kulcs | Mit jelent | |
||
| 236 | |---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
||
| 237 | | `last_check_ts` | UTC ISO timestamp az utolsó NAV-hívási **kísérletről** (siker és hiba is). Throttle bemenete. | |
||
| 238 | | `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). | |
||
| 239 | | `last_success_date` | Visszafelé kompatibilitásra a \`--full\`/\`--from\`/\`--to\` (tetelek-alapú) refresh-hez. | |
||
| 240 | | `last_success_ts` | UTC ISO timestamp az utolsó sikeres refresh-ről. A render-cache invalidáció bemenete. | |
||
| 241 | |||
| 242 | ## NAV bridge — delta + state |
||
| 243 | |||
| 244 | `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. |
||
| 245 | |||
| 246 | ### Public API |
||
| 247 | |||
| 248 | | Függvény | Mit csinál | |
||
| 249 | |----------------------------------------------|----------------------------------------------------------------------| |
||
| 250 | | `init_db(path)` | SQLite kapcsolat + séma (`schema.sql`) végrehajtás | |
||
| 251 | | `call_nav(direction, frm, to, ...)` | Subprocess-hívás a Rust binary-ra, JSON visszaadás | |
||
| 252 | | `upsert_invoices(conn, invoices, direction)` | UPSERT az `invoices` + tételsor-újraírás | |
||
| 253 | | `compute_delta_window(conn)` | (frm, to) — vagy `last_success_date − 2`, vagy `2024-01-01` ha üres | |
||
| 254 | | `refresh(conn, frm, to, ...)` | A teljes lánc: két irány fetch + upsert + state update. Hibát feldob | |
||
| 255 | | `maybe_refresh_delta(conn)` | Throttled, lock-olt verzió. **Ezt hívja a WebDAV provider.** | |
||
| 256 | |||
| 257 | ### maybe_refresh_delta — döntési fa |
||
| 258 | |||
| 259 | - GET érkezik a /szamlak.xlsx-re |
||
| 260 | - \_refresh_lock acquire |
||
| 261 | - last_check_ts létezik és \< 60s? |
||
| 262 | - IGEN → throttled, return {throttled: True} (nincs NAV-hívás) |
||
| 263 | - NEM → refresh() futtatás |
||
| 264 | - SIKER → last_success_date + last_success_ts + last_check_ts frissítés |
||
| 265 | - HIBA → record_check_failure() → csak last_check_ts frissül |
||
| 266 | - \_refresh_lock release |
||
| 267 | |||
| 268 | 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. |
||
| 269 | |||
| 270 | ### Hibakezelés filozófia |
||
| 271 | |||
| 272 | > **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. |
||
| 273 | |||
| 274 | ## Throttle és render-cache |
||
| 275 | |||
| 276 | Két különböző cache van, ne keverjük össze: |
||
| 277 | |||
| 278 | > **Throttle (NAV-hívás cache)** |
||
| 279 | > |
||
| 280 | > **Cél:** a NAV API ne kapjon 60 másodpercen belül több hívást. |
||
| 281 | > |
||
| 282 | > **Bemenet:** `state.last_check_ts` (DB-ben, restart-tűrő). |
||
| 283 | > |
||
| 284 | > **Ablak:** 60 mp (`THROTTLE_SECONDS`). |
||
| 285 | > |
||
| 286 | > **Ha hit:** a kód a régi DB-tartalmat fogja renderelni — friss API-hívás nem történik. |
||
| 287 | |||
| 288 | > **Render-cache (xlsx-bytes cache)** |
||
| 289 | > |
||
| 290 | > **Cél:** ne renderelje újra az xlsx-et minden GET-re ha az adat nem változott. |
||
| 291 | > |
||
| 292 | > **Bemenet:** `state.last_success_ts` vs cache-ben tárolt érték. |
||
| 293 | > |
||
| 294 | > **Ablak:** nincs idő — amíg a state nem mozdul, hit. |
||
| 295 | > |
||
| 296 | > **Ha hit:** ugyanazt a bytes-objektumot adjuk vissza, ETag is azonos → Nextcloud nem tölti újra. |
||
| 297 | |||
| 298 | Tipikus szekvencia (két párhuzamos GET 5 mp különbséggel): |
||
| 299 | |||
| 300 | | Idő | Esemény | NAV-hívás | Render | Bytes-méret | |
||
| 301 | |------|---------|----------------|------------------------------------------------|------------------| |
||
| 302 | | T+0 | 1\. GET | igen | igen (state változott) | 1.45 MB | |
||
| 303 | | T+5 | 2\. GET | nem (throttle) | nem (cache hit) | 1.45 MB (cached) | |
||
| 304 | | T+90 | 3\. GET | igen | függ — ha új esemény jött, igen; egyébként nem | — | |
||
| 305 | |||
| 306 | ### Beépített delta-scheduler |
||
| 307 | |||
| 308 | 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. |
||
| 309 | |||
| 310 | | Paraméter | Érték | Forrás | |
||
| 311 | |------------------|---------------------------------|-----------------------------------------------------------| |
||
| 312 | | Default periódus | 1800 s (30 perc) | `delta_scheduler.DEFAULT_INTERVAL_SECONDS` | |
||
| 313 | | CLI flag | `--delta-interval <sec>` | `webdav_server.py` | |
||
| 314 | | Env var | `NAVDAV_DELTA_INTERVAL_SECONDS` | docker-compose.yml-ben állítható | |
||
| 315 | | Letiltás | `--delta-interval=0` | például manuális cron mellett | |
||
| 316 | | Min. érték | 60 s | `DeltaScheduler.__init__` ValueError-t dob ennél kisebbre | |
||
| 317 | |||
| 318 | 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. |
||
| 319 | |||
| 320 | > **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. |
||
| 321 | |||
| 322 | ## xlsx renderer |
||
| 323 | |||
| 324 | `render_xlsx.py`. `xlsxwriter` backend, in-memory mód (`{"in_memory": True}`), `BytesIO` output. Két lap, autofilter, freeze pane. |
||
| 325 | |||
| 326 | ### Lap: Számlák (egy sor = egy NAV-esemény) |
||
| 327 | |||
| 328 | 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. |
||
| 329 | |||
| 330 | | \# | Oszlop | Forrás | Formátum | |
||
| 331 | |-------|-------------------------------|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------| |
||
| 332 | | 1 | Irány | `direction` | magyar címke (Bejövő / Kimenő) | |
||
| 333 | | 2 | Számlaszám | `invoice_number` | — | |
||
| 334 | | 3 | Művelet | `invoice_operation` | CREATE / MODIFY / STORNO | |
||
| 335 | | 4 | Eredeti számla | `original_invoice_number` | STORNO/MODIFY-nál a CREATE száma | |
||
| 336 | | 5 | Utólagos művelet | subquery: ugyanezen invoice_number-re hivatkozó STORNO/MODIFY | pl. `STORNO SZ/2026/00405`; láncolat esetén pontosvesszővel | |
||
| 337 | | 6 | Kelt | `invoice_issue_date` | `yyyy.mm.dd.` | |
||
| 338 | | 7 | Teljesítés | `invoice_delivery_date` | `yyyy.mm.dd.` | |
||
| 339 | | 8 | Számv. teljesítés | `invoice_accounting_delivery_date` | `yyyy.mm.dd.` — Sztv. szerinti könyvelési teljesítés napja | |
||
| 340 | | 9 | Fizetési határidő | `payment_date` | `yyyy.mm.dd.` | |
||
| 341 | | 10–11 | Partner / Adószám | iránytól függő (supplier vagy customer) | — | |
||
| 342 | | 12 | Közösségi adószám | `community_vat_number` | Áfa tv. szerinti közösségi adóazonosító — a kibocsátóé | |
||
| 343 | | 13 | Partner címe | iránytól függő (supplier_address vagy customer_address) | összevont string | |
||
| 344 | | 14 | Bankszámlaszám | `supplier_bank_account_number` | A kibocsátóé — INBOUND-on a fizetési cél, OUTBOUND-on a saját | |
||
| 345 | | 15 | Adóalany státusza | `customer_vat_status` | DOMESTIC / OTHER / PRIVATE_PERSON | |
||
| 346 | | 16 | Bizonylattípus | `invoice_category` | NORMAL / SIMPLIFIED / AGGREGATE — Sztv. és Áfa tv. szerinti számla-típus | |
||
| 347 | | 17 | Megjelenési forma | `invoice_appearance` | ELECTRONIC / PAPER / EDI / UNKNOWN — NAV hivatalos terminus | |
||
| 348 | | 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 | |
||
| 349 | | 19 | Pénznem | `currency` | ISO 4217 kód | |
||
| 350 | | 20 | Árfolyam | `exchange_rate` | HUF-ra; `1` HUF-számláknál | |
||
| 351 | | 21 | Fizetési mód | `payment_method` | TRANSFER / CASH / CARD / VOUCHER / OTHER | |
||
| 352 | | 22–23 | Nettó / ÁFA (eredeti pénznem) | `invoice_net_amount` / `invoice_vat_amount` | per-row: HUF→`#,##0\ "Ft";[Red]-…;-`, egyéb→`#,##0.00;[Red]-…;-` | |
||
| 353 | | 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 | |
||
| 354 | | 25 | Bruttó (eredeti pénznem) | `invoice_gross_amount` vagy `net+vat` | mint Nettó/ÁFA — per-row HUF/deviza | |
||
| 355 | | 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 | |
||
| 356 | |||
| 357 | Autofilter, freeze az 1. sor + 2 oszlop, sorrendezés: `invoice_issue_date DESC, invoice_number DESC`. |
||
| 358 | |||
| 359 | > **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. |
||
| 360 | |||
| 361 | > **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. |
||
| 362 | |||
| 363 | ### Lap: Tételek (egy sor = egy számlatétel) |
||
| 364 | |||
| 365 | 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.###`. |
||
| 366 | |||
| 367 | > **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. |
||
| 368 | |||
| 369 | ### Lap: ÁFA-bontás (egy sor = egy ÁFA-rátarendszer egy számlán) |
||
| 370 | |||
| 371 | 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). |
||
| 372 | |||
| 373 | ### Lap: Duplikáció-gyanú (egy sor = egy gyanús csoport) |
||
| 374 | |||
| 375 | 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. |
||
| 376 | |||
| 377 | 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. |
||
| 378 | |||
| 379 | ### Lap: Időbeli kérdés (egy sor = egy számla) |
||
| 380 | |||
| 381 | 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. |
||
| 382 | |||
| 383 | ### Lap: Partner-egyezés (egy sor = egy csoport) |
||
| 384 | |||
| 385 | 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. |
||
| 386 | |||
| 387 | > **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. |
||
| 388 | |||
| 389 | ## WebDAV provider |
||
| 390 | |||
| 391 | `webdav_server.py`. `wsgidav` 4.x DAV provider + `cheroot` WSGI szerver, kötés **localhost-only** (`127.0.0.1:8080`). Read-only. |
||
| 392 | |||
| 393 | ### Erőforrás-fa |
||
| 394 | |||
| 395 | / ← RootCollection (DAVCollection) |
||
| 396 | └── szamlak.xlsx ← XlsxResource (DAVNonCollection) |
||
| 397 | |||
| 398 | 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. |
||
| 399 | |||
| 400 | ### PROPFIND vs GET — fontos distinkció |
||
| 401 | |||
| 402 | 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. |
||
| 403 | |||
| 404 | - XlsxResource.\_\_init\_\_(path, environ) |
||
| 405 | - REQUEST_METHOD == "GET"? |
||
| 406 | - IGEN → \_trigger_refresh() — maybe_refresh_delta() hívás |
||
| 407 | - NEM (PROPFIND, HEAD, OPTIONS) → refresh skip |
||
| 408 | - \_ensure_cache() — render xlsx ha cache üres vagy state mozdult |
||
| 409 | |||
| 410 | 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. |
||
| 411 | |||
| 412 | ### HTTP fejlécek |
||
| 413 | |||
| 414 | | Fejléc | Forrás | Példa | |
||
| 415 | |----------------|---------------------------------------|---------------------------------------------------------------------| |
||
| 416 | | Content-Type | fix | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | |
||
| 417 | | Content-Length | `len(_cache["bytes"])` | `1451511` | |
||
| 418 | | ETag | md5(content) | `3ab628c5d57e138082b71b167d68d5af` | |
||
| 419 | | Last-Modified | `last_success_ts` → unix → HTTP dátum | `Thu, 30 Apr 2026 20:06:55 GMT` | |
||
| 420 | |||
| 421 | ### Authentication |
||
| 422 | |||
| 423 | HTTP Basic, `SimpleDomainController` egy user-mappinggel. A user/jelszó környezeti változókból: |
||
| 424 | |||
| 425 | NAVDAV_USER=<username> # default: navdav |
||
| 426 | NAVDAV_PASSWORD=<password> # KÖTELEZŐ — nélküle a szerver nem indul |
||
| 427 | |||
| 428 | 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). |
||
| 429 | |||
| 430 | ## Bootstrap és üzemeltetés |
||
| 431 | |||
| 432 | 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. |
||
| 433 | |||
| 434 | ### Production deploy — egysoros, Gitea-ról |
||
| 435 | |||
| 436 | 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. |
||
| 437 | |||
| 438 | curl -fsSL "http://kpeter:<TOKEN>@192.168.2.210:3000/kpeter/navdav/raw/branch/master/install-from-gitea.sh" | \ |
||
| 439 | GITEA_TOKEN=<TOKEN> bash |
||
| 440 | |||
| 441 | A `install-from-gitea.sh` 8 lépésben telepít: |
||
| 442 | |||
| 443 | 1. Előfeltétel-ellenőrzés (docker, compose, daemon) |
||
| 444 | 2. Deploy mappa: `~/navdav` |
||
| 445 | 3. `docker-compose.yml` letöltése Gitea-ról (curl, nincs git-clone — kevesebb host-csomag) |
||
| 446 | 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 |
||
| 447 | 5. `.env` — random 24-karakteres NavDaV-jelszó + `GITEA_TOKEN` (build-időre, a NAV CLI klónozásához) |
||
| 448 | 6. `docker compose build --pull` + `up -d --force-recreate` |
||
| 449 | 7. Healthcheck-megvárás (max 60s) |
||
| 450 | 8. Bootstrap, ha üres a DB (idempotens — második futtatáson kihagyva) |
||
| 451 | |||
| 452 | 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. |
||
| 453 | |||
| 454 | ### Frissítés — ugyanaz a parancs |
||
| 455 | |||
| 456 | 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. |
||
| 457 | |||
| 458 | ### Docker-image szerkezete |
||
| 459 | |||
| 460 | | Réteg | Tartalom | |
||
| 461 | |---------------------------------------|-------------------------------------------------------------------------------------------------------------| |
||
| 462 | | 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) | |
||
| 463 | | Stage 2 — `python:3.12-slim-bookworm` | Python runtime + `xlsxwriter` + `wsgidav` + `cheroot` + `sqlite3`; a NavDaV kód a build-context-ből másolva | |
||
| 464 | | Volume | `/data` — SQLite DB, cached xlsx | |
||
| 465 | | Bind mount | `./nav.env → /opt/nav/.env` (RO) — NAV credentials a Rust binárisnak | |
||
| 466 | | Env-file | `./.env` — NavDaV credentials + GITEA_TOKEN (csak build-time) | |
||
| 467 | | Hálózat | port 8081, kötés `0.0.0.0:8081 → conténer 8081` (LAN-elérhetőség Nextcloud-szerver felé) | |
||
| 468 | | Healthcheck | 30s interval, TCP socket connect-test localhost:8081 | |
||
| 469 | | Restart policy | `unless-stopped` — daemon-restartolásnál is feláll automatikusan | |
||
| 470 | |||
| 471 | ### Kanonikus üzemeltetési parancsok |
||
| 472 | |||
| 473 | | Cél | Parancs | |
||
| 474 | |-----------------------------------------|---------------------------------------------------------------------------------------------| |
||
| 475 | | Status | `docker compose ps` | |
||
| 476 | | Logok | `docker compose logs -f --tail=30` | |
||
| 477 | | Manuális bootstrap (ha kell) | `docker compose exec navdav python bootstrap_import.py --from 2024-01-01 --to "$(date -I)"` | |
||
| 478 | | Manuális delta-refresh (CLI, throttled) | `docker compose exec navdav python nav_bridge.py` | |
||
| 479 | | Restart | `docker compose restart` | |
||
| 480 | | Stop (volume megmarad) | `docker compose down` | |
||
| 481 | | Stop + adat-törlés | `docker compose down -v` ⚠️ a teljes `navdav-data` volume is törlődik | |
||
| 482 | | Smoke test | `curl -s -u "navdav:$PASS" -I http://localhost:8081/szamlak.xlsx | head -5` | |
||
| 483 | |||
| 484 | ### Backup |
||
| 485 | |||
| 486 | A `navdav-data` Docker volume tartalmazza a teljes történeti NAV-adatot. Backup tipikusan crontab-ból: |
||
| 487 | |||
| 488 | # Minden éjjel 02:00-kor — SQLite online backup, file-konzisztens snapshot |
||
| 489 | 0 2 * * * docker compose -f $HOME/navdav/docker-compose.yml exec -T navdav \ |
||
| 490 | sqlite3 /data/navdav.db ".backup '/data/backups/navdav-$(date +\%Y\%m\%d).db'" |
||
| 491 | |||
| 492 | 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). |
||
| 493 | |||
| 494 | ### Natív telepítés (alternatíva, fejlesztés) |
||
| 495 | |||
| 496 | 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. |
||
| 497 | |||
| 498 | cd ~/navdav |
||
| 499 | sudo -E bash deploy.sh # admin-jog kell apt + systemd-unit miatt |
||
| 500 | |||
| 501 | Részletek: a script kommentjeiben. Production-ra a Docker-út ajánlott. |
||
| 502 | |||
| 503 | ## Nextcloud bekötés |
||
| 504 | |||
| 505 | A Nextcloud admin felületen **External Storage** (Settings → Administration → External storages): |
||
| 506 | |||
| 507 | | Mező | Érték | |
||
| 508 | |------------------|-------------------------------------------------------------------| |
||
| 509 | | Name | `NAV számlák` (vagy bármi) | |
||
| 510 | | External storage | `WebDAV` | |
||
| 511 | | URL | `http://192.168.2.211:8081/` (a NavDaV-VM, „web", LAN-os bekötés) | |
||
| 512 | | Username | `NAVDAV_USER` értéke | |
||
| 513 | | Password | `NAVDAV_PASSWORD` értéke | |
||
| 514 | | Available for | érintett user vagy csoport | |
||
| 515 | | Read-only | igen — a NavDaV nem fogad el írást | |
||
| 516 | |||
| 517 | ### Megnyitási módok |
||
| 518 | |||
| 519 | | Hogyan nyitja a user | Mit lát | |
||
| 520 | |---------------------------------|---------------------------------------------------------------------------------------------------------------------------------| |
||
| 521 | | 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) | |
||
| 522 | | 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 | |
||
| 523 | | Virtual files | Ugyanaz mint a desktop — ETag-vezérelt | |
||
| 524 | |||
| 525 | ## Fájlok és tech stack |
||
| 526 | |||
| 527 | | Fájl | Mit tartalmaz | Technológia | |
||
| 528 | |----------------------------------|----------------------------------------------------------------------------------------------------------|--------------------------------| |
||
| 529 | | `schema.sql` | SQLite séma — `invoices`, `invoice_lines`, `state`, indexek | SQL WAL mode, FK on | |
||
| 530 | | `nav_bridge.py` | NAV subprocess + UPSERT + state + throttle/lock + CLI | Python stdlib only | |
||
| 531 | | `render_xlsx.py` | SQLite → in-memory xlsx, két lap, autofilter, freeze | Python + `xlsxwriter` | |
||
| 532 | | `webdav_server.py` | WebDAV provider (DAVProvider, DAVCollection, DAVNonCollection) + render-cache + Basic auth | Python + `wsgidav` + `cheroot` | |
||
| 533 | | `bootstrap_import.py` | Vékony wrapper a `nav_bridge.refresh()` körül, default `2024-01-01 → ma` | Python | |
||
| 534 | | `requirements.txt` | Csak runtime deps: `xlsxwriter`, `wsgidav`, `cheroot` | — | |
||
| 535 | | `Dockerfile` | Multi-stage build: Rust-stage NAV CLI + Python-stage NavDaV runtime; env-paraméteres path-ok | Docker | |
||
| 536 | | `docker-compose.yml` | Production deploy konfig: remote Gitea-context build, healthcheck, volumes, port mapping, restart policy | Compose | |
||
| 537 | | `install-from-gitea.sh` | Egysoros telepítő (curl \| bash); idempotens; `nav.env`+`.env` generálás, build, healthcheck, bootstrap | Bash | |
||
| 538 | | `deploy.sh` | Natív (Docker nélküli) deploy alternatíva — Rust + Python venv + systemd | Bash | |
||
| 539 | | `navdav.db` (vagy Docker volume) | SQLite akkumulátor — **nincs Git-ben**. Docker módban a `navdav-data` volume-ban | — | |
||
| 540 | |||
| 541 | ### Külső függőség (nem ebben a repóban) |
||
| 542 | |||
| 543 | > **/home/petitan/nav (Rust binary)** |
||
| 544 | > |
||
| 545 | > 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. |
||
| 546 | > |
||
| 547 | > 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). |
||
| 548 | |||
| 549 | ## Nyitott pontok és iteratív állás |
||
| 550 | |||
| 551 | ### Iteratív implementációs terv |
||
| 552 | |||
| 553 | | \# | Lépés | Állapot | |
||
| 554 | |-----|------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| |
||
| 555 | | 1 | Bootstrap-import script (Rust JSON → SQLite) | kész | |
||
| 556 | | 2 | xlsx render (SQLite → xlsx, CLI-tesztelhető) | kész | |
||
| 557 | | 3 | NAV bridge (delta + state, cron-kompatibilis) | kész | |
||
| 558 | | 4 | Throttle + lock | kész | |
||
| 559 | | 5 | WebDAV provider, Basic auth, ETag/mtime | kész | |
||
| 560 | | 6 | Nextcloud External Storage bekötés (LAN-bekötés) | kész | |
||
| 561 | | 7 | Docker-image (multi-stage Rust+Python), docker-compose, healthcheck | kész | |
||
| 562 | | 8 | Egysoros telepítő Gitea-ról (`install-from-gitea.sh`) | kész | |
||
| 563 | | 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) | |
||
| 564 | |||
| 565 | ### Megfigyelt apróságok |
||
| 566 | |||
| 567 | > **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). |
||
| 568 | |||
| 569 | > **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. |
||
| 570 | |||
| 571 | > **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. |
||
| 572 | |||
| 573 | ### NAV projektben felvett és lezárt issue-k |
||
| 574 | |||
| 575 | 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. |
||
| 576 | |||
| 577 | | \# | Probléma | NavDaV-hatás | Megoldás | |
||
| 578 | |-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
||
| 579 | | [`#2`](../../nav/ISSUES.md) | `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` | |
||
| 580 | | [`#3`](../../nav/ISSUES.md) | `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 | |
||
| 581 | | [`#4`](../../nav/ISSUES.md) | `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) | |
||
| 582 | | [`#5`](../../nav/ISSUES.md) | `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 | |
||
| 583 | | [`#6`](../../nav/ISSUES.md) | `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 | |
||
| 584 | | [`#7`](../../nav/ISSUES.md) | `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) | |
||
| 585 | | [`#8`](../../nav/ISSUES.md) | `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 | |
||
| 586 | | [`#9`](../../nav/ISSUES.md) | 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 | |
||
| 587 | |||
| 588 | Az issue-k megoldása után `bootstrap_import.py --from 2024-01-01` backfilleli minden korábbi adatot: |
||
| 589 | |||
| 590 | | Mérőszám | Előtte | Utána | |
||
| 591 | |----------------------------------------------------|-----------------|----------------------------------------------------------------------------| |
||
| 592 | | Üres OUTBOUND `customer_name` adószámmal | 31 | **0** | |
||
| 593 | | `&`-tartalmú partner-nevek (28 hó, mindkét irány) | 0 (silent fail) | **51** | |
||
| 594 | | `customer_vat_status` kitöltöttség | nem létezett | **5798/5798** (DOMESTIC: 5786, OTHER: 5, PRIVATE_PERSON: 7) | |
||
| 595 | | SIMPLIFIED+AGGREGATE számlák Bruttó kitöltöttsége | 0 / 111 | **111 / 111** | |
||
| 596 | | 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) | |
||
| 597 | | Számlavezető bank (`supplier_bank_account_number`) | 0 / 5798 | **4861 / 5798** (1105 INBOUND + 3756 OUTBOUND) | |
||
| 598 | | Cím (`supplier_address`, `customer_address`) | 0 / 5798 | **5798 / 5798** supplier_address; **5791 / 5798** customer_address | |
||
| 599 | |||
| 600 | 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. |
||
| 601 | |||
| 602 | ------------------------------------------------------------------------ |
||
| 603 | |||
| 604 | NavDaV — NAV Online Számla → Nextcloud Excel híd · Petitan Kft. |