feat(web): dashboard compact — import pe Acasa, status cu bife, Trimiteri lizibile, Mapari complete (3.5)
Acasa = ecran de import (tab Import scos, ?tab=import->Acasa). Bara status compacta pe 2 randuri cu bife accesibile (glife + text) + data formatata. 'Coada'->'Trimiteri': coloane RO, stare umana, detaliu la click in panou dedicat. Mapari pe 3 sectiuni (de rezolvat / op salvate / formate coloane), Cont doar cheie+creds. Filtrare Trimiteri, corectie inline needs_data cu re-enqueue + detectie coliziune idempotency, badge contoare pe tab-uri. Helper pur partajat payload_view.py (web + GET /v1/prezentari). Backend trimitere (worker/idempotenta/mapping/schema) neatins. 483 teste. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,9 @@ Reguli de contract (detalii in `docs/api-rar-contract.md`): `FINALIZATA` e termi
|
||||
> PRD-uri (`docs/prd/prd-X.Y-*.md`), linkate in coloana Detalii. La fiecare livrabila terminata:
|
||||
> schimba statusul + data + linkul PRD si actualizeaza "Ultima actualizare".
|
||||
|
||||
**Ultima actualizare**: 2026-06-18 — 3.4 LIVRAT (interfata web ergonomica: tab-uri + wizard + microcopy). US-001 modul pur `app/web/labels.py` (stari tehnice→text uman + clasa CSS; test parametrizat din CHECK-ul `schema.sql` iese rosu la stare nemapata). US-002 bara status `/_fragments/status` + `_status.html` (etichete umane, defalcare blocate pe motiv, poll 15s, scoped pe cont). US-003 shell 6 tab-uri (Acasa·Import·Coada·Mapari·Cont·Nomenclator) cu deep-link `?tab=`, panou activ randat server-side, fragmente inactive lazy pe click, ARIA real (tablist/tab/tabpanel + aria-selected + navigare cu sageti). US-004 stepper import 4 pasi (PUR vizual, `hx-target="#import-section"` + csrf pastrate). US-005 Acasa onboarding checklist auto-bifat (are_creds/are_trimiteri) + colaps cand totul gata + empty states prietenoase Coada/Mapari. VERIFY lead-driven (TestClient ACs + 434 pytest pass; E2E browser/RAR LIVE neprobat in sesiune — recomandata probare manuala `--send`). Fix izolare teste (reset `ratelimit._hits` in fixturi, 429 la rulare subset). `/code-review` high: regasit avertisment "cont in asteptare de activare" (regresie din scoaterea `/_fragments/banner`) re-introdus in bara status + culori hardcodate→variabile paleta. 434 teste pass. Backend trimitere neatins. PRD: [prd-3.4](prd/prd-3.4-ux-dashboard-web.md). Urmeaza Etapa 4 (4.1 mapare AI/MCP).
|
||||
**Ultima actualizare**: 2026-06-19 — 3.5 LIVRAT (dashboard compact: import pe prima pagina, status cu bife, Trimiteri lizibile, Mapari complete). 11 stories in 4 valuri, TDD. US-001 bara status compacta pe 2 randuri cu bife accesibile (glife ✓/✗ + text, nu doar culoare) + `format_data_rar` (dd.mm.yyyy hh24:mi:ss, helper pur). US-002 Acasa = ecranul de import (upload dominant inline, tab Import scos, `?tab=import`→Acasa fara 404). US-003 helper pur partajat `app/payload_view.py` (payload→campuri afisabile, defensiv, coercion Excel) refolosit si de `GET /v1/prezentari` (DRY). US-004 "Coada"→"Trimiteri": coloane RO + stare umana + detaliu complet la click in panou dedicat `#trimitere-detaliu` (nu inline — poll 10s), scoped 404 cross-account. US-005/006 CRUD mapari operatii + formate coloane salvate (scoped, re-rezolvare auto la edit cod). US-007 "Mapari" 3 sectiuni (de rezolvat / op salvate / formate coloane), "Cont" doar cheie+creds. US-008 motiv (mesaj validare) pe randuri needs_data in preview. US-009 filtre Trimiteri (stare SQL / vehicul+data Python) scoped + "sterge filtrele". US-010 corectie inline needs_data→queued cu payload+idempotency recalculate, sent read-only (403), coliziune idempotency prinsa pre-UPDATE. US-011 badge contoare pe tab-uri (Mapari/Trimiteri), scoped, aria-label. VERIFY context curat PASS (483 teste; E2E browser/RAR LIVE neprobat — recomandata probare manuala `--send`). `/code-review` high a prins 4 findings reale, toate reparate: corectie needs_mapping re-rezolva prestatiile (nu mai poate trimite cod nul la RAR), filtru fara LIMIT silentios, coliziune idempotency atomica (try/except IntegrityError), comparatie data doar ISO. Backend trimitere (worker, masina stari, idempotenta-logica, mapping-rezolvare, schema) NEATINS. PRD: [prd-3.5](prd/prd-3.5-dashboard-compact-trimiteri-mapari.md). Urmeaza Etapa 4 (4.1 mapare AI/MCP).
|
||||
|
||||
> 2026-06-18 — 3.4 LIVRAT (interfata web ergonomica: tab-uri + wizard + microcopy). US-001 modul pur `app/web/labels.py` (stari tehnice→text uman + clasa CSS; test parametrizat din CHECK-ul `schema.sql` iese rosu la stare nemapata). US-002 bara status `/_fragments/status` + `_status.html` (etichete umane, defalcare blocate pe motiv, poll 15s, scoped pe cont). US-003 shell 6 tab-uri (Acasa·Import·Coada·Mapari·Cont·Nomenclator) cu deep-link `?tab=`, panou activ randat server-side, fragmente inactive lazy pe click, ARIA real (tablist/tab/tabpanel + aria-selected + navigare cu sageti). US-004 stepper import 4 pasi (PUR vizual, `hx-target="#import-section"` + csrf pastrate). US-005 Acasa onboarding checklist auto-bifat (are_creds/are_trimiteri) + colaps cand totul gata + empty states prietenoase Coada/Mapari. VERIFY lead-driven (TestClient ACs + 434 pytest pass; E2E browser/RAR LIVE neprobat in sesiune — recomandata probare manuala `--send`). Fix izolare teste (reset `ratelimit._hits` in fixturi, 429 la rulare subset). `/code-review` high: regasit avertisment "cont in asteptare de activare" (regresie din scoaterea `/_fragments/banner`) re-introdus in bara status + culori hardcodate→variabile paleta. 434 teste pass. Backend trimitere neatins. PRD: [prd-3.4](prd/prd-3.4-ux-dashboard-web.md). Urmeaza Etapa 4 (4.1 mapare AI/MCP).
|
||||
|
||||
> 3.3b LIVRAT (self-service cheie/creds + admin web + email). US-007 rute web proprii pentru rotire cheie + setare creds RAR scoped pe sesiune (C13, nu endpointul API). US-010 rol admin (`users.is_admin`) + `require_admin`→`AdminRequired`→403 + CLI `tools/account.py set-admin` + bootstrap automat (primul cont care se inregistreaza = admin, citit in `BEGIN IMMEDIATE` anti-race). US-011 panou `/admin` (conturi in asteptare/active, activare/dezactivare cu CSRF + PRG, contul dev id=1 protejat) + link "Panou admin" pe dashboard doar pentru admini + buton logout. US-012 `app/email.py notify_signup` best-effort DEGRADAT fara SMTP (no-op + log, prinde orice exceptie, nu blocheaza signup) + config `smtp_*`. Fix migrare defensiva `users.is_admin`/`email_verified` in `_migrate` (gap prins de VERIFY r1, ca C1 pe `accounts.active`). 2 runde VERIFY context curat (r2 PASS, sweep securitate toate rutele noi sub require_login/require_admin + CSRF, scoped sesiune). `/code-review` high: TOCTOU bootstrap mutat in tranzactie + `_render_admin` extras (anti-duplicare + N+1). 393 teste pass. Urmeaza Etapa 4 (4.1 mapare AI/MCP). Deferat din 3.1 (P3): `rename`/`set-cui`, `--if-not-exists`. SMTP real = follow-up pe US-012.
|
||||
|
||||
@@ -81,6 +83,7 @@ Reguli de contract (detalii in `docs/api-rar-contract.md`): `FINALIZATA` e termi
|
||||
| 3.3a | Self-onboarding web (core) | DONE | 2026-06-17 | `users` (scrypt) + sesiune (`SessionMiddleware`, same_site=strict) + CSRF (enforce prod, inclusiv login/signup) + rate-limit signup/login + signup/login/logout + dashboard & import scoped pe sesiune (NULL→1, anti-leak C6) + gate worker `active=0` (`COALESCE`). 2 runde VERIFY (leak `/_fragments/mapari` prins+reparat) + code-review (csrf erori, scrypt_params, login rate-limit). 361 teste. PRD: [prd-3.3](prd/prd-3.3-self-onboarding-web.md) |
|
||||
| 3.3b | Self-service cheie/creds + admin web + email | DONE | 2026-06-18 | US-007 (rute web proprii `/cont/roteste-cheie`+`/cont/rar-creds` scoped sesiune, C13), US-010 (rol admin `is_admin` + `require_admin`→403 + CLI `set-admin` + bootstrap primul cont=admin), US-011 (`/admin` activare/dezactivare cu CSRF+PRG, link doar pt admini + logout), US-012 (`app/email.py` notify best-effort degradat fara SMTP + log `SIGNUP`). Fix migrare defensiva `users.is_admin`/`email_verified`. 2 runde VERIFY context curat (r1 a prins migrarea lipsa, reparat; r2 PASS) + `/code-review` high (TOCTOU bootstrap admin mutat in tranzactie + extras `_render_admin` anti-duplicare/N+1). 393 teste. PRD: [prd-3.3](prd/prd-3.3-self-onboarding-web.md) |
|
||||
| 3.4 | Interfata web ergonomica (tab-uri + wizard + microcopy uman) | DONE | 2026-06-18 | Dashboard reorganizat in 6 tab-uri (Acasa·Import·Coada·Mapari·Cont·Nomenclator) cu deep-link `?tab=` + panou activ server-side + lazy pe rest; bara status cu etichete umane (`app/web/labels.py`) + defalcare blocate; import ca stepper 4 pasi (PUR vizual); Acasa onboarding auto-bifat + empty states. Backend trimitere neatins. 434 teste. PRD: [prd-3.4](prd/prd-3.4-ux-dashboard-web.md) |
|
||||
| 3.5 | Dashboard compact: import pe prima pagina, status cu bife, Trimiteri lizibile, Mapari complete | DONE | 2026-06-19 | 11 stories (4 valuri), 3 review-uri de plan facute. Acasa=ecran de import (scoate tab Import); bara status compacta font normal + bife accesibile (auto-send/RAR) + data `dd.mm.yyyy hh24:mi:ss`; "Coada"→"Trimiteri" cu coloane RO + detalii comanda din `payload_json` (helper partajat `payload_view.py`) + detaliu la click in panou dedicat; filtrare Trimiteri (US-009); corectie inline `needs_data` cu re-enqueue + detectie coliziune idempotency (US-010); badge contoare pe tab-uri (US-011); "Mapari" 3 sectiuni (de rezolvat / op salvate cu re-rezolvare auto / formate coloane), "Cont"=doar cheie+creds; feedback `needs_data` la import. Backend trimitere neatins. PRD: [prd-3.5](prd/prd-3.5-dashboard-compact-trimiteri-mapari.md) |
|
||||
|
||||
### Etapa 4 — Viitor (Treapta 3)
|
||||
|
||||
|
||||
495
docs/prd/prd-3.5-dashboard-compact-trimiteri-mapari.md
Normal file
495
docs/prd/prd-3.5-dashboard-compact-trimiteri-mapari.md
Normal file
@@ -0,0 +1,495 @@
|
||||
# PRD 3.5 — Dashboard compact: import pe prima pagina, status cu bife, Trimiteri lizibile, Mapari complete
|
||||
|
||||
**Stare**: inchis
|
||||
|
||||
> Proces complet: `docs/ROADMAP.md` §5. Contractul RAR (sursa de adevar): `docs/api-rar-contract.md`.
|
||||
> Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` (actualizata de lead).
|
||||
> Aceasta e o livrabila de **UI/UX** — continua 3.4 (`prd-3.4-ux-dashboard-web.md`). Atinge stratul web
|
||||
> (Jinja2 + HTMX, zero build) si rute de prezentare/listare. **Nu** modifica `worker/`, `mapping.py`
|
||||
> (logica de rezolvare), `idempotency.py`, masina de stari submissions sau contractul RAR.
|
||||
|
||||
## 1. Obiectiv
|
||||
|
||||
Feedback de utilizare pe interfata 3.4: e mai buna, dar (a) importul — operatia principala — e ascuns
|
||||
intr-un tab, (b) bara de status are fonturi mici si etichete fara bife, (c) pagina "Coada" e neintuitiva
|
||||
(coloane tehnice "HTTP RAR", stare in engleza "sent", "Motiv" gol, si **nu se vede la ce comanda se
|
||||
refera** — doar `idPrezentare`), (d) pagina "Mapari" arata doar operatiile nerezolvate — maparile deja
|
||||
salvate par pierdute, iar maparile de coloane nu se vad nicaieri.
|
||||
|
||||
Livrabila rezolva aceste patru zone, fara a schimba comportamentul backend de trimitere:
|
||||
|
||||
1. **Acasa = ecranul de import.** Prima pagina arata direct caseta de upload (importul e operatia
|
||||
principala), sub o bara de status compacta; ghidul "primii pasi" si link-urile de ajutor coboara pe
|
||||
un singur rand discret. Tab-ul "Import" separat dispare (era acelasi flux).
|
||||
2. **Bara de status compacta, font normal, cu bife.** Doua randuri: sus doua bife (verde/rosu) pentru
|
||||
"Trimitere automata" si "Legatura RAR" + "Ultima autentificare RAR" in format `dd.mm.yyyy hh24:mi:ss`;
|
||||
jos contoarele (in asteptare / declarate / blocate).
|
||||
3. **"Coada" → "Trimiteri", lizibila.** Coloane umane (Stare in romana via `labels.py`, Vehicul,
|
||||
Operatie, Data prestatie, Nr. prezentare RAR, Actualizat, Motiv uman). Click pe rand → detaliu complet
|
||||
(toate campurile, inclusiv codul HTTP tehnic si motivul integral). Detaliile comenzii se citesc din
|
||||
`payload_json` (text JSON simplu, nu criptat).
|
||||
4. **"Mapari" complet — trei sectiuni.** (1) De rezolvat (`needs_mapping`, ca acum), (2) Mapari operatii
|
||||
salvate — `operations_mapping` editabil (schimba cod RAR / auto-send / sterge), (3) Formate de coloane
|
||||
salvate — `column_mappings` per semnatura (vezi coloanele, format data, editeaza/sterge). "Cont" ramane
|
||||
doar cheie API + creds RAR.
|
||||
|
||||
Decizii de layout confirmate cu utilizatorul (AskUserQuestion, cu preview): Acasa=Import direct; status pe
|
||||
doua randuri cu bife; Trimiteri cu detalii in tabel + expand; toate maparile intr-un singur loc ("Mapari").
|
||||
|
||||
## 2. Non-Goals (anti scope-creep)
|
||||
|
||||
- **Fara schimbari de backend de trimitere**: worker, mapare op→cod (rezolvarea), idempotenta,
|
||||
reconciliere, masina de stari submissions raman neatinse. Doar prezentare + listare/editare web.
|
||||
- **Fara endpoint-uri API noi `/v1/*`** si fara schimbari de schema SQL **de structura**. Tabelele
|
||||
`operations_mapping` / `column_mappings` exista deja; doar le expunem/edita prin rute web. Exceptie
|
||||
controlata (acceptata la CEO review): US-009 poate adauga **un index** pe `submissions` pentru filtrare
|
||||
(nu coloane noi, nu tabele noi).
|
||||
- **Fara framework JS / build step**: ramane Jinja2 + HTMX + CSS in `base.html`. Eventualul JS e vanilla
|
||||
inline, minim. Fara React/Vue/Tailwind/bundler.
|
||||
- **Fara rescriere a fluxului de import** (parsare, mapare coloane, preview, commit raman ca logica) —
|
||||
doar muta upload-ul pe Acasa si imbraca rezultatul.
|
||||
- **Fara redesign login/signup/admin** dincolo de aplicarea acelorasi clase/etichete daca e trivial.
|
||||
- **Fara i18n / tema light**: texte in romana hardcodate, paleta dark din `base.html`.
|
||||
- **Fara modificari de worker / masina de stari / reconciliere**: US-010 (corectie inline) doar
|
||||
re-valideaza (`validation.py`) si re-pune randul in `queued` (re-enqueue), fara sa atinga worker-ul.
|
||||
- **Editarea de continut e permisa DOAR pentru randuri ne-trimise blocate** (`needs_data`/`needs_mapping`),
|
||||
prin US-010. Randurile `sent`/`FINALIZATA` raman **read-only** (terminal la RAR, fara anulare/corectie).
|
||||
|
||||
## 3. Stories atomice
|
||||
|
||||
> Fiecare story: cea mai mica unitate care lasa sistemul functional. Backend + UI pentru acelasi
|
||||
> comportament = 2 stories. Toate atingerile sunt in `app/web/` (templates + routes + `labels.py`).
|
||||
> Verificare E2E = browser HTMX pe `http://localhost:8000/` (Playwright MCP sau `/browse`).
|
||||
> **Regula de aur**: fluxul import → commit → worker → FINALIZATA la RAR test NU are voie sa se strice,
|
||||
> iar deep-link-urile `?tab=` raman valide.
|
||||
|
||||
### US-001: Bara de status compacta cu bife + data formatata (backend format + UI)
|
||||
**Ca** operator de service **vreau** o bara de status compacta, cu font normal, cu bife clare si data
|
||||
ultimei autentificari completa **pentru ca** sa vad starea sistemului dintr-o privire, fara sa ghicesc.
|
||||
|
||||
- **Depinde de**: —
|
||||
- **Fisiere**: `app/web/labels.py` (helper format data), `app/web/routes.py` (`fragment_status`),
|
||||
`app/web/templates/_status.html`, `tests/test_web_labels.py`, `tests/test_web_status.py` (~5 fisiere)
|
||||
- **Test intai (RED)**: `test_format_data_rar` — `2026-06-18T14:30:22` (sau forma stocata in
|
||||
`worker_heartbeat.last_rar_login_ok`) → `"18.06.2026 14:30:22"`; valoare lipsa → `"—"`; format invalid
|
||||
→ fallback grijuliu (nu arunca). `tests/test_web_status.py::test_status_are_bife` — fragmentul randat
|
||||
contine bifa verde cand worker viu + RAR ok, rosie cand oprit/indisponibil.
|
||||
- **Continut**:
|
||||
- Helper pur in `labels.py` (ex. `format_data_rar(raw) -> str`) care produce `dd.mm.yyyy hh24:mi:ss`.
|
||||
- `_status.html` rescris pe **doua randuri**: rand 1 = `[bifa] Trimitere automata activa` +
|
||||
`[bifa] Legatura RAR OK` + `Ultima autentificare RAR: <data>`; rand 2 = `In asteptare: N |
|
||||
Declarate la RAR: N | Blocate: N`. Font normal (13-14px), fara `font-size:11px/12px`.
|
||||
- **Accesibilitate (design review)**: starea NU se distinge doar prin culoare. Glifa difera —
|
||||
`✓` (✓) pentru activ/OK, `✗` (✗) pentru oprit/indisponibil — plus textul difera
|
||||
(activa/oprita, functionala/indisponibila). Culoarea (verde/rosu) e redundanta, nu singurul semnal.
|
||||
- Pastreaza avertismentul "cont in asteptare de activare" (regresia reparata in 3.4) si poll-ul 15s.
|
||||
- **Acceptance criteria**:
|
||||
- [x] Data ultimei autentificari apare ca `dd.mm.yyyy hh24:mi:ss` (test pe helper, pur).
|
||||
- [x] Doua stari binare au bifa verde/rosie dupa starea reala (worker viu/mort, RAR ok/indisponibil).
|
||||
- [x] Niciun text din bara nu mai foloseste `font-size` sub 13px.
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: browser pe `/` — bara compacta, bife corecte, data formatata romaneste.
|
||||
|
||||
### US-002: Acasa devine ecranul de import (upload inline + help compact, scoatere tab Import)
|
||||
**Ca** operator **vreau** sa import direct de pe prima pagina **pentru ca** importul e ce fac cel mai des.
|
||||
|
||||
- **Depinde de**: US-001 (bara status finala deasupra)
|
||||
- **Fisiere**: `app/web/templates/dashboard.html` (lista tab-uri), `app/web/routes.py` (`_TABS_VALIDE`,
|
||||
`_render_panel`, `fragment_import`/`fragment_acasa`), `app/web/templates/_acasa.html`,
|
||||
`app/web/templates/_upload.html` (reutilizat), `tests/test_web_dashboard.py` (~5 fisiere)
|
||||
- **Test intai (RED)**: `test_acasa_contine_upload` — fragmentul `/_fragments/acasa` contine formularul de
|
||||
upload (`hx-post` catre ruta de import existenta); `test_tab_import_redirect` — `?tab=import` nu mai e
|
||||
tab separat (redirect la `acasa` sau eticheta absenta din tab-bar), iar deep-link-ul nu da 404.
|
||||
- **Continut**:
|
||||
- `_acasa.html`: caseta de upload (din `_upload.html`) ca prim element; sub ea, "primii pasi" pe un
|
||||
**singur rand** compact (`o Cont RAR o Cheie API * Import`) + un rand de ajutor cu link-uri mici
|
||||
(Ghid / Coada / Mapari). Upload-ul porneste stepper-ul existent (target `#import-section`).
|
||||
- **Ierarhie (design review)**: upload-ul e vizual DOMINANT (titlu clar + caseta mare); checklist-ul
|
||||
"primii pasi" si ajutorul sunt subordonate (font mai mic, sub upload), nu concureaza cu el. Prima
|
||||
pagina are un singur centru de greutate: importa un fisier.
|
||||
- Scoate `("import", ...)` din lista de tab-uri din `dashboard.html`; pastreaza `?tab=import` valid
|
||||
(redirect la `acasa`) pentru orice URL salvat. Stepper-ul, mapare-coloane, preview, commit raman.
|
||||
- "Incarca alt fisier" din stepper trimite inapoi la Acasa (nu la un tab inexistent).
|
||||
- **Acceptance criteria**:
|
||||
- [x] Pe `/` (tab implicit Acasa) caseta de upload e vizibila fara click suplimentar.
|
||||
- [x] Tab-bar-ul nu mai are "Import"; `?tab=import` nu da 404 (redirect/echivalent la Acasa).
|
||||
- [x] Fluxul upload → mapare coloane → preview → commit functioneaza neschimbat (target/csrf intacte).
|
||||
- [x] Link-urile de ajutor + checklist incap pe randuri compacte, font normal.
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: browser — incarc un fisier de pe Acasa, parcurg stepper-ul pana la commit.
|
||||
|
||||
### US-003: Prezentare detalii comanda din payload (backend pur, testat)
|
||||
**Ca** operator **vreau** ca fiecare trimitere sa-mi spuna despre ce vehicul/operatie e vorba
|
||||
**pentru ca** acum vad doar `idPrezentare` si nu stiu ce comanda am trimis si ce nu.
|
||||
|
||||
- **Depinde de**: —
|
||||
- **Fisiere**: `app/payload_view.py` (nou, helper PARTAJAT web+API), `app/api/v1/router.py` (refactor
|
||||
mic: `GET /v1/prezentari` foloseste acelasi helper, nu o copie), `tests/test_payload_view.py`
|
||||
(~3 fisiere). Decizie eng review (DRY): un singur modul de extragere payload→campuri afisabile, ca sa
|
||||
nu diverge intre canalul web si cel API (router.py:247-264 face azi extragerea sa proprie).
|
||||
- **Test intai (RED)**: `test_detalii_din_payload` — dat un `payload_json` (text JSON: `vin`, `numar`/
|
||||
`numarInmatriculare`, `odometru_final`, `data_prestatie`, `cod_prestatie`/`cod_op_service`/`denumire`),
|
||||
helperul intoarce un dict de prezentare `{vehicul_nr, vin_scurt, operatie, data_prestatie, odometru,
|
||||
cod}`; `test_payload_partial` — campuri lipsa → `"—"`/gol fara exceptie; `test_payload_invalid` →
|
||||
fallback grijuliu (nu arunca); `test_payload_coercion_excel` — odometru `"123.0"`/numeric si VIN
|
||||
non-string (coercion Excel) afisate curat (`str()` defensiv), chei API vs import (`numar` vs
|
||||
`numarInmatriculare`) ambele rezolvate.
|
||||
- **Continut**: functie pura care primeste randul submission (sau `payload_json`) si produce campurile
|
||||
de afisat. `vin_scurt` = forma trunchiata pentru tabel (VIN integral ramane in detaliu). Citeste cheile
|
||||
defensiv (canalele API si import pot diferi usor; `payload_json` e plaintext — vezi `router.py`).
|
||||
- **Acceptance criteria**:
|
||||
- [x] Helper pur (fara DB, fara request), 100% acoperit de teste pe cazurile plin/partial/invalid.
|
||||
- [x] Nu arunca niciodata pe payload malformat (degradeaza la `—`).
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: — (acoperit de US-004).
|
||||
|
||||
### US-004: "Coada" → "Trimiteri" — tabel lizibil + detaliu la click
|
||||
**Ca** operator **vreau** un tabel de trimiteri pe care il inteleg, cu detaliile comenzii
|
||||
**pentru ca** "HTTP RAR"/"sent"/"Motiv gol" nu-mi spun nimic si nu stiu la ce comanda se refera randul.
|
||||
|
||||
- **Depinde de**: US-003 (prezentare payload), US-001 (`labels.py` pt stare umana)
|
||||
- **Fisiere**: `app/web/templates/_coada.html` (titlu + tab label), `app/web/templates/_submissions.html`,
|
||||
`app/web/templates/dashboard.html` (eticheta tab "Coada"→"Trimiteri", `tab_id` ramane `coada`),
|
||||
`app/web/routes.py` (`fragment_submissions` imbogatit + ruta detaliu `/_fragments/trimitere/{id}`),
|
||||
`app/web/templates/_trimitere_detaliu.html` (nou), `tests/test_web_submissions.py` (~6 fisiere)
|
||||
- **Test intai (RED)**: `test_submissions_coloane_umane` — tabelul contine antete RO (Stare, Vehicul,
|
||||
Operatie, Data prestatie, Nr. prezentare RAR, Motiv) si **nu** mai contine "HTTP RAR" ca antet principal,
|
||||
nici status brut englezesc afisat ca atare (folose `labels.eticheta_stare`); `test_detaliu_trimitere` —
|
||||
`/_fragments/trimitere/{id}` intoarce detaliul complet scoped pe cont (404 cross-account).
|
||||
- **Continut**:
|
||||
- Eticheta tab "Coada" → **"Trimiteri"** (pastreaza `tab_id="coada"` ca deep-link `?tab=coada` sa ramana
|
||||
valid). Titlu sectiune "Trimiteri catre RAR".
|
||||
- `_submissions.html`: coloane = `#`, **Stare** (`eticheta_stare` text RO + pill culoare), **Vehicul**
|
||||
(nr + VIN scurt, din US-003), **Operatie**, **Data prestatie**, **Nr. prezentare RAR** (`id_prezentare`
|
||||
sau `—`), **Actualizat**, **Motiv** (text uman; pt `needs_data` → ex. "lipsa odometru"). Codul HTTP
|
||||
tehnic NU mai e coloana principala — coboara in detaliu.
|
||||
`query`-ul include `payload_json` (pt US-003) pe langa campurile actuale.
|
||||
- Click pe rand → `hx-get="/_fragments/trimitere/{id}"` → `_trimitere_detaliu.html` cu toate campurile
|
||||
(vehicul integral, operatie+cod, odometru, data, stare, `rar_status_code`, `rar_error` integral,
|
||||
retry, timestamps). Scoped pe contul sesiunii.
|
||||
- **Detaliul se randeaza intr-un PANOU DEDICAT** (ex. `#trimitere-detaliu` sub/langa tabel), NU inline
|
||||
in randul din tabel. Motiv (CEO review, Finding #1): `_submissions.html` are `hx-trigger="every 10s"`;
|
||||
un expand inline ar fi sters de poll-ul de refresh. Panoul dedicat nu e prins de poll.
|
||||
- **Vizibilitate (design review)**: la deschidere, panoul trebuie sa fie evident — scroll-to panou
|
||||
si/sau evidentiere a randului selectat in tabel. Altfel pare ca "nu s-a intamplat nimic" (panoul
|
||||
apare sub fold).
|
||||
- **Acceptance criteria**:
|
||||
- [x] Antetele coloanelor sunt in romana; starea afisata e text uman (nu "sent").
|
||||
- [x] Fiecare rand arata vehicul + operatie + data (din payload), nu doar `idPrezentare`.
|
||||
- [x] Click pe rand deschide detaliul complet, scoped pe cont (404 la id-ul altui cont).
|
||||
- [x] Motivul pentru `needs_data`/`error` apare in coloana Motiv (nu gol cand exista `rar_error`).
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: browser — tab Trimiteri, identific un rand dupa vehicul, deschid detaliul.
|
||||
|
||||
### US-005: Listare + editare mapari operatii salvate (backend)
|
||||
**Ca** operator **vreau** sa vad si sa pot schimba maparile operatie→cod pe care le-am salvat
|
||||
**pentru ca** acum, dupa ce mapez si trimit, ele dispar din ecran si par pierdute.
|
||||
|
||||
- **Depinde de**: —
|
||||
- **Fisiere**: `app/web/routes.py` (query `operations_mapping` + rute edit/delete),
|
||||
`tests/test_web_mapari_salvate.py` (~2 fisiere)
|
||||
- **Test intai (RED)**: `test_lista_mapari_salvate` — intoarce randurile `operations_mapping` ale contului
|
||||
cu `nume_prestatie` jonctionat din nomenclator; `test_editeaza_mapare_salvata` — POST schimba
|
||||
`cod_prestatie`/`auto_send` doar pe contul propriu (cross-account interzis), verifica `cod_prestatie`
|
||||
exista in nomenclator (ca la `/mapari` actual); `test_sterge_mapare_salvata` — DELETE scoped pe cont.
|
||||
- **Continut**: functie de listare scoped pe cont + rute web `POST /mapari/salvate` (update) si
|
||||
`POST /mapari/salvate/sterge` (delete) cu CSRF + PRG/HTMX swap. **Nu** schimba logica de rezolvare din
|
||||
`mapping.py`; doar CRUD pe tabela existenta. **Re-rezolvare obligatorie** (promovata din optional la CEO
|
||||
review): la schimbarea unui cod, submission-urile blocate (`needs_mapping`) pe acel `cod_op_service` se
|
||||
re-rezolva automat, reutilizand helperul de re-rezolvare existent (`reresolve_account`/echivalent) —
|
||||
fara cod nou de rezolvare. Inchide pain-ul "am mapat dar nu vad efectul".
|
||||
- **Acceptance criteria**:
|
||||
- [x] Listarea e scoped pe contul sesiunii (fara leak cross-account — vezi C6 din 3.3a).
|
||||
- [x] Editarea respinge cod inexistent in nomenclator si cont strain.
|
||||
- [x] Stergerea afecteaza doar contul propriu.
|
||||
- [x] La editarea unui cod, submission-urile `needs_mapping` pe acel `cod_op_service` se deblocheaza
|
||||
automat (test: rand blocat → editez maparea → randul trece din `needs_mapping`).
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: — (acoperit de US-006).
|
||||
|
||||
### US-006: Listare + editare/stergere formate de coloane salvate (backend)
|
||||
**Ca** operator **vreau** sa vad formatele de import memorate si sa le pot edita/sterge
|
||||
**pentru ca** nu stiu ce coloane sunt retinute si ce se intampla cand vin cu un fisier cu alte coloane.
|
||||
|
||||
- **Depinde de**: —
|
||||
- **Fisiere**: `app/web/routes.py` (query `column_mappings` + rute edit/delete),
|
||||
`tests/test_web_formate_coloane.py` (~2 fisiere)
|
||||
- **Test intai (RED)**: `test_lista_formate_coloane` — intoarce randurile `column_mappings` ale contului
|
||||
cu coloanele (din `signature_coloane`/`json_mapare`), `format_data` si un contor de utilizare (cate
|
||||
`import_batches`/submissions folosesc acea semnatura — best-effort, sau omis daca nu e ieftin);
|
||||
`test_sterge_format_coloane` — DELETE scoped pe cont; `test_editeaza_format_coloane` — POST schimba
|
||||
`json_mapare`/`format_data` pentru o semnatura, scoped pe cont.
|
||||
- **Continut**: listare scoped pe cont + rute `POST /formate-coloane/...` (edit/delete) cu CSRF.
|
||||
Comportament documentat (nu cod nou de import): un fisier cu **alte coloane** = semnatura noua = format
|
||||
nou separat (`UNIQUE (account_id, signature_coloane)`), nu suprascrie; **acelasi antet** = maparea
|
||||
retinuta se reaplica automat la urmatorul import (comportament existent din 2.4).
|
||||
- **Acceptance criteria**:
|
||||
- [x] Listarea arata coloanele fiecarui format + format data, scoped pe cont.
|
||||
- [x] Stergerea/editarea afecteaza doar contul propriu (fara leak cross-account).
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: — (acoperit de US-006 UI in US-007).
|
||||
|
||||
### US-007: Pagina "Mapari" cu trei sectiuni (UI)
|
||||
**Ca** operator **vreau** o singura pagina "Mapari" cu tot ce tine de mapari
|
||||
**pentru ca** sa rezolv ce e blocat si sa-mi gestionez maparile salvate (operatii + coloane) intr-un loc.
|
||||
|
||||
- **Depinde de**: US-005, US-006
|
||||
- **Fisiere**: `app/web/templates/_mapari.html`, `app/web/routes.py` (`_render_mapari`/`fragment_mapari`
|
||||
imbogatit sa treaca cele 3 seturi de date), `app/web/templates/_cont.html` (ramane doar cheie API +
|
||||
creds RAR — fara mapari), `tests/test_web_mapari_ui.py` (~4 fisiere)
|
||||
- **Test intai (RED)**: `test_mapari_trei_sectiuni` — fragmentul `/_fragments/mapari` contine cele trei
|
||||
sectiuni (De rezolvat / Mapari operatii salvate / Formate de coloane salvate); `test_cont_fara_mapari`
|
||||
— `/_fragments/cont` nu mai contine sectiuni de mapari.
|
||||
- **Continut**:
|
||||
- `_mapari.html` reorganizat pe 3 sectiuni: (1) **De rezolvat** = `pending_unmapped` (ca acum), (2)
|
||||
**Mapari operatii salvate** = lista din US-005, fiecare cu select cod RAR + checkbox auto-send + buton
|
||||
sterge (HTMX swap pe `#mapari-section`), (3) **Formate de coloane salvate** = lista din US-006, fiecare
|
||||
cu coloanele afisate + format data + actiuni edit/sterge. Empty states prietenoase per sectiune.
|
||||
- `_cont.html`: confirma ca nu contine mapari (cheie API + creds RAR doar).
|
||||
- **Acceptance criteria**:
|
||||
- [x] "Mapari" arata cele 3 sectiuni; sectiunile goale au mesaj prietenos, nu lipsesc tacit.
|
||||
- [x] Salvarea/stergerea reincarca sectiunea via HTMX fara reload de pagina.
|
||||
- [x] "Cont" nu mai contine mapari.
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: browser — vad maparile salvate dupa ce am mapat+trimis; editez un cod; vad formatul
|
||||
de coloane al fisierului importat.
|
||||
|
||||
### US-008: Feedback clar pentru randuri respinse la import (lipsa odometru / needs_data)
|
||||
**Ca** operator **vreau** sa inteleg de ce un rand nu s-a importat **pentru ca** am incarcat un fisier
|
||||
fara odometru si pur si simplu "nu s-a importat", fara explicatie.
|
||||
|
||||
- **Depinde de**: US-002 (importul pe Acasa)
|
||||
- **Fisiere**: `app/web/templates/_preview_import.html`, `app/web/routes.py` (mesaj preview, daca e cazul),
|
||||
`tests/test_web_preview_motive.py` (~3 fisiere)
|
||||
- **Test intai (RED)**: `test_preview_arata_motiv_needs_data` — un rand `needs_data` din lipsa odometru
|
||||
apare in preview cu motiv explicit ("lipsa odometru" / mesajul de validare), nu doar numarat la "blocate".
|
||||
- **Continut**: preview-ul de import (cele 6 stari deja existente din 2.5) afiseaza pentru randurile
|
||||
`needs_data`/`needs_review` **motivul** (din validare/`error`), ca operatorul sa stie ce sa corecteze.
|
||||
Reutilizeaza mesajele de validare existente (`validation.py`); fara reguli noi de validare.
|
||||
- **Acceptance criteria**:
|
||||
- [x] Un rand fara odometru apare explicit cu motivul, nu doar in contorul "blocate".
|
||||
- [x] Contoarele preview (ok/needs_data/...) raman corecte.
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: browser — import un fisier fara odometru, vad in preview de ce e blocat randul.
|
||||
|
||||
### US-009: Filtrare/cautare in "Trimiteri" (stare / vehicul / data prestatie)
|
||||
**Ca** operator cu sute de trimiteri **vreau** sa filtrez lista **pentru ca** sa gasesc rapid o comanda
|
||||
sau toate cele blocate, fara sa derulez tot.
|
||||
|
||||
- **Depinde de**: US-003, US-004
|
||||
- **Fisiere**: `app/web/routes.py` (`fragment_submissions` accepta parametri de filtru),
|
||||
`app/web/templates/_coada.html` (controale filtru), `app/web/templates/_submissions.html`,
|
||||
`app/schema.sql` (index pe `submissions(account_id, status)` daca lipseste — exista deja
|
||||
`idx_submissions_account_status`, deci probabil zero schimbari), `tests/test_web_filtrare.py` (~4 fisiere)
|
||||
- **Test intai (RED)**: `test_filtru_stare` — `?status=needs_data` intoarce doar acele randuri (scoped pe
|
||||
cont); `test_filtru_vehicul` — cautare text pe nr/VIN (case-insensitive); `test_filtru_data` — interval
|
||||
`data_prestatie`. Toate scoped pe cont, fara leak.
|
||||
- **Continut**: controale HTMX (select stare + input text vehicul + interval data) care reincarca
|
||||
`/_fragments/submissions` cu query string; filtrarea pe vehicul/data se face dupa parsarea
|
||||
`payload_json` (text JSON), filtrarea pe stare in SQL (foloseste indexul existent). Pastreaza poll-ul
|
||||
(poll-ul re-trimite filtrul curent). Empty state "nimic pe filtrul curent" **+ buton „sterge filtrele"**
|
||||
(design review) cand exista un filtru activ.
|
||||
- **Acceptance criteria**:
|
||||
- [x] Filtrele combina (stare + vehicul + data) si raman aplicate la refresh-ul de 10s.
|
||||
- [x] Filtrarea e scoped pe cont (fara leak cross-account).
|
||||
- [x] Nu necesita coloane/tabele noi (cel mult confirma indexul existent).
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: browser — filtrez dupa "blocate" si dupa un numar de inmatriculare.
|
||||
|
||||
### US-010: Corectie inline pentru randuri ne-trimise blocate (needs_data)
|
||||
**Ca** operator **vreau** sa completez un camp lipsa (ex. odometru) direct pe randul blocat si sa-l
|
||||
re-trimit **pentru ca** acum trebuie sa refac tot fisierul de import doar pentru un camp.
|
||||
|
||||
- **Depinde de**: US-003, US-004
|
||||
- **Fisiere**: `app/web/routes.py` (ruta `POST /trimitere/{id}/corecteaza`),
|
||||
`app/web/templates/_trimitere_detaliu.html` (form de corectie pe randurile `needs_data`/`needs_mapping`),
|
||||
`tests/test_web_corectie.py` (~3 fisiere)
|
||||
- **Test intai (RED)**: `test_corectie_needs_data` — un rand `needs_data` din lipsa odometru: completez
|
||||
odometru → re-validare (`validation.py`) → status `queued`, payload actualizat, idempotency recalculata;
|
||||
`test_corectie_sent_interzis` — un rand `sent`/`FINALIZATA` NU poate fi editat (403/refuz, read-only);
|
||||
`test_corectie_coliziune_idempotency` — daca noua cheie coincide cu alt submission existent, corectia se
|
||||
opreste cu mesaj „exista deja o trimitere identica (rand #N)", fara IntegrityError/500 si fara duplicat;
|
||||
`test_corectie_cont_strain` — interzis cross-account.
|
||||
- **Continut**: pe panoul de detaliu (US-004), pentru randuri `needs_data`/`needs_mapping`, un mini-form
|
||||
cu campurile relevante; la submit re-valideaza prin `validation.py` (fara reguli noi), reconstruieste
|
||||
`payload_json` + recalculeaza `idempotency_key` (canonicalize → build_key, ca la enqueue), seteaza
|
||||
status `queued` si `next_attempt_at=now`. **Nu** atinge worker-ul / masina de stari (doar re-enqueue).
|
||||
Randurile `sent`/`FINALIZATA` raman read-only (gard explicit).
|
||||
- **Coliziune idempotency** (decizie eng review): INAINTE de UPDATE, verifica daca noua `idempotency_key`
|
||||
exista deja pe alt submission al contului; daca da, opreste corectia si afiseaza „exista deja o
|
||||
trimitere identica (rand #N)" cu link la acel rand. Fara 500, fara duplicat. (UNIQUE pe
|
||||
`idempotency_key` ar arunca IntegrityError altfel.)
|
||||
- **Loc + eroare (design review)**: formul de corectie traieste IN panoul de detaliu (US-004), nu intr-o
|
||||
sectiune separata. Eroarea de validare se afiseaza clar pe campul invalid (nu un mesaj generic sus).
|
||||
- **Acceptance criteria**:
|
||||
- [x] Un rand `needs_data` corectat valid trece in `queued` cu payload + idempotency actualizate.
|
||||
- [x] Randurile `sent`/`FINALIZATA` nu pot fi editate (gard testat).
|
||||
- [x] Coliziunea de idempotency e prinsa si comunicata clar (rand-duplicat identificat), fara 500.
|
||||
- [x] Corectia respinge date inca invalide (mesaj de validare) si conturi straine.
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: browser — corectez odometru pe un rand blocat, il vad trecand in asteptare;
|
||||
cu worker `--send` pe RAR test, ajunge FINALIZATA.
|
||||
|
||||
### US-011: Badge cu contoare pe tab-uri (atentionari)
|
||||
**Ca** operator **vreau** sa vad pe eticheta tab-ului cate lucruri ma asteapta **pentru ca** sa stiu
|
||||
unde sa intervin fara sa deschid fiecare tab.
|
||||
|
||||
- **Depinde de**: US-007 (mapari), US-004 (trimiteri)
|
||||
- **Fisiere**: `app/web/templates/dashboard.html` (badge pe eticheta tab), `app/web/routes.py`
|
||||
(contoarele deja calculate in `fragment_status`/panel context, transmise la tab-bar),
|
||||
`tests/test_web_badge.py` (~3 fisiere)
|
||||
- **Test intai (RED)**: `test_badge_mapari` — cand exista operatii `needs_mapping`, eticheta "Mapari"
|
||||
poarta un numar; `test_badge_trimiteri_blocate` — cand exista randuri blocate, "Trimiteri" poarta
|
||||
marcaj; `test_badge_zero_ascuns` — fara nimic de rezolvat, niciun badge.
|
||||
- **Continut**: numar mic pe eticheta tab-ului, alimentat din contoarele existente (needs_mapping pt
|
||||
Mapari, blocate pt Trimiteri). Pur prezentare; reutilizeaza ce calculeaza deja status-ul. Accesibil
|
||||
(text in `aria-label`, nu doar culoare).
|
||||
- **Acceptance criteria**:
|
||||
- [x] Badge apare doar cand contorul > 0; dispare la zero.
|
||||
- [x] Numarul e corect si scoped pe cont.
|
||||
- [x] `aria-label`-ul tab-ului include sensul badge-ului (nu doar pastila colorata).
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: browser — cu o operatie nemapata, "Mapari" arata "(1)"; dupa rezolvare, dispare.
|
||||
|
||||
## 4. Riscuri
|
||||
|
||||
- **Scoaterea tab-ului "Import" rupe deep-link-uri/teste** (`?tab=import`, link-uri din `_acasa.html`,
|
||||
"Incarca alt fisier" din stepper). Mitigare: `?tab=import` → redirect la `acasa`; grep dupa toate
|
||||
referintele `tab=import`/`/_fragments/import` inainte de stergere; test dedicat (US-002).
|
||||
- **Citirea `payload_json` pentru detalii** depinde de forma payload-ului, care difera usor intre canalul
|
||||
API si import. Mitigare: helper pur defensiv cu fallback `—` (US-003), testat pe ambele forme; nu se
|
||||
bazeaza pe o cheie obligatorie.
|
||||
- **Leak cross-account** pe noile listari/editari (mapari salvate, formate coloane, detaliu trimitere).
|
||||
Mitigare: toate scoped pe contul sesiunii cu regula NULL→1 (C6/3.3a), test cross-account per ruta noua,
|
||||
re-folosind pattern-ul `account_scope_clause` (3.2).
|
||||
- **Afisare PII (VIN/nr) pe ecran** in Trimiteri. Acceptabil: e proprietatea contului autentificat, scoped
|
||||
pe sesiune; nu se expune in loguri si nu apare in raspunsuri 422 (handler existent in `main.py`).
|
||||
- **Aglomerare "Mapari" cu 3 sectiuni**. Mitigare: empty states + colaps vizual cand o sectiune e goala.
|
||||
|
||||
## 5. Intrebari deschise
|
||||
|
||||
> Se rezolva cu utilizatorul ÎNAINTE de executie (poarta de aprobare PRD).
|
||||
|
||||
- ~~Detaliul trimiterii: expand inline sau panou?~~ **REZOLVAT (CEO review):** panou dedicat
|
||||
`#trimitere-detaliu`, nu inline — altfel poll-ul de 10s sterge expand-ul (vezi US-004).
|
||||
- "Editare" format de coloane: redeschidem editorul de mapare campuri (`_mapcoloane.html`) prefiltrat pe
|
||||
semnatura salvata, sau permitem doar stergere + re-import? (propunere MVP: stergere + vizualizare; edit
|
||||
de campuri = nice-to-have daca incape in story).
|
||||
- Contor de utilizare pe formate de coloane: il afisam (cost de query) sau il omitem in v1? (propunere:
|
||||
omitem daca nu e ieftin — nu e critic).
|
||||
|
||||
## 6. Valuri de executie (graful de dependente)
|
||||
|
||||
```
|
||||
Val 1: [US-001] [US-003] [US-005] [US-006] ← fara dependente, fisiere distincte → paralel (max 3-4)
|
||||
Val 2: [US-002] [US-004] ← US-002←US-001 ; US-004←US-003+US-001
|
||||
Val 3: [US-007] [US-008] [US-009] [US-010] ← US-007←US-005+US-006 ; US-008←US-002 ;
|
||||
US-009←US-003+US-004 ; US-010←US-003+US-004
|
||||
Val 4: [US-011] ← US-011←US-007+US-004 (contoare din ambele)
|
||||
```
|
||||
|
||||
> Atentie la fisiere partajate intre valuri: `routes.py`, `dashboard.html`, `_status.html`,
|
||||
> `_submissions.html` si `_trimitere_detaliu.html` (US-004/009/010) sunt atinse de mai multe stories —
|
||||
> secventiaza-le sau worktree + merge de catre lead (vezi anti-pattern ROADMAP). In special US-009 si
|
||||
> US-010 ating ambele acelasi panou de detaliu/tabel — ruleaza-le secvential, nu in paralel.
|
||||
|
||||
## 7. Review-uri de plan
|
||||
|
||||
### CEO review (2026-06-19) — SELECTIVE EXPANSION
|
||||
|
||||
- **Abordare aleasa**: A (cele 8 stories complete), cu expansiuni cherry-pick acceptate.
|
||||
- **Expansiuni acceptate** (adaugate ca stories): US-009 filtrare/cautare Trimiteri, US-010 corectie
|
||||
inline pentru `needs_data`, US-011 badge contoare pe tab-uri.
|
||||
- **Finding promovat**: re-rezolvarea automata la editarea unei mapari salvate, din "optional" in scope
|
||||
obligatoriu (US-005).
|
||||
- **Finding de robustete inchis**: detaliul Trimiteri merge in **panou dedicat**, nu inline — altfel
|
||||
poll-ul de 10s din `_submissions.html` ar sterge expand-ul (US-004 + §5).
|
||||
- **Schimbare de scope fata de draft**: editarea de continut e acum permisa pentru randuri ne-trimise
|
||||
blocate (US-010); `sent`/`FINALIZATA` raman read-only. Vezi §2 non-goals actualizat.
|
||||
|
||||
### Eng review (2026-06-19)
|
||||
|
||||
- **Step 0**: 11 stories e mult, dar e o livrabila UI sparta in stories atomice TDD (conventia
|
||||
proiectului), nu overbuild. Risc real = contentia pe fisiere partajate, nu numarul. Scope confirmat.
|
||||
- **Decizie idempotency (US-010)**: corectia detecteaza coliziunea de `idempotency_key` INAINTE de UPDATE
|
||||
si o comunica clar (rand-duplicat identificat), fara 500/duplicat.
|
||||
- **Decizie DRY (US-003)**: helper partajat `app/payload_view.py` folosit si de web si de
|
||||
`GET /v1/prezentari` — o singura sursa de extragere payload→campuri.
|
||||
- **Aserțiune adaugata**: test ca `submissions.payload_json` e plaintext (US-003) — daca vreodata se
|
||||
cripteaza, testul cade si stim sa adaptam.
|
||||
- **Plafon perf notat (US-009)**: filtrarea pe vehicul/data parseaza payload in Python per rand; OK la
|
||||
scara actuala, `json_extract()` daca devine necesar. Nu blocheaza.
|
||||
- **Secventiere intarita**: US-004 (schelet tabel+panou) → apoi US-009 si US-010, strict secvential pe
|
||||
`_submissions.html`/`_trimitere_detaliu.html`/`fragment_submissions`. NU paraleliza valul 3 pe ele.
|
||||
|
||||
### Design review (2026-06-19) — rating 7/10 → 9/10 dupa fixuri
|
||||
|
||||
Layout-urile au fost alese vizual cu utilizatorul (mockup-uri ASCII in AskUserQuestion). Patru cerinte
|
||||
de design adaugate ca AC:
|
||||
|
||||
- **Accesibilitate bife (US-001)**: glife distincte (✓/✗) + text, nu doar culoare (daltonism).
|
||||
- **Ierarhie Acasa (US-002)**: upload-ul vizual dominant; checklist + ajutor subordonate.
|
||||
- **Vizibilitate panou (US-004)**: scroll-to / evidentiere rand la deschiderea detaliului.
|
||||
- **Stari de eroare (US-009/010)**: „sterge filtrele" + empty state; eroare de validare pe campul invalid,
|
||||
in panoul de detaliu (decizie utilizator).
|
||||
|
||||
## 8. Raport review-uri de plan (consolidat)
|
||||
|
||||
| Review | Data | Rezultat | Decizii cheie |
|
||||
|---|---|---|---|
|
||||
| CEO (SELECTIVE EXPANSION) | 2026-06-19 | Aprobat cu expansiuni | Abordare A; +US-009/010/011; re-rezolvare US-005 obligatorie; detaliu in panou (nu inline) |
|
||||
| Eng | 2026-06-19 | Aprobat | Coliziune idempotency US-010 detectata pre-UPDATE; helper partajat `payload_view.py`; secventiere val 3 |
|
||||
| Design | 2026-06-19 | Aprobat (9/10) | Bife accesibile; ierarhie Acasa; vizibilitate panou; stari de eroare |
|
||||
|
||||
**Scope final**: 11 stories (US-001…US-011), in 4 valuri. Backend de trimitere (worker, masina de stari,
|
||||
reconciliere, idempotenta ca logica) neatins; singura mutatie de date noua = corectia US-010 (re-enqueue
|
||||
randuri ne-trimise) + posibil un index (US-009). **DECIZII NEREZOLVATE**: niciuna care sa blocheze
|
||||
executia — raman 2 intrebari de finete in §5 (editare format coloane: stergere+vizualizare in MVP;
|
||||
contor utilizare formate: omis daca nu e ieftin), ambele cu propunere si fara impact pe arhitectura.
|
||||
|
||||
Urmatorul pas (ROADMAP §5): `**Stare**: aprobat` → EXECUTE (TDD pe valuri). Poarta umana: aprobarea PRD.
|
||||
|
||||
---
|
||||
|
||||
## Raport VERIFY
|
||||
|
||||
> Completat de subagentul verificator (context curat, ROADMAP §5.6) — 2026-06-19.
|
||||
|
||||
**Verdict global: PASS.** Toate cele 11 stories verificate prin cod + teste. Regresia de aur intacta.
|
||||
Non-Goals respectate.
|
||||
|
||||
- **Suita**: `python3 -m pytest -q` → **483 passed** (de la 434 baseline 3.4; +49 teste noi). Verde.
|
||||
- **PASS/FAIL per story**: toate US-001…US-011 PASS. Dovezi (verificator context curat):
|
||||
- US-001: `format_data_rar` (dd.mm.yyyy hh24:mi:ss, lipsa→"—", invalid→fallback fara exceptie);
|
||||
bife accesibile cu glife `✓`/`✗` + text distinct + culoare redundanta; fara font-size <13px.
|
||||
- US-002: Acasa include upload (`hx-post="/_import/upload"`), tab Import scos, `?tab=import`→Acasa (fara 404).
|
||||
- US-003: `app/payload_view.py` pur/defensiv, refolosit de `GET /v1/prezentari` (DRY), payload_json brut neexpus.
|
||||
- US-004: coloane RO, stare umana, detaliu in panou dedicat `#trimitere-detaliu` (nu inline), 404 cross-account.
|
||||
- US-005/006: CRUD `operations_mapping`/`column_mappings` scoped pe cont, re-rezolvare la edit cod.
|
||||
- US-007: `_fragments/mapari` 3 sectiuni cu empty states; `_fragments/cont` fara mapari.
|
||||
- US-008: preview arata mesajul de validare pentru randuri needs_data.
|
||||
- US-009: filtre stare(SQL)/vehicul/data scoped pe cont; empty state cu "sterge filtrele".
|
||||
- US-010: corectie needs_data→queued cu payload+idempotency recalculate; sent read-only (403);
|
||||
coliziune idempotency prinsa pre-UPDATE; cross-account 404.
|
||||
- US-011: badge Mapari(needs_mapping)/Trimiteri(blocate), ascuns la zero, scoped, aria-label cu sens.
|
||||
- **Regresia de aur**: flux import→commit→coada + canal API `POST /v1/prezentari` intacte
|
||||
(`test_import_ui/e2e`, `test_api`, `test_web_tabs`); deep-link-uri `?tab=` valide.
|
||||
- **Non-Goals**: `git diff --stat` confirma `app/worker/`, `app/idempotency.py`, `app/mapping.py`,
|
||||
`app/schema.sql` NEATINSE; CHECK status pastreaza cele 6 stari; niciun endpoint `/v1/*` nou.
|
||||
- **E2E live RAR**: neprobat in sesiune (fara credentiale RAR live, identic cu 3.4) — recomandata
|
||||
probare manuala `./start.sh test both --send` + browser pe `http://localhost:8000/`.
|
||||
|
||||
### Findings `/code-review` (high) — reparate inainte de inchidere
|
||||
|
||||
1. **Corectie + needs_mapping (sever)**: ruta `POST /trimitere/{id}/corecteaza` re-punea in `queued`
|
||||
fara re-rezolvarea prestatiilor → un cod nemapat putea ajunge la RAR cu `codPrestatie: null`
|
||||
(FINALIZATA ireversibil). **Fix**: re-ruleaza `resolve_prestatii` + `has_no_auto_send` (ca
|
||||
`reresolve_account`); cod nemapat ramane `needs_mapping`. Test: `test_corectie_needs_mapping_nu_ajunge_in_coada`.
|
||||
2. **Filtru dupa LIMIT 200**: cautarea pe vehicul/data rata randuri mai vechi de 200. **Fix**: fara LIMIT
|
||||
in SQL cand filtrul text/data e activ, plafonare dupa filtrare.
|
||||
3. **Coliziune idempotency non-atomica (cursa TOCTOU → 500)**: **Fix**: `try/except sqlite3.IntegrityError`
|
||||
in jurul UPDATE-ului `queued`, mesaj prietenos in loc de 500.
|
||||
4. **Comparatie data non-ISO gresita la filtru**: **Fix**: `_is_iso_date` — compar doar date ISO YYYY-MM-DD.
|
||||
|
||||
Findings de cleanup (scope-clause hand-coded in `_status_counts`/`_get_acasa_context`, `_render_panel_*`
|
||||
duplicate) sunt preexistente din 3.4, in afara scope-ului 3.5 — neatinse intentionat.
|
||||
Reference in New Issue
Block a user