Files
rar-autopass/docs/plans/plan.md
Claude Agent 36d1b916d5 feat(T4): payload builder finalizat + snapshot test
- app/payload.py rafinat: odometruFinal/odometruInitial string (initial gol -> null),
  evita capcana falsy `or ""` (pastreaza "0"), normalizare vin/nrInm/coduri,
  tipPrestatie niciodata trimis, obs/b64Image omise cand lipsesc
- tests/test_payload.py: 10 teste, inclusiv snapshot vs exemplul oficial din contract

Verify: pytest 39 passed (29 + 10).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:28:33 +00:00

23 KiB
Raw Blame History

Plan unic — Gateway RAR AUTOPASS (migrare ROAAUTO din VFP în Web API)

Planul executabil, sursă unică. Consolidează design-ul de produs + planul de implementare, aliniat la contractul verificat live. Citește-l împreună cu:

  • docs/api-rar-contract.mdcontractul RAR (sursa de adevăr), verificat live 2026-06-15.
  • docs/CONTEXT.md — continuitate între sesiuni.

Înlocuiește fostele plan-eng-review.md + plan-design-review.md (consolidate aici). Ultima actualizare: 2026-06-15.

Cum reiei (sesiune nouă)

  1. git clone git@gitea.romfast.ro:romfast/rar-autopass.git
  2. cp settings.xml.example settings.xml și completează credențialele de test RAR (blocul <test>). NU se comite.
  3. Citește docs/api-rar-contract.md (contractul) și acest plan.
  4. Pornește de la Roadmap de execuție (mai jos) — pasul blocant următor e T1.

1. Problema și rezultatul țintă

ROAAUTO (Visual FoxPro + Oracle, la fiecare service client) declară azi prestațiile la RAR AUTOPASS direct din clasa RarAutoPass (rar_autopass.prg), prin MSXML2.ServerXMLHTTP. Obligație legală (L.142/2023, OM 210/2024). Integrarea e testată doar pe endpoint-ul de test RAR, nepusă la clienți.

Problema reală e de ISV: nu vrei să redistribui un .exe VFP la fiecare corecție. Muți logica (mapare + login RAR + jurnal + retry) pe un gateway central depanabil o dată pentru toți; ROAAUTO rămâne client subțire. Un client real a cerut automatizarea — primul plătitor, nu ipoteză.

Rezultat țintă (treapta 1): o prezentare reală trimisă din ROAAUTO prin gateway apare FINALIZATA la RAR, vizibilă în dashboard, cu retry pe erori tranzitorii și fără a stoca parole.

2. Contract RAR — reguli care guvernează implementarea

Detalii complete: docs/api-rar-contract.md. Esențialul pentru cod:

  • Endpoint-uri (verificate live): login POST /public/login; nomenclator GET /nomenclator/getNomenclatorPrestatii; trimitere POST /prezentari/postPrezentare. Bază test https://apps.rarom.ro/test-rar-autopass, prod …/rar-autopass.
  • JWT TTL = 30h (108000s). Worker-ul se re-loghează la expirare; retry-ul NU e plafonat la 30h.
  • Payload postPrezentare: vin, nrInmatriculare, dataPrestatie, odometruFinal, odometruInitial, prestatii:[{codPrestatie,idPrezentare:null}], sistemReparat, status:"FINALIZATA", obs?, b64Image?. tipPrestatie NU se trimite (generat de server, GENERIC).
  • Reguli de validare (RAR le aplică; le replicăm în gateway înainte de enqueue):
    • vin: 17 caractere majuscule, fără spații/special, fără O, I, Q → regex ^[A-HJ-NPR-Z0-9]{17}$.
    • nrInmatriculare: max 10, litere+cifre, majuscule → ^[A-Z0-9]{1,10}$.
    • dataPrestatie: ∈ [2024-12-01, azi], TZ Europe/Bucharest.
    • b64Image: opțional; dacă prezent, base64 valid.
    • odometruInitial: null normal; obligatoriu dacă prestatiiR-ODO sau I-ODO; odometruInitial <= odometruFinal.
    • odometruFinal: trimis ca string (per exemplul oficial).
    • sistemReparat: trimis mereu; default "null" în v1.
    • Normalizează vin/nrInmatriculare cu .strip().upper() înainte de regex (DBF-urile pot avea spații).
  • Anulare/corecție prin API: NU există. API e strict INSERT FINALIZATA; FINALIZATA nu se anulează (doar SALVATA, pe care API nu le produce). Corecția = email suport.autopass@rarom.ro.
  • Monitorizare: pe TEST lista finalizate nu întoarce prestatii (pe prod da, cu filtrare + export Excel).
  • Idempotency: RAR n-are câmp nr. comandă și acceptă duplicate → dedup-ul e responsabilitatea noastră.

