diff --git a/TODOS.md b/TODOS.md index c970fbb..6da0e97 100644 --- a/TODOS.md +++ b/TODOS.md @@ -38,3 +38,18 @@ Elemente deferate din review-uri. Negrupte de un PRD curent; de promovat cand de - [ ] **Validare premisa "utilizare mobil reala"** — inainte de orice extindere responsive viitoare, confirma device-mix-ul (analytics/cerere user). Daca ~95% desktop, nu mai investi in cardificare mobil. (CEO F1, high — premisa nedovedita acum.) + +## Din /plan-ceo-review PRD 5.15 (2026-06-28) + +- [ ] **Validare premisa "utilizare mobil reala" (reluare F1 din 5.13)** — partea slim/compact a lui + 5.15 presupune utilizare reala pe mobil. Inainte de orice rafinare responsive viitoare, confirma + device-mix-ul (analytics / cerere user). Daca ~95% desktop, nu mai investi in cardificare mobil. + (CEO, high — premisa nedovedita.) + +- [ ] **Deduparea/etichetarea temelor grafit~dark si hartie~light** — 5.15 adauga 4 teme peste cele 3 + existente (7 + Auto). grafit e ~ identic cu dark, hartie ~ identic cu light. Daca selectorul devine + confuz sau matricea de test apasa, dedupica sau eticheteaza-le clar. (CEO, low — simplificare optionala.) + +- [ ] **US-009/US-010 ca PRD separat daca propagarea design e urgenta** — salvarea mapare-din-chip si + bulk-fix sunt adiacente FUNCTIONALE (acceptate via SELECTIVE EXPANSION), dincolo de obiectivul pur de + propagare design. Daca vrei sa livrezi designul rapid, pot fi scoase intr-un PRD propriu. (CEO, low.) diff --git a/docs/prd/prd-5.15-propagare-design-dashboard-editare.md b/docs/prd/prd-5.15-propagare-design-dashboard-editare.md index 320c8d8..0f2a76b 100644 --- a/docs/prd/prd-5.15-propagare-design-dashboard-editare.md +++ b/docs/prd/prd-5.15-propagare-design-dashboard-editare.md @@ -32,6 +32,23 @@ Decizii de produs confirmate cu userul (poarta de aprobare a acestui PRD): coloana Observatii, **concatenam denumirea operatiei de service in `obs`**. `obs` ramane in `payload_json` (camp din contractul RAR), fara coloana noua. +Decizii din /plan-ceo-review (2026-06-28, mod SELECTIVE EXPANSION): +- **D6** (sanatate mereu-vizibila): cardurile-contor inlocuiesc bara de status, DAR sanatatea + (worker viu? RAR accesibil? ultima autentificare) ramane intr-un **strip mereu-vizibil, colorat, + deasupra contoarelor** (verde "declaratiile curg" / rosu "blocat: worker oprit / RAR inaccesibil"). + Invariant: zero-silent-failures — semnalul critic NU se ingroapa sub volum. (Rafineaza D1.) +- **D7** (operatie -> obs, fara regresie de mapare): la import, denumirea operatiei RAMANE in + `op_service` (sursa pentru maparea op->cod) SI se COPIAZA in `obs`. `obs` e sink aditional, nu + mutare; fluxul needs_mapping ramane neatins. (Rafineaza D5.) +- **D8** (idempotenta obs): `obs` e EXCLUS din cheia de idempotenta (`idempotency.py:98`). Deci + editarea `obs` NU schimba cheia si NU poate crea duplicate — corecteaza AC-ul gresit din US-005. + `prestatii` ESTE in cheie (sortat dupa cod) — multi-select re-cheieaza randul (US-006). +- **D9** (secventiere): 5.15 INAINTE de 5.14 (mapare LLM). Editorul manual defineste forma listei + `prestatii` si UX-ul de confirmare; 5.14 umple codurile peste aceeasi forma. +- **D10** (extinderi acceptate, SELECTIVE EXPANSION): toate 4 intra in scope — (a) salvare mapare + din chip (US-009), (b) bulk-fix din lista (US-010), (c) require dinamic odometruInitial la chip + R-ODO/I-ODO (US-007), (d) editare keyboard-first in form slim (US-007). + Fapte verificate care fundamenteaza scope-ul (nu presupuneri): - `vin` la RAR e **un singur camp** (17 car., MAJUSCULE, fara O/I/Q) — cerinta "fara 2 campuri VIN" e deja respectata azi (`_form_editare.html` are un singur `vin`); ramane sa NU regresam. @@ -112,16 +129,25 @@ chips **pentru ca** dashboard-ul si formularul sa le consume DRY, identic cu moc - **Fisiere**: `app/web/templates/_status.html`, `app/web/templates/_acasa.html`, `app/web/routes.py` (`_status_counts` extins cu `sent_today`/`sent_month`), `tests/test_web_status.py`, `tests/test_web_dashboard.py` (~5 fisiere) -- **Test intai (RED)**: `tests/test_web_status.py` — `test_trei_contoare_card`, `test_trimise_all_time_luna_azi`, `test_sanatate_compacta_worker_rar`, `test_fara_bara_veche` +- **Test intai (RED)**: `tests/test_web_status.py` — `test_strip_sanatate_mereu_vizibil`, `test_strip_rosu_worker_oprit`, `test_trei_contoare_card`, `test_trimise_all_time_luna_azi`, `test_fara_bara_veche` - **Acceptance criteria**: - - [ ] Antetul Acasa = card "Trimiteri RAR AUTOPASS" cu 3 contoare slim: **In coada** (queued, accent), + - [ ] **Strip de sanatate mereu-vizibil, DEASUPRA contoarelor** (D6): o linie compacta colorata — + verde "declaratiile curg" cand worker viu + RAR ok; **rosu** + text explicit cand worker + oprit SAU RAR inaccesibil ("Blocat: worker oprit" / "Blocat: RAR inaccesibil"), cu ultima + autentificare RAR. Glife accesibile ✓/✗ (nu doar culoare). Invariant zero-silent-failures: + semnalul "declaratiile NU pleaca" e imposibil de ratat, NU ingropat sub volum. + - [ ] Sub strip: card "Trimiteri RAR AUTOPASS" cu 3 contoare slim: **In coada** (queued, accent), **Trimise** (sent, verde), **De corectat** (blocate = needs_data + needs_mapping + error, rosu). - [ ] Cardul **Trimise** afiseaza trei valori temporale (D4): all-time (cifra principala) + "luna asta" - + "azi" (sub-linie secundara). `_status_counts` extins cu `sent_today`/`sent_month` (filtru pe - `updated_at`/data trimitere; scoped pe cont), restul contoarelor din numaratoarea existenta. - - [ ] Indicatorii de sanatate worker/RAR + ultima autentificare RAR raman, intr-o forma compacta - (pill/glif), nu bara cu 2 randuri ca azi; pastreaza glifele accesibile ✓/✗ (nu doar culoare). - - [ ] Navigarea existenta (Trimiteri/Mapari + badge needs_mapping) se pastreaza. + + "azi" (sub-linie secundara). `_status_counts` extins cu `sent_today`/`sent_month`. + **Sursa de timp**: NU exista coloana `sent_at`; folosim `status='sent' AND date(updated_at)=...`. + Justificare (verificat): un rand `sent` nu mai primeste scrieri ulterioare pana la purge-delete + la +90z (`purge_after` se seteaza in ACEEASI scriere care marcheaza `sent`), deci `updated_at` + == momentul trimiterii pentru randurile `sent` -> fara migrare de coloana (respecta Non-Goal). + Daca pe viitor apar scrieri post-`sent`, reevalueaza o coloana `sent_at` dedicata. + - [ ] Navigarea existenta (Trimiteri/Mapari + badge needs_mapping) se pastreaza. Click pe contorul + **De corectat** deep-link-eaza in lista filtrata pe blocate (`?status=` existent din 5.x), + nu intr-o pagina noua. - [ ] Scoped pe cont; poll-ul existent (`/_fragments/status`) randeaza noul antet fara a pierde tab-ul. - [ ] Responsive: cele 3 contoare pe un rand pe desktop, stivuite/2-pe-rand pe mobil, fara overflow. - **Verificare E2E**: browser pe `/` — contoare corecte vs date din DB, sanatate worker mort/viu, @@ -161,11 +187,13 @@ sa corectez/completez ce s-a facut, separat de codurile RAR. - [ ] `obs` traieste in `payload_json` (camp `obs` din contractul RAR); fara coloana noua / migrare (D5). - [ ] `obs` adaugat in `EDIT_FIELDS`; `corecteaza` si `editeaza` (preview) accepta si persista `obs`. - [ ] `obs` optional (text liber, fara validare de continut, doar trim); apare in `payload_view`. - - [ ] `obs` se include in payload-ul trimis la RAR (camp `obs`) — fara a schimba celelalte campuri; - idempotenta se recalculeaza ca la orice editare (mecanism existent). - - [ ] **La import**, daca fisierul NU are coloana Observatii, denumirea operatiei de service se - CONCATENEAZA in `obs` (D5: `obs` = operatiile efectuate); daca are coloana Observatii, se - pastreaza textul ei. Format de concatenare definit (ex. denumiri separate prin "; "). + - [ ] `obs` se include in payload-ul trimis la RAR (camp `obs`). **`obs` e EXCLUS din cheia de + idempotenta** (`idempotency.py:98`) — deci editarea DOAR a `obs` NU schimba cheia si NU poate + crea duplicat (D8). NU recalcula/forta cheia pe baza `obs`. (Corecteaza formularea anterioara.) + - [ ] **La import** (D7): denumirea operatiei RAMANE in `op_service` (sursa pentru maparea op->cod); + daca fisierul NU are coloana Observatii, denumirea operatiei se **COPIAZA** (nu se muta) si in + `obs`; daca are coloana Observatii, se pastreaza textul ei. Format de concatenare definit + (denumiri separate prin "; "). Fluxul needs_mapping ramane neatins. - **Verificare E2E**: `POST /trimitere/{id}/corecteaza` cu `obs` -> persistat -> vizibil in detaliu; optional proba live RAR ca `obs` apare in FINALIZATA. @@ -174,17 +202,29 @@ sa corectez/completez ce s-a facut, separat de codurile RAR. comanda poate avea mai multe prestatii, asa cum accepta RAR. - **Depinde de**: — -- **Fisiere**: `app/web/routes.py` (`/corecteaza`, `/repune`), - `app/api/v1/import_router.py` (`/editeaza`), `app/mapping.py` (folosit cu lista — fara schimbare - de logica), `app/validation.py` (fiecare cod in nomenclator), `tests/test_web_corectie*.py`, - `tests/test_mapping*.py` (~6 fisiere) +- **Fisiere**: `app/web/routes.py` (`/corecteaza`, `/repune` — **rescrie logica single-`prestatii[0]`** + de azi: `cod_prestatie_curent` la `routes.py:977-982` + injectia la `1146-1164`/`1288-1324` + presupun UN cod; multi-select cere pre-fill din lista intreaga + scriere pe toti itemii), + `app/api/v1/import_router.py` (`/editeaza`, idem), `app/mapping.py` (NEATINS — deja accepta lista), + `app/validation.py` (fiecare cod in nomenclator), `tests/test_web_corectie*.py`, + `tests/test_mapping*.py` (~6 fisiere). Nota: `mapping.py` e neatins, dar call-site-urile din + handler-e cer un rewrite real (nu "fara schimbare de logica"). - **Test intai (RED)**: `tests/test_web_corectie_prestatii.py` — `test_mai_multe_coduri_acceptate`, `test_cod_invalid_respins`, `test_lista_goala_needs_mapping`, `test_idempotency_recalculat`, `test_odometru_initial_conditionat_R_ODO` - **Acceptance criteria**: - - [ ] Handler-ele de editare accepta o LISTA de `cod_prestatie` (mai multe valori), inlocuind - selectul unic; reconstruiesc `prestatii` ca `[{cod_prestatie, idPrezentare:null}, ...]`. + - [ ] Handler-ele de editare accepta o LISTA de `cod_prestatie`, inlocuind selectul unic. **NU + reconstrui lista cu itemi goi**: handler-ele de azi injecteaza codul DOAR in `prestatii[0]` + (`routes.py:1146-1164`, `1288-1324`) — multi-select le rescrie ca: pastreaza itemii existenti + cu `cod_op_service`/`denumire` (invariant D7) si seteaza/adauga `cod_prestatie` pe ei. + `idPrezentare:null` se adauga in `payload.py` la construirea payload-ului, NU in itemul intern. + - [ ] **Pereche operatie<->cod definita**: cand exista operatii (`cod_op_service`), fiecare cod-chip + se ataseaza unei operatii (1 operatie -> 1 cod, ca azi, dar acum N operatii -> N coduri); + cand NU exista operatie (cod direct, ex. corectie pura), chip-urile sunt coduri libere intr-o + lista fara `op_service`. Aceasta pereche e ce consuma US-009 (salvare mapare op->cod). - [ ] Fiecare cod e validat fata de nomenclator (`valid_codes`); cod necunoscut -> respins cu mesaj (NU se trimite raw — invariant ORA-12899 din CLAUDE.md/contract). - [ ] Lista goala de coduri -> ramane `needs_mapping` (nu se trimite fara cod). + - [ ] **Coduri duplicate** (acelasi cod adaugat de 2x) -> deduplicate inainte de persistare + (cheia sorteaza deja dupa identitate, dar lista persistata nu trebuie sa contina duplicate). - [ ] Recalcul idempotenta dupa editare (mecanism existent), cu prinderea coliziunii ca azi. - [ ] Se pastreaza regula `odometruInitial` obligatoriu cand lista contine `R-ODO`/`I-ODO` (contract §payload) — validare existenta, doar verificata pe lista. @@ -208,9 +248,20 @@ e compact si imi arata clar codurile RAR si observatiile, ca in mockup. nomenclator) adauga un cod nou; lista se trimite ca `cod_prestatie` multiplu (US-006). - [ ] Acelasi `_form_editare.html` slujeste ambele modale (detaliu `/corecteaza` si preview `/editeaza`), fara duplicare; degradare fara JS rezonabila (chips ca lista, picker = select). - - [ ] Stilizare fidela mockup-ului pe cele 4 teme; tinte 44px pe mobil; a11y (label-uri, aria). + - [ ] **Require dinamic odometruInitial** (D10c): cand lista de chips contine `R-ODO` sau `I-ODO`, + formularul DEZVALUIE si cere `odometru_initial` (contract §payload), previne 400 RAR si un + drum `needs_data`. Cand niciun chip R-ODO/I-ODO -> campul ramane optional/ascuns. + - [ ] **Editare keyboard-first** (D10d): in picker, Enter adauga chip-ul selectat; sageti + navigheaza optiunile; Esc inchide modalul; focus-ul revine logic dupa adaugare/stergere. + - [ ] Stilizare fidela mockup-ului pe toate temele; tinte 44px pe mobil; a11y (label-uri, aria, + anunt de chip adaugat/sters pentru screen-reader). + - [ ] **Suprafata JS reala** (nota de efort): chips add/remove client-side + picker navigabil cu + tastatura + management focus + reveal conditional odometruInitial = JS ne-trivial intr-un app + HTMX/minimal-JS. Fallback fara JS: picker = `