Claude Agent aac9971f2b fix(web): mesaj onest despre mediul RAR al instantei (Issue A / A1)
Textul din bannerul de import (0 medii) si din antetul formularului de
credentiale nu spunea concret ce mediu foloseste instanta curenta. Vechiul
"Trimiterea va folosi configuratia globala" era jargon, iar "Pentru a activa
Testare sau Productie" nu clarifica relatia instanta<->mediu.

- Adauga globalul Jinja `mediu_instanta()` = eticheta umana a ancorei globale
  AUTOPASS_RAR_ENV (Testare/Productie), fallback sigur pe Testare.
- `_upload.html`: bannerul de 0 medii numeste concret mediul global al instantei
  pe care cad trimiterile pana la activarea unui mediu.
- `_cont.html`: nota onesta sub antetul "Credentiale RAR" — instanta ruleaza pe
  mediul global X, ambele medii se pot configura aici (fiecare validat separat),
  iar la 0 medii active trimiterile cad pe mediul global al instantei.

Fara selector nou si fara schimbari in logica de scriere a credentialelor
(A1, aliniat PRD 5.20: instanta = ancora de fallback pentru env).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:24:33 +00:00

Gateway RAR AUTOPASS

Gateway web (Python / FastAPI) care preia prezentarile de service-auto si le declara la RAR AUTOPASS (Legea 142/2023, OM 210/2024). Inlocuieste integrarea Visual FoxPro (ROAAUTO).

Doua procese peste acelasi SQLite, care comunica prin tabela submissions:

  • API (app.main:app) — dashboard web, API v1, signup/login, panou admin, /healthz, /metrics.
  • Worker (app.worker) — login RAR, trimite prezentarile din coada, retry/backoff, heartbeat.

Trimiterea catre RAR e dezactivata implicit (AUTOPASS_WORKER_SEND_ENABLED=false) — sigur pentru probe.

Sursa de adevar pentru contractul RAR: docs/api-rar-contract.md. Progres + proces: docs/ROADMAP.md. Ghid pentru utilizatori (service-uri), in termeni simpli: docs/ghid-utilizator.md.

Pornire rapida

pip3 install -r requirements.txt            # Python 3.12+

uvicorn app.main:app --reload --port 8010   # API (dashboard /, Swagger /docs)
python3 -m app.worker                        # worker (doar daca vrei sa procesezi coada)

La prima pornire se creeaza schema SQLite si se face seed la nomenclatorul RAR — dashboard-ul si maparile merg imediat, offline. Pentru testarea UI-ului si a importului nu ai nevoie de worker.

Dev rapid fara login: porneste cu AUTOPASS_WEB_AUTH_REQUIRED=false (dashboard pe contul implicit id=1).

Cu start.sh (ambaleaza mediu + rol)

./start.sh test both --send     # API + worker, trimite la RAR test (loguri in .run/)
./start.sh test finalizate      # listeaza prezentarile inregistrate la RAR (verificare independenta)
./start.sh status               # stare procese + /healthz
./start.sh stop                 # opreste procesele pornite cu "both"

./start-test.sh / ./start-prod.sh   # fixeaza mediul (test/prod), forwardeaza rolul

Pagini web

URL Ce vezi
/ Dashboard: coada, prezentari blocate, stare worker, import fisier, mapari, nomenclator
/signup · /login Inregistrare cont (emite cheia API o data) · autentificare
/admin Panou admin: conturi pe stari, activare/blocare/arhivare (doar admini)
/integrare Exemple cod (Python/C#/Node/VFP), export Postman/OpenAPI, testeaza conexiunea
/docs Swagger UI — API v1 interactiv
/healthz · /metrics sanatate JSON · metrici text

Import fisier (xlsx / csv)

Pe dashboard: incarca fisierul → mapeaza coloanele (sugerate automat fuzzy; maparea se retine pe semnatura coloanelor, per cont) → preview (fiecare rand: ok / needs_mapping / needs_data / already_sent / ...) → confirma (retastezi numarul de randuri ok). Randurile intra in coada.

Coloane recunoscute (cu sinonime): VIN, Nr inmatriculare, Data prestatie, Odometru final, Odometru initial, Operatie, Observatii. Fiecare cont poate avea mai multe formate memorate.

API v1 (curl)

Dev: fara cheie → cont id=1. Productie (AUTOPASS_REQUIRE_API_KEY=true): header X-API-Key: rfak_....

curl -s http://localhost:8010/healthz | python3 -m json.tool   # sanatate
curl -s http://localhost:8010/v1/nomenclator                   # coduri RAR (cache local)
curl -s http://localhost:8010/v1/prezentari                    # coada

# Trimite o prezentare. `rar_credentials` e OPTIONAL: daca lipseste, worker-ul
# foloseste creds-urile RAR salvate pe cont (POST /v1/conturi/rar-creds). Trimite-le
# explicit doar cand vrei sa le suprascrii pe acea cerere.
curl -s -X POST http://localhost:8010/v1/prezentari \
  -H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \
  -d '{
    "prezentari": [{
      "vin": "WAUZZZ8K0AA000001", "nr_inmatriculare": "B123ABC",
      "data_prestatie": "2026-06-15", "odometru_final": "120000",
      "prestatii": [{"cod_op_service": "REVIZIE PERIODICA", "denumire": "REVIZIE PERIODICA"}]
    }]
  }'

# Dry-run: valideaza payload + mapare, FARA enqueue, FARA creds
curl -s -X POST http://localhost:8010/v1/prezentari/valideaza \
  -H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' -d '{ "prezentari": [ ... ] }'

# Import fisier
curl -s -X POST http://localhost:8010/v1/import -H 'X-API-Key: rfak_...' -F 'file=@import.xlsx'