3. Arhitectură

ROAAUTO (VFP, la client)                         GATEWAY FastAPI (central, 1 container)
  citește comanda + creds RAR din Oracle    POST /v1/prezentari {comanda + RAR creds + idempotency implicit}
  ──HTTPS──────────────────────────────────────▶ API
                                                   ├─ valid Pydantic (vin 17/fără O,I,Q; nrInm; dată ∈ [2024-12-01,azi] TZ Bucharest)
                                                   ├─ rezolvă op→codPrestatie (operations_mapping)
                                                   ├─ odometruInitial cerut DOAR dacă coduri ∋ R-ODO/I-ODO
                                                   ├─ NU trimite tipPrestatie; sistemReparat="null"
                                                   ├─ calc idempotency_key = hash(conținut canonic)
                                                   ├─ INSERT submission (PII criptat tranzitoriu, queued)
  ◀── {submissionId, status: queued|needs_mapping|needs_data} ──┘  (UNIQUE → dedup, întoarce id existent)

                       WORKER (proces separat, restart:always, poll SQLite WAL)
                         claim atomic: BEGIN IMMEDIATE; UPDATE…SET sending WHERE id=? AND status='queued'
                         login RAR → JWT (30h, re-login la expirare) → reconciliere → postPrezentare → retry/backoff
                         succes: scrie idPrezentare(data.id); PURJEAZĂ creds; PII criptat rămâne max 90 zile

  Browser ─▶ Dashboard (Jinja2+HTMX): monitorizare live RAR + coadă + editor mapări + nomenclator + audit CSV
                                       + BANNER alertă submission-uri blocate (singura plasă pe pene > 30h)
  (Re-push din ROAAUTO: SCOS din v1. Durabilitatea = SQLite pe volum persistent + backup + restart.)

Mașina de stări submission

queued → sending → { sent | needs_mapping | needs_data | error }
  needs_mapping : operație fără codPrestatie mapat → ținut gateway-side, NU trimis incomplet
  needs_data    : prestatii ∋ R-ODO/I-ODO fără odometruInitial, SAU validare RAR eșuată (VIN/dată/nrInm) → ținut
  error         : login creds invalide, RAR 4xx nerecuperabil, sau pană > 30h → ALERTĂ banner (fără re-push automat)
  sent          : are idPrezentare (data.id) RAR; creds purjate; PII criptat max 90 zile; terminal
  (anulare/corecție: NU există tranziție — FINALIZATA e terminal la RAR; corecția = suport RAR)

