# 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.md` — **contractul 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 ``). 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ă `prestatii` ∋ `R-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ă: - [x] **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.) - [x] **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. - [x] **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** cu `status:"FINALIZATA"`, `sistemReparat:"null"`, fără `tipPrestatie`, `odometruFinal` string. Verify: snapshot payload == 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ă `odometruInitial` → `needs_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 API** — `FINALIZATA` nu se anulează/corectează prin API; corecția = suport RAR. - **Atașare poză odometru în ROAAUTO** — `b64Image` 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.000–2.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,720` → `rar_client.py` | | Mapare op→codPrestatie + `auto_send` | `GetCodRarPentruOperatie` → `operations_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 |