# PRD 5.12 — Editare unificata in modal + cont cu companie/email/CUI obligatorii + rafinari import (calendar data, mapare cu antet+prima inregistrare, un singur Salveaza, preview compact) + responsive tableta/mobil **Stare**: inchis (verify-pass 2026-06-26; 8 stories TDD prin agent team, VERIFY context curat PASS + 1 FAIL remediat, /code-review high 3 buguri reparate; regresie 987 passed/1 skipped/0 failed; asteapta confirmare commit — poarta umana) > Proces complet: `docs/ROADMAP.md` §5. Contractul RAR (sursa de adevar de contract): > `docs/api-rar-contract.md`. Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` > (actualizata de lead). Acest PRD nu repeta strategia/contractul — le linkeaza. > > Continua 5.11 ([prd-5.11](prd-5.11-ux-import-compact-preview-navigatie.md)). **Backendul de > trimitere (worker, masina de stari de trimitere, idempotenta, contract RAR) ramane NEATINS.** > Atingeri de schema permise (ambele coloane noi, migrare defensiva `_migrate`, ca la 3.3b/3.5/3.6): > `accounts.email` (US-001) si `import_rows.reviewed` (US-007, marcaj „verificat" per rand de preview). > Vezi Non-Goals. ## 1. Obiectiv Continuam dogfooding-ul de first-run inceput in 5.11. Sase frictiuni confirmate E2E in browser (Playwright pe `exemple/prezentari_test.csv`, 2026-06-26) — toate UI/UX, plus o regula de date pe conturi: 1. **Editarea unui rand din preview e rupta vizual si arunca o eroare JS.** Modul de editare inline (`tr.preview-edit` cu `display:block` intr-un tabel `table-layout:fixed`) colapseaza coloanele — antetul si formularul se randeaza pe verticala, caracter cu caracter (reprodus identic cu `image copy.png`). La click pe **Anuleaza** se arunca in consola `TypeError: Cannot read properties of null (reading 'htmx-internal-data')` (reprodus live). Decizie utilizator: **editarea trebuie sa fie un MODAL, ca la Trimiteri, refolosind ACELASI formular** (fara cod duplicat). 2. **Data prestatiei se scrie doar manual** (input text cu hint `YYYY-MM-DD`). Trebuie sa se poata alege si din **calendar** (`` nativ, decizie utilizator — zero dependinte JS). 3. **Conturile nu au reguli minime de identitate.** Confirmat in baza: toate conturile au `cui=NULL`, iar conturile create din CLI/teste nu au niciun utilizator → fara email. Decizie utilizator: un cont inregistrat trebuie sa aiba **obligatoriu companie, email si CUI**. 4. **Maparea coloanelor nu arata datele.** Pasul 2 listeaza nume de coloana + 2 exemple stivuite, dar nu se vede clar **capul de tabel (numele coloanelor) + valorile primei inregistrari**, ca operatorul sa stie ce mapeaza. 5. **Panoul „Operatii de mapat la cod RAR" cere un Salveaza per rand.** La un fisier cu N operatii nemapate sunt N butoane „Salveaza" si N submit-uri. Trebuie **un singur buton Salveaza** care salveaza toate maparile odata. 6. **Tabelul de preview (pasul 3) nu e compact si are o coloana neclara.** Randurile sunt foarte inalte (VIN-ul se sparge pe verticala), iar coloana **„Verificat?"** nu are sens evident in acest pas (operatorul nu intelege bifa). Trebuie lista mai compacta si coloana clarificata/eliminata. 7. **Pe tableta si mobil interfata arata prost si articolele din header se suprapun.** Header-ul are grila desktop `1fr auto 1fr` (`min-height:92px`, logo 60px) si un singur prag mobil `@media (max-width:767px)`, dar **nimic pentru tableta (768–1024px)** — acolo logo + titlu + badge mediu + comutator tema + versiune + hamburger se inghesuie si se suprapun. Tot fluxul (header, import, preview, modal, Trimiteri, Mapari, Cont) trebuie **compact, functional si ergonomic** pe tableta si telefon, cu tinte touch si fara suprapuneri. Toate sunt **UI/UX**, cu o singura exceptie de date controlata: identitatea contului (companie/email/CUI obligatorii, US-001/002). ## 2. Non-Goals (anti scope-creep) - **Nu** atingem worker-ul, reconcilierea, idempotenta, `build_key`, masina de stari de trimitere sau contractul RAR. - **Nu** schimbam canalul API (`POST /v1/prezentari` / `/valideaza`) si nici logica de mapare (`mapping.py` `resolve_prestatii`). Maparea operatie→cod ramane neschimbata; doar UI-ul de mapare din pasul de import se reorganizeaza (US-005) si reuseaza `save_mapping`/`reresolve_account` existente. - **Nu** stergem coloanele DB `auto_send` (deja neutralizate in 5.11) si nu reintroducem conceptul. - **Nu** schimbam stocarea editarii de preview: ramane `import_rows.override_json` (Approach B din 3.6), ruta `POST /_import/{id}/rand/{i}/editeaza` ramane sursa de adevar; doar **suprafata** de editare trece din rand-inline in modal. - **Nu** facem editare in bloc / multi-rand si nici editare a operatiei/codului RAR din modalul de rand (codul se mapeaza din panoul „Operatii de mapat", ca azi). - **Nu** schimbam fluxul de login/parola; un cont poate avea in continuare mai multe loginuri (`users`), dar primeste un email canonic de contact pe `accounts` (US-001). - **Nu** rescriem validarea de continut (`validation.py`); `` produce tot `YYYY-MM-DD`, acceptat azi. ## 3. Stories atomice > Fiecare story: cea mai mica unitate care lasa sistemul functional. Backend + UI pentru acelasi > comportament = 2 stories. Toate rutele web noi sub `require_login`, scoped pe contul din sesiune > (404 cross-account), CSRF pe toate POST-urile. > > **Cerinta transversala (toate story-urile cu UI): responsive obligatoriu.** Fiecare suprafata noua/atinsa > (US-002..007) se verifica E2E pe **3 viewport-uri: desktop (≥1280px), tableta (768–1024px) si mobil > (≤767px / ~390px)** — fara overflow orizontal (`scrollWidth <= clientWidth`), fara suprapuneri, tinte > touch ≥44px, modal full-screen pe mobil. US-008 acopera header-ul + cadrul global; fiecare story isi > verifica propria suprafata pe cele 3 viewport-uri. ### US-001: Backend — companie/email/CUI obligatorii pe cont (`accounts.email` + validari) **Ca** administrator al gateway-ului **vreau** ca orice cont sa aiba companie, email si CUI **pentru ca** azi conturile pot exista fara email (CLI/teste) si fara CUI, deci nu pot fi identificate fiscal/contactate. - **Depinde de**: — - **Fisiere**: `app/schema.sql` (coloana `accounts.email` + `_migrate` defensiv), `app/accounts.py` (`create_account` accepta+valideaza `email`; helper `account_is_complete`), `app/web/auth_routes.py` (signup: CUI devine obligatoriu; scrie `accounts.email`), `tools/account.py` (CLI create cere `--email` + `--cui`), `tests/test_accounts.py`, `tests/test_signup.py` (~6 fisiere) - **Test intai (RED)**: `tests/test_accounts.py` — `test_create_account_fara_email_ridica`, `test_create_account_fara_cui_ridica`, `test_email_normalizat_lowercase_trim`, `test_migrare_adauga_coloana_email_idempotent`, `test_account_is_complete_false_pe_legacy_incomplet`; `tests/test_signup.py` — `test_signup_fara_cui_422`, `test_signup_scrie_email_pe_account`, `test_signup_cui_existent_mesaj_prietenos` (NU mesajul tehnic cu `activate --account`). - **Acceptance criteria**: - [x] Migrare: `accounts.email TEXT` (nullable la nivel de schema pentru conturile legacy), `_migrate` defensiv idempotent (ca `users.is_admin` la 3.3b). Contul de sistem id=1 ramane fara email (exceptat). - [x] `create_account(conn, name, cui, email, active)` — `name`/`cui`/`email` goale → `ValueError` cu cauza+fix (catalog `errors.py` daca exista cod potrivit); `email` normalizat (trim+lower); `cui` normalizat (trim+upper, ca azi). CUI duplicat → mesajul existent. - [x] Signup web: `cui` devine **obligatoriu** (azi optional); la succes scrie `accounts.email = email`-ul utilizatorului. Lipsa CUI → re-randare formular cu eroare (422), pastrand campurile. - [x] **CUI duplicat la signup = mesaj prietenos, NU cel tehnic** (decizie user 2026-06-26, optiunea 1): „Aceasta firma (CUI …) e deja inregistrata. Cere accesul de la administratorul contului." — fara referinta la CLI `activate --account`. **Model: 1 firma = 1 cont = 1 login**; fluxul de invitatie/alaturare a unui al doilea email pe aceeasi firma e deferit la TODOS (optiunea 2). - [x] **Canal de contact concret in mesaj** (T3 gate /autoplan, aprobat 2026-06-26): mesajul include un email/canal de suport configurabil din settings (ex. `support_email`); daca setarea lipseste, fallback la formularea de mai sus. Operatorul primeste un pas urmator real, nu doar „cere accesul". Nu mai lasam mesajul tehnic ridicat de `create_account` sa ajunga verbatim in signup — detectam CUI duplicat in handler-ul de signup si compunem mesajul prietenos acolo (NU `error=str(exc)`). - [x] CLI `tools/account.py create` cere `--email` + `--cui` (refuza fara ele); `--with-key` neschimbat. - [x] `account_is_complete(row)` (companie + email + CUI ne-goale) — helper pur, fara efecte. - [x] **NU** atinge `users`, `submissions`, worker-ul sau idempotenta. - **Verificare E2E**: TestClient — signup fara CUI → 422; signup complet → `accounts.email` populat; `create_account` fara email/cui → ValueError. ### US-002: UI — gate de activare + pagina Cont editeaza companie/email/CUI + banner legacy **Ca** operator/administrator **vreau** sa vad si sa completez companie/email/CUI **pentru ca** conturile incomplete (legacy) trebuie aduse la regula fara re-inregistrare. - **Depinde de**: US-001 - **Fisiere**: `app/web/templates/_cont.html` (sectiune noua „Date firma"), `app/web/routes.py` (ruta `POST /cont/date-firma` scoped sesiune + CSRF; context `account_meta`+`cont_incomplet`), `app/web/templates/admin.html` + `app/web/routes.py` (gate activare pe `account_is_complete`), `app/web/templates/_banner.html` sau `_acasa.html` (banner „Completeaza datele firmei"), `tests/test_web_cont.py`, `tests/test_admin.py` (~6 fisiere) - **Test intai (RED)**: `tests/test_web_cont.py` — `test_cont_afiseaza_companie_email_cui`, `test_post_date_firma_actualizeaza`, `test_post_date_firma_cui_duplicat_eroare`, `test_banner_cont_incomplet_pe_legacy`; `tests/test_admin.py` — `test_activare_cont_incomplet_refuzata`. - **Acceptance criteria**: - [x] `_cont.html` are o sectiune „Date firma" (deasupra cheii API) cu companie + email + CUI editabile, prefilled din `accounts`; `POST /cont/date-firma` valideaza (reuse `create_account`-style) + CSRF + scoped sesiune; eroare pe CUI duplicat / camp gol, mesaj 3-niveluri. - [x] Banner ne-blocant „Completeaza datele firmei (email/CUI)" pe Acasa cand `account_is_complete` e fals; dispare dupa completare. NU blocheaza importul/uploadul. - [x] In panoul admin, butonul **Activeaza** e dezactivat (cu tooltip) pe conturi incomplete — nu activam la RAR un cont fara identitate completa. - [x] Fara regresie pe rutele existente din `_cont.html` (cheie API, creds RAR). - **Verificare E2E**: browser pe `/?tab=cont` — completez email+CUI → banner dispare; admin nu poate activa un cont incomplet. ### US-003: UI — pasul „Potriveste coloanele" arata antet + prima inregistrare **Ca** operator **vreau** sa vad numele coloanelor din fisier si valorile primului rand **pentru ca** sa stiu exact ce date mapez la fiecare camp RAR. - **Depinde de**: — - **Fisiere**: `app/web/templates/_mapcoloane.html`, `app/web/routes.py` (`web_upload_import` / `web_save_mapare_coloane` paseaza deja `sample_rows`; expune `prima_inregistrare`), `tests/test_web_mapcoloane.py` (~3 fisiere) - **Test intai (RED)**: `tests/test_web_mapcoloane.py` — `test_mapcoloane_arata_cap_tabel_coloane`, `test_mapcoloane_arata_valori_prima_inregistrare`, `test_mapcoloane_fara_randuri_degradeaza` (fisier cu antet, fara randuri de date → fara crash). - **Acceptance criteria**: - [x] Deasupra (sau langa) randurile de mapare, un mic tabel orizontal cu **un cap de tabel = numele coloanelor din fisier** si **un rand = valorile primei inregistrari** (truncate la o lungime rezonabila, `title` pe valoare integrala). Foloseste `.tablewrap` pentru scroll orizontal pe mobil. - [x] Fiecare coloana din capul de tabel ramane vizual asociata cu select-ul ei de mapare (ex. aceeasi ordine, sau evidentiere la hover) — operatorul vede „coloana X (valoare „...") → campul canonic Y". - [x] Fisier fara randuri de date → se arata doar capul de tabel, fara „prima inregistrare" (fara crash). - [x] Nicio schimbare de backend de parsare/mapare; doar randare (datele exista deja in `sample_rows`). - **Verificare E2E**: browser pasul 2 — upload `prezentari_test.csv` → vad antetul real + valorile randului 1. ### US-004: UI+backend — un singur „Salveaza" pe „Operatii de mapat la cod RAR" **Ca** operator **vreau** sa salvez toate maparile de operatii dintr-un singur click **pentru ca** azi e cate un buton per operatie si trebuie apasat pe fiecare. - **Depinde de**: — - **Fisiere**: `app/web/templates/_preview_import.html` (panoul de mapare → un singur `