4. Componente (un repo, docker compose up)

  1. API app/api/v1 (FastAPI):
    • POST /v1/prezentari (una/mai multe) → Pydantic (validările din §2), mapare, enqueue, submissionId.
    • GET /v1/prezentari?status=&data= și /{id} — monitorizare programatică (stare coadă + idPrezentare).
    • GET /v1/nomenclator, POST /v1/nomenclator/refresh.
    • GET/PUT /v1/mapari — CRUD mapare per cont, cu sugestie fuzzy pe denumire (cherry-pick).
    • GET /v1/audit/export?from=&to=CSV cu ce s-a trimis (cherry-pick, leagă reținerea 90 zile).
    • Auth gateway: API key per cont ROA (separată de creds RAR), cu emitere/rotire/revocare.
    • Redactare credențiale (CORE): middleware care garantează că body-ul pe /v1/prezentari NU se loghează niciodată și password se scrubează din excepții/APM.
    • (SCOS din v1: anulare/corecție prin API. Amânat: POST /v1/import xlsx/csv — treapta 2.)
  2. Client RAR app/rar_client.py — portare din rar_autopass.prg + rar-forms.prg: login+JWT, getNomenclatorPrestatii, postPrezentare, getAllPrezentari/getAllPrezentariFinalizate. httpx + retry/backoff. URL-uri din VFP testat (verificate live). (NU portăm anulare/corecție.)
  3. Worker app/worker — proces propriu supravegheat (NU task asyncio în uvicorn — altfel worker mort lasă containerul „sănătos"), sub Docker restart: always. Buclă claim atomic → login → reconciliere → send → retry.
    • JWT 30h, re-login la expirare (retry nu e plafonat la 30h).
    • Reconciliere înainte de re-send (anti-duplicat pe răspuns pierdut — P1): dacă răspunsul RAR se pierde după ce RAR a inserat, rândul rămâne sending; un re-send orb ar crea duplicat (RAR acceptă duplicate). Înainte de re-send, worker interoghează lista RAR pe VIN + dataPrestatie + odometruFinal; dacă găsește → marchează sent cu idPrezentare găsit, NU re-trimite. UNIQUE pe submissions NU acoperă acest caz.
    • Lease/timeout pe sending orfane (worker mort mid-POST) → recuperat de reconciliere.
    • b64Image (opțional) mare → BLOB/path pe disc, nu RAM.
  4. Dashboard app/web — Jinja2 + HTMX (server-rendered, zero build): monitorizare live RAR + coadă + editor mapări (cu fuzzy) + browser nomenclator + banner alertă submission-uri blocate. Stări explicite: empty (coadă goală cu CTA), error (needs_mapping/needs_data cu motiv), RAR indisponibil → ultima stare a cozii.
  5. SQLite (WAL) — un fișier .db pe volum persistent + backup:
    • accounts, api_keys.
    • operations_mapping (cod_op_service → codPrestatie, auto_send) ← mapare_prestatii.DBF.
    • nomenclator_rar (cache {codPrestatie, numePrestatie}) ← prestatii_rar.DBF.
    • submissions: idempotency_key UNIQUE, status, statusCode RAR, eroare, idPrezentare, retry, timestamps. PII (vin, odometru, dataPrestatie, prestatii, b64Image) criptat + purge_after (sent+90z).
    • Niciun câmp pentru parole RAR.

5. Securitate & raza de explozie

Gateway-ul vede parolele RAR ale tuturor clienților în memorie/tranzit. Zero-storage reduce riscul la rest, dar o greșeală de logging scurge parole live, iar AGPL = codul e public. Controale hard:

  • Middleware de redactare — body-uri cu parole niciodată în loguri/APM/excepții.
  • HTTPS obligatoriu; recomandare TLS pinning din ROAAUTO către gateway.
  • Parola folosită doar pentru login în worker. Cu JWT 30h, după primul login reușit tokenul acoperă retry-urile; creds se șterg. Dacă login-ul eșuează (RAR jos), creds rămân în itemul (criptat) până la primul login reușit.

Failure modes registry

Codepath Failure Rescued User vede Logged
postPrezentare cu R-ODO/I-ODO fără odometru init record fraud-sensibil incomplet Y (needs_data determinist) flag dashboard DA
dublu-send (răspuns RAR pierdut, rând sending) duplicat la RAR (UNIQUE NU acoperă) Y (reconciliere VIN+dată+odometru) nimic (transparent) DA
dublu-enqueue (același conținut, 2 cereri API) submission dublu Y (idempotency UNIQUE) „deja înregistrat" DA
VIN cu O/I/Q sau ≠17 / dată în afara intervalului RAR respinge 4xx Y (validare Pydantic înainte de enqueue) flag „date invalide" DA
login 401 creds greșite nu se poate trimite Y (NU retry; error+motiv) „credențiale RAR invalide" DA
pană RAR > 30h (fără re-push) declarație legală întârziată Parțial (alertă, NU auto-recovery) banner + webhook DA
worker mort (proces, nu container) coada se oprește tăcut Y (proces propriu + /healthz → restart) banner DA

Regulă: fără except Exception generic. Fiecare rescue: retry / degradare cu mesaj / re-raise cu context.

Risc acceptat (2026-06-15): scoaterea re-push-ului lasă penele RAR > 30h fără recuperare automată. Acoperit de alerta de submission blocat. Re-introducem re-push (timer VFP OnAutoProcessTimer) dacă apare în practică o pană > 30h.

6. Client ROAAUTO (VFP) — refactor minim

  • settings.xml păstrează doar URL gateway + API key (rotește parola de test expusă acum în SVN!).
  • Creds RAR ale clientului se citesc din Oracle și se trimit în payload la gateway peste HTTPS.
  • export_comenzi.prg rămâne, dar construiește JSON și face POST /v1/prezentari (nu XML + apel RAR direct).
  • Dispar din VFP: Login, UpdateNomenclator, GetCodRarPentruOperatie, maparea, rar_log → în web.
  • ROAAUTO = pur expeditor în v1. Re-push scos; timer-ul OnAutoProcessTimer rămâne disponibil dacă revine.
  • Fără atașare poză (b64Image opțional) — un câmp mai puțin de construit în VFP.

7. Migrare date

tools/import_dbf.py (cu dbfread) — dry-run + raport întâi: rânduri valide, mapări orfane, coduri necunoscute în nomenclator. Confirmi, apoi scrie în SQLite. Surse: mapare_prestatii.DBF, prestatii_rar.DBF. (rar_log.DBF NU se migrează — jurnalul nou e submissions + live din RAR.)

8. Observabilitate (cherry-picks)

  • /healthz — worker viu + ultimul login RAR reușit + adâncime coadă; pică → semnal de restart.
  • /metrics — submissions pe status, latență send, retry count, backlog needs_mapping/needs_data.
  • Alertă submission-uri blocate — banner dashboard + webhook/email peste prag (plasă de siguranță legală, acum critică).

9. Deploy

  • Start: LXC Proxmox + Cloudflare Tunnel (0 €, teste). Producție: VPS mic always-on (~5 €/lună).
  • Un container: uvicorn (API) + worker (proces 2), restart: always, volum SQLite persistent + backup. Mutare = copiezi container + .db.
  • Open-source pe github.com/romfast, AGPL-3.0. ⚠️ Decide CLA / copyright assignment din ziua 1 dacă vrei opțiunea de dual-license (open core + hosted comercial). Relicențierea fără CLA cere acordul tuturor contributorilor.
  • romfast.ro/hosting.com = doar landing (ASGI + worker daemon nu merg pe shared hosting).

10. Roadmap de execuție (pașii rămași)

Nimic din cod nu e scris încă (app/, tools/ nu există). Ordine recomandată:

  • T1 (P1, BLOCANT) — verificare contract live. 2026-06-15. postPrezentare real pe test (record data.id=68514). Capturate: format eroare data:[{field,message}] + 3 mesaje exacte (VIN O/I/Q, dată veche, dată viitoare), forma răspuns success, idPrezentare==id, idAgent server-side, sistemReparat:"null" acceptat, b64Image/odometruInitial omise OK. Descoperire: WAF cere User-Agent (altfel 403). Toate detaliile în docs/api-rar-contract.md.
  • T5 (P1) — tools/import_dbf.py dry-run + raport pe mapare_prestatii.DBF / prestatii_rar.DBF, apoi import în SQLite. Verify: raport rânduri valide/orfane/coduri necunoscute; import idempotent. (Stub creat — neimplementat.)
  • Schelet repo 2026-06-15. app/api/v1, app/rar_client.py (cu User-Agent), app/worker, app/web, SQLite (WAL), Dockerfile + docker compose, /healthz verde. Verificat: login prin client OK, nomenclator 18 coduri, worker heartbeat → worker_alive=True, enqueue + dedup idempotency funcționale.
  • T3 (P1) — validare completă 2026-06-15. app/validation.py (VIN ^[A-HJ-NPR-Z0-9]{17}$, nrInm, dată ∈ [2024-12-01,azi] TZ Bucharest, R-ODO/I-ODO→odometruInitial, odometruInitial<=odometruFinal) + normalize strip/upper în modelele Pydantic. Eșecurile de conținut → needs_data (ținute, nu 422) per masina de stări; JSON malformat → 422. Verify: 29 teste pass (tests/test_validation.py per regulă + tests/test_api.py rutare/idempotenta).
  • T4 (P1) — payload builder 2026-06-15. app/payload.py: status:"FINALIZATA", sistemReparat:"null", fără tipPrestatie, odometruFinal/odometruInitial string (initial gol → null), prestatii:[{codPrestatie,idPrezentare:null}], obs/b64Image omise când lipsesc. Verify: 10 teste (tests/test_payload.py), inclusiv snapshot vs exemplul oficial din contract.
  • T2 (P1) — app/worker reconciliere VIN+dată+odometru înainte de re-send pe sending + lease/timeout orfane. Verify: test integration — răspuns pierdut simulat → fără duplicat la RAR.
  • T6 (P2) — worker proces/container propriu supravegheat; /healthz pică → restart. Verify: worker omorât → restart automat.
  • T7 (P2) — deploy: SQLite pe volum persistent numit + backup (singura copie durabilă, re-push scos). Verify: recreare container → coada supraviețuiește.
  • Dashboard (Jinja2+HTMX) cu stările empty/error/RAR-indisponibil + banner alertă. Apoi /design-review pe UI-ul live.

De decis ulterior (urmărit, nu blocant)

  • [P2] Defer criptare-at-rest + purjare 90z până după primul postPrezentare real reușit? (gold-plating vs. privacy-argument-de-adopție).
  • [P2] Gate pe auto_send pentru operații nou-mapate / valori neobișnuite — un record FINALIZATA eronat e permanent.

11. Verificare (end-to-end)

  1. Spike „The Assignment" (JWT 30h, b64Image opțional, tipPrestatie server-gen, R-ODO/I-ODO) — docs/api-rar-contract.md.
  2. T1: postPrezentare real pe test → 200 + data.id.
  3. import_dbf.py --dry-run raport corect; apoi import confirmat.
  4. docker compose up; /healthz verde.
  5. POST /v1/prezentari cu o comandă reală (test) → submissionId, worker trimite, apare FINALIZATA la RAR + dashboard.
  6. Re-trimite aceeași comandă identică → același submissionId (idempotency), NU dublă la RAR.
  7. Operație nemapată → needs_mapping; prestatii ∋ R-ODO/I-ODO fără odometruInitialneeds_data; VIN cu O/I/Q sau dată < 2024-12-01 → respins la validare, NU ajunge la RAR.
  8. Oprește RAR / forțează 5xx → submission error, worker reia în fereastra de re-login; pană > 30h → alertă banner.
  9. Simulează răspuns pierdut → reconcilierea previne duplicatul.
  10. SQLite n-are câmp parolă; după sent, PII criptat + purge_after; logurile n-au parole.
  11. Teste: unit (mapare, idempotency hash, validare VIN/dată/nrInm, R-ODO→odometruInitial), integration (claim atomic, retry/backoff, reconciliere), E2E pe endpoint test RAR.

12. NOT in scope (amânat, cu motiv)

  • Re-push din ROAAUTO — scos din v1; worker + alertă acoperă. Re-introducem dacă apare pană RAR > 30h.
  • Anulare/corecție prin APIFINALIZATA nu se anulează/corectează prin API; corecția = suport RAR.
  • Atașare poză odometru în ROAAUTOb64Image opțional, nu în treapta 1.
  • POST /v1/import xlsx/csv + UX mapare coloane — treapta 2 (piață non-ROA). Motor identic, fără rescriere.
  • Modelul de conturi RAR (addClient/roluri) — rămâne la RAR.
  • Outbox în Oracle (Approach C) — pentru clienți non-ROA / viitor, cere acces gateway→Oracle.
  • Agregare/produse din datele service-urilor — niciodată default; doar opt-in + anonimizare + lawyered.
  • Redis/arq/Postgres — SQLite WAL + un worker acoperă volumul (60-100 prezentări/lună/client).

13. Open questions rămase

  1. Sursa pozei odometrului — închis (b64Image opțional).
  2. tipPrestatie / JWT TTL — închis (server-generated; 30h). Rămâne: ce valori reale acceptă sistemReparat în afară de "null".
  3. Un singur user RAR per agent economic sau mai mulți (idUser/idAgent — afectează filtrarea monitorizării).
  4. Monetizare/direcție SaaS — de reluat după ce prima prezentare reală merge la primul client.

14. Decizii blocate (nu le re-deschide fără motiv)

  • Idempotency = hash de conținut pe server, UNIQUE (RAR n-are câmp nr. comandă, acceptă duplicate) + reconciliere înainte de re-send pentru răspuns pierdut.
  • Reținere temporară 90 zile a payload-ului criptat, apoi purjare (defensibilitate vs privacy).
  • Odometru: strict + needs_data, declanșat determinist de R-ODO/I-ODO.
  • Re-push scos din v1; anulare/corecție API scoase (FINALIZATA).
  • URL-urile RAR: sursa de adevăr = VFP testat / contract verificat, NU spec-ul vechi (typo-uri).
  • Cherry-picks v1: alertă submission-uri blocate (critică), /healthz+/metrics, sugestie fuzzy mapare, export audit CSV.

ANEXĂ — Rațiunea de produs & direcția SaaS

Context de produs (din /office-hours), păstrat pentru deciziile de prioritizare. Nu blochează execuția treptei 1.

Demand evidence (validat: client real + lege)

Un client real a cerut automatizarea introducerii prezentărilor în AUTOPASS — de aici a pornit proiectul. Status quo înlocuit: interfața web oficială AUTOPASS, unde service-urile introduc manual, prezentare cu prezentare. Obligație legală reală (L.142/2023): la fiecare prestație VIN + odometru; reparare/înlocuire odometru; operațiuni principale la direcție, frânare, structura caroseriei/șasiului și alte sisteme de siguranță. Amenzi: info eronate 1.0002.000 lei; manipulare odometru până la 5.000 lei / penal. Implică toți clienții ROA + mii de service-uri non-ROA cu aceeași obligație → piață dincolo de ROA pentru un canal de import (xlsx/csv) ulterior.

Teza de produs

Cel mai ușor mod de a băga operațiile de service în AUTOPASS — din ROAAUTO (API), din alte aplicații, sau din fișiere — în loc de tastarea manuală. Câștigi prin efort minim cerut service-ului, nu prin features. Lecția GTM (demoanaf.ro): viral prin variantă reimaginată, simplă a unui serviciu oficial greoi.

Trepte (același motor — mapare + coadă + trimitere + monitorizare — fără rescriere)

  • Treapta 1 (acum): core-ul pentru clientul care a cerut + clienții ROA, prin ROAAUTO.
  • Treapta 2 (non-ROA, web upload): import xlsx/csv cu mapare reținută (map once, reuse forever) + dashboard. Login web, fără instalare. Freemium pe volum (gratis ~30-40 prezentări/lună; plată peste prag). Metrica de preț = prezentări/lună = unitatea obligatorie legal.
  • Treapta 3: integrări mai adânci + sugestii AI de mapare (eventual conector MCP).

Moat: (1) mapările reținute per service cresc costul de plecare; (2) lățimea integrării; (3) fiabilitatea de conformitate (retry, monitorizare din RAR); (4) privacy (nu reținem datele lor) — argument de adopție.

Adopție & praguri (cifre reale)

Clienți reali cunoscuți: 60-80 și 80-100 prezentări/lună → ~2-4 min/prezentare manual × volum = 3-6 ore/lună de tastare = durerea care convertește. Prima folosire trivială (upload → mapare reținută → trimite, sub 5 min). Gratis la volumul lor = fără decizie de achiziție. Service-urile mici = bază virală (durere mică); volumele mari = cei mai dispuși să plătească.

Import xlsx/csv — UX (treapta 2)

Două straturi de mapare, ambele reținute per cont: (1) mapare coloane (schema fișier → câmpuri canonice), (2) mapare operații (etichete service → codPrestatie, cu sugestie fuzzy). Flux: upload → recunoaște coloanele → propune maparea → preview (rânduri nemapate flag-uite) → „Trimite la RAR" → monitorizare. Spectru de integrare (același backend): API → drop fișier (folder/SFTP/email-to-import) → upload manual în browser.

Opțiuni de deploy

Opțiune Cost Când
LXC Proxmox + Cloudflare Tunnel 0 € Start + teste (risc: net/curent birou)
VPS mic always-on (ex. Hetzner) ~5 €/lună Clienți reali / producție (recomandat)
romfast.ro / shared hosting inclus ⚠️ Doar landing (ASGI + worker daemon nepotrivite pe shared)

What already exists (reuse din VFP)

Sub-problemă Reuse
Contract RAR (login/JWT, nomenclator, postPrezentare) rar_autopass.prg, rar-forms.prg:655,720rar_client.py
Mapare op→codPrestatie + auto_send GetCodRarPentruOperatieoperations_mapping
Export (oglindă treapta 2) btnExportExcel.Click (rar_advanced.prg)
Migrare DBF import_dbf.py citește direct cele .DBF
Timer (dacă revine re-push) OnAutoProcessTimer/nTimerHandle (rar-forms.prg) — nefolosit în v1