Toate endpointurile sunt in /docs. Exemple gata facute + Postman/OpenAPI: hub-ul /integrare.

Conturi si chei API

Fiecare service = un cont (accounts) cu lifecycle (pending → active → blocked / archived / deleted). Worker-ul trimite doar pentru conturi active. Web-ul se autentifica prin sesiune (login email+parola), API-ul prin cheie API (X-API-Key). Cheia identifica contul, e separata de credentialele RAR.

# Self-onboarding: service-ul deschide /signup → primeste cheia o data.
# Primul user inregistrat in toata baza (indiferent de account_id) devine admin
# automat (is_admin, acces /admin) — nu e hardcodat in .env. Doar al doilea+ signup
# creeaza cont obisnuit, neadmin. Daca a trecut deja primul signup, acorzi admin manual:
python3 -m tools.account set-admin --account N

# Sau din CLI (admin, pe masina gateway-ului):
python3 -m tools.account create --name "Service Auto SRL" --cui RO12345678 --with-key
python3 -m tools.account list [--pending] | activate --account N | set-admin --account N
python3 -m tools.apikey create|list|rotate|revoke --account N   # cheie afisata O SINGURA DATA

Creds RAR per cont, pe medii — fiecare cont are doua sloturi separate de credentiale RAR, Testare si Productie (sisteme RAR distincte; un set de creds merge pe exact unul). Criptate Fernet at-rest. Din web se seteaza in Cont → Credentiale RAR (doua sectiuni, cu validare prin login si confirmare unica la activarea Productie). Din API, campul rar_target alege slotul:

# Seteaza creds pe mediul Testare (lipsa rar_target -> mediul implicit / ancora globala)
curl -s -X POST http://localhost:8010/v1/conturi/rar-creds \
  -H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \
  -d '{"email": "service@exemplu.ro", "password": "parola-rar", "rar_target": "test"}'

La trimitere, POST /v1/prezentari accepta rar_env (test/prod); lipsa lui -> mediul implicit al contului. Pe ce mediu a mers fiecare rand vezi in GET /v1/prezentari (camp rar_env) si in badge-ul din dashboard. Explicatie pentru operatori: docs/ghid-utilizator.md.

POST-urile si listarile per-cont (/v1/prezentari, /v1/audit/export, fragmentele web) sunt filtrate pe contul cheii API. GET /v1/nomenclator ramane public intentionat (coduri RAR publice, fara date personale).

Proba reala la RAR (mediu test)

  1. Pune creds de test in settings.xml (copiaza din settings.xml.example, bloc <test>; nu se comite). settings.xml tine un singur cont RAR doar pentru dev/test — creds-urile conturilor reale stau criptate in DB.
  2. Baga prezentari in coada (import sau API).
  3. ./start.sh test worker --send — worker-ul trimite si trece fiecare rand in sent (cu id_prezentare), needs_data sau error.
  4. Verifica: dashboard, curl /v1/prezentari, sau ./start.sh test finalizate (listeaza direct de la RAR).

sent + id_prezentare = RAR a acceptat. La raspuns pierdut, worker-ul reconciliaza anti-duplicat (cauta in finalizate, marcheaza sent fara re-trimitere). FINALIZATA e terminal la RAR.

Configurare (AUTOPASS_*)

Variabila Implicit Rol
DB_PATH ./data/autopass.db calea SQLite
RAR_ENV test ancora globala test / prod; mediile per-cont (Testare/Productie) au prioritate cand contul le are configurate
REQUIRE_API_KEY false true = cere cheie pe /v1/* (prod)
WEB_AUTH_REQUIRED true false = dashboard fara login, cont id=1 (dev)
CREDS_KEY (efemera) cheie Fernet creds RAR — trebuie PARTAJATA intre API si worker
SESSION_SECRET (efemer) secret cookie sesiune; persistent in prod
WORKER_SEND_ENABLED false true = trimite efectiv la RAR
SMTP_HOST (+ _PORT/_USER/_PASSWORD/_FROM) (none) notificare admin la signup (best-effort)

Genereaza chei: python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" (CREDS_KEY) si python3 -c "import secrets; print(secrets.token_hex(32))" (SESSION_SECRET).

Teste

python3 -m pytest -q                 # toata suita
python3 -m pytest tests/test_x.py -q # un fisier

Docker / deploy

cp .env.example .env                 # CRITIC: completeaza AUTOPASS_CREDS_KEY (partajata api+worker)
docker compose up --build            # api (:8010) + worker + autoheal, acelasi image + volum SQLite

Structura

app/
  main.py            # FastAPI: API v1 + dashboard + auth + admin
  api/v1/            # router.py (prezentari, valideaza, nomenclator, mapari, conturi),
                     #   import_router.py, integrare_router.py (ping, postman/openapi)
  web/               # routes.py (dashboard + import HTMX), auth_routes.py, admin_routes.py,
                     #   session.py, csrf.py, labels.py, templates/, static/
  worker/            # proces separat: login RAR, send, retry, heartbeat
  rar_client.py      # client HTTP RAR (login/JWT, postPrezentare, nomenclator)
  auth.py users.py accounts.py    # chei API, parole scrypt + admin, lifecycle conturi
  validation.py mapping.py errors.py crypto.py    # validare, mapare cod, erori 3-niveluri, Fernet
  schema.sql         # schema SQLite
tools/               # CLI admin: account, apikey, backup, rar_finalizate, import_dbf
docs/                # contract RAR + ROADMAP + prd/
tests/  legacy-vfp/  # suita pytest · arhiva ROAAUTO (referinta)
Description
No description provided
Readme 7.2 MiB
Languages
Python 83.5%
HTML 12.9%
xBase 2.8%
Shell 0.8%