# PRD 3.6 — Editare celule in preview + Acasa unificata (Trimiteri inline, upload slim, Mapari tabelar) **Stare**: inchis (2026-06-22 — CLOSE: `/code-review` high a prins 1 bug real, reparat; dashboard ROADMAP → DONE. Toate US-001..007 implementate, 523 teste pass, VERIFY E2E browser + LIVE RAR test PASS; vezi `## Raport VERIFY` si `## Raport CLOSE`) > Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`. > Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` (actualizata de lead). > Continua 3.5 ([prd-3.5](prd-3.5-dashboard-compact-trimiteri-mapari.md)). **Backend trimitere (worker, > masina stari, idempotenta-logica, mapping-rezolvare) NU se atinge** — doar canalul de import si stratul web. > Exceptie schema decisa la poarta autoplan: UNA coloana nullable `import_rows.override_json` (Approach B), cu > migrare defensiva `_migrate` ca la 3.3b/3.5. Worker/idempotenta/mapare raman neatinse. ## 1. Obiectiv Dupa upload, utilizatorul putea citi randurile cu probleme dar nu le putea **corecta inainte de trimitere** — singura editare de continut era post-confirmare, in alt tab. Livram: (a) editare de celule direct in preview (buton "Editeaza" pe rand), si (b) reunificarea fluxului pe pagina **Acasa** — Trimiterile devin o sectiune permanenta sub upload (tab-ul "Trimiteri" dispare), zona de upload se comprima la o bara slim, iar "Mapari" trece pe format tabelar compact cu eticheta auto-send reformulata ca un comutator Automat/Manual. Motivatie: microcopia spune "vezi mai jos trimiterile", dar trimiterile erau intr-un alt tab; cazul real de utilizare (fisier cu o data/VIN lipsa sau gresit) cerea re-upload in loc de o corectie pe loc. ## 2. Non-Goals (anti scope-creep) - **Fara editare a operatiei/codului RAR in celula** — codul de operatie ramane rezolvat prin panoul de mapare existent (`needs_mapping`). Editarea de celule acopera campurile de continut: VIN, nr. inmatriculare, data prestatie, odometru initial/final. (Operatia se schimba din panoul de mapare, nu din tabel.) - **Fara editare in bloc / multi-rand** — un rand pe rand, mod editare explicit. - **Schema: o singura coloana noua** — `import_rows.override_json` (nullable, criptat Fernet), patch CANONIC peste randul mapat (Approach B). NU se modifica `raw_json` (ramane cheiat pe anteturile fisierului). Restul schemei neatins. - **Fara atingerea logicii de idempotenta, validare, mapare sau a worker-ului** — editarea = mutatie pura de stocare; recalculul de stare merge OBLIGATORIU prin `_resolve_row_for_preview` (un singur clasificator, fara drift). - **Fara reorganizarea tab-urilor ramase** (Mapari/Cont/Nomenclator) dincolo de scoaterea tab-ului Trimiteri. - **Fara paginare noua pe Trimiteri** — sectiunea de pe Acasa refoloseste filtrarea existenta (US-009 din 3.5). ## 3. Stories atomice > Backend + UI pentru acelasi comportament = stories separate. Toate rutele web noi sunt sub `require_login` > si **scoped pe contul din sesiune** (gard cross-account 404, identic cu rutele existente). CSRF pe toate POST-urile. ### US-001: Backend — persista editarea unui rand de preview **Ca** utilizator care vede un rand cu date gresite/lipsa in preview **vreau** sa salvez valori corectate pentru acel rand **pentru ca** sa-l trimit fara re-upload de fisier. - **Depinde de**: — - **Fisiere**: `app/schema.sql` (coloana `override_json` + `_migrate`), `app/api/v1/import_router.py` (`_resolve_row_for_preview` + `commit_import` aplica override; ruta noua), `tests/test_import_edit_row.py`, `tests/fixtures/import_antet_necanonic.csv` (NOU), `tests/fixtures/import_lipsa_coloana.csv` (NOU) (~5 fisiere) - **Stocare (Approach B, decis la poarta autoplan):** editarea scrie un dict CANONIC in `import_rows.override_json` (nullable, criptat Fernet). `_resolve_row_for_preview` si `commit_import` aplica `mapped.update(override_json)` ULTIMUL, dupa maparea `json_mapare`. Astfel se poate completa si un camp a carui coloana LIPSESTE din fisier (cazul "completez informatii lipsa"), iar `raw_json`/idempotency raman neatinse. - **Test intai (RED)**: `tests/test_import_edit_row.py` — `test_editeaza_rand_antet_necanonic_devine_ok` (fixture cu antet `Serie sasiu`/`Data` — prinde bug-ul de stocare), `test_editeaza_completeaza_coloana_absenta` (fisier fara coloana data → editarea adauga data → ok), `test_editeaza_status_identic_cu_GET_preview` (ruta editare NU re-deriva status; egal cu `GET /preview`), `test_editeaza_rand_scoped_alt_cont_404`, `test_editeaza_batch_inexistent_404`, `test_editeaza_row_index_invalid_pe_batch_valid_404`, `test_editeaza_pastreaza_campuri_neatinse` (operatie/prestatii raman), `test_editeaza_batch_committed_409` (guard post-commit), `test_editeaza_raw_corupt_no_op` (decrypt fail → 422/no-op, fara crash), `test_editeaza_empty_input_sterge_campul` (semantica empty = CLEAR, pentru cazul "corectez o valoare gresita"). - **Acceptance criteria**: - [ ] Migrare: `import_rows.override_json TEXT` (nullable), `_migrate` defensiv (idempotent, ca `is_admin` in 3.3b). - [ ] Ruta `POST /v1/import/{import_id}/rand/{row_index}/editeaza` (`resolve_account_id`) + alias web `POST /_import/{import_id}/rand/{row_index}/editeaza` (`require_login`). Campuri: `vin`, `nr_inmatriculare`, `data_prestatie`, `odometru_initial`, `odometru_final`. - [ ] **Mutatie pura**: decripteaza `override_json` curent (sau {}), aplica campurile (vezi semantica empty), re-cripteaza, `UPDATE`. NU recalculeaza statusul in ruta — preview-ul il rederiva via `_resolve_row_for_preview`. - [ ] **Semantica empty**: input gol = STERGE cheia din override (revine la valoarea din fisier daca exista). Documentat + testat. - [ ] **Scoping intr-o singura interogare**: `import_rows r JOIN import_batches b ON b.id=r.batch_id WHERE b.id=? AND b.account_id=? AND r.row_index=?` → 404 pe gol (acopera alt cont, batch inexistent, row_index invalid). - [ ] **Guard committed**: batch cu `status='committed'` → 409 (editarea n-ar avea efect downstream). - [ ] `decrypt_creds` → None/exceptie → 422/no-op defensiv (ca import_router.py:602-606), niciodata scriere goala. - [ ] Coercion: nu se afirma `canonicalize_row` pe `odometru_initial` (normeaza doar `_final`); validarea (`_parse_int`) tolereaza ".0" — testul verifica prin validare, nu prin canonicalize. - [ ] NU atinge `submissions`. - **Verificare E2E**: TestClient — (a) fixture cu antet ne-canonic, rand needs_data → editeaza → preview = `ok`; (b) fixture fara coloana data → editeaza data → `ok`. ### US-002: UI — buton "Editeaza" pe rand in tabelul de preview **Ca** utilizator **vreau** sa pun un rand in mod editare si sa-i corectez celulele pe loc **pentru ca** sa nu reincarc tot fisierul pentru o singura valoare. - **Depinde de**: US-001 - **Fisiere**: `app/web/templates/_preview_import.html`, `app/web/templates/_preview_rand.html` (NOU — fragment rand), `app/web/routes.py` (handler fragment rand), `tests/test_preview_edit_ui.py` - **Test intai (RED)**: `tests/test_preview_edit_ui.py` — `test_preview_are_buton_editeaza_pe_rand`, `test_editeaza_intra_in_mod_editare_form_propriu` (randul devine FORM separat, NU in `#confirm-form`), `test_salveaza_reda_doar_randul` (raspuns = fragment rand + OOB contoare, NU tot `#import-section`), `test_enter_in_camp_editare_nu_declanseaza_confirm`, `test_eroare_validare_pastreaza_valorile_introduse` (data invalida → ramane in editare, mesaj pe camp). - **Acceptance criteria**: - [ ] Fiecare rand are un buton "Editeaza" (coloana de actiuni la final). - [ ] **Swap pe rand, NU pe sectiune (D-3.1):** Editeaza/Salveaza tintesc randul `