feat(5.9): US-001 - eticheta umana scurta pe rand + cod brut pentru modal (R1)

- _submission_row_view expune eticheta_problema (motiv || eticheta_scurta), gol pe queued/sending/sent, fara decoder nou (R1 DRY)
- parse_erori expune cheia `cod` (cod brut catalog) pe ramurile imbogatite, pentru derivare in modal
- 5 teste US-001 in tests/test_web_submissions.py
- gates: tests PASS (819), /review (backend) PASS

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-24 21:17:53 +00:00
parent 0ba728cab5
commit 6d10f92452
5 changed files with 137 additions and 3 deletions

View File

@@ -275,6 +275,8 @@ def parse_erori(rar_error: object) -> list[dict]:
"cauza": e.get("cauza") or e.get("message") or "", "cauza": e.get("cauza") or e.get("message") or "",
"fix": e.get("fix") or "", "fix": e.get("fix") or "",
"field": e.get("field"), "field": e.get("field"),
# Cod BRUT de catalog (ex. RAR_EROARE_SERVER) — DOAR pentru modal (US-001/R1).
"cod": e.get("cod"),
}) })
else: else:
# Forma veche: {field, message} fara cod # Forma veche: {field, message} fara cod
@@ -303,6 +305,8 @@ def parse_erori(rar_error: object) -> list[dict]:
"cauza": data.get("cauza") or "", "cauza": data.get("cauza") or "",
"fix": data.get("fix") or "", "fix": data.get("fix") or "",
"field": data.get("field"), "field": data.get("field"),
# Cod BRUT de catalog (ex. COD_NEMAPAT) — DOAR pentru modal (US-001/R1).
"cod": data.get("cod"),
}] }]
# Dict vechi: unmapped # Dict vechi: unmapped
if "unmapped" in data: if "unmapped" in data:

View File

@@ -649,9 +649,31 @@ def _is_iso_date(value: object) -> bool:
return False return False
# Stari care semnaleaza o problema ce necesita atentia operatorului. Eticheta umana
# scurta de pe rand (US-001, R1) e ne-goala DOAR pe acestea; pe queued/sending/sent e "".
_STARI_CU_PROBLEMA = ("error", "needs_data", "needs_mapping")
def _eticheta_problema(status: str, motiv: str) -> str:
"""Eticheta umana scurta a problemei pentru randul de tabel (US-001, R1).
Reutilizeaza `motiv` (motiv_uman, deja calculat in randul de view) si cade pe
`eticheta_scurta` cand motivul e gol — NU re-parseaza `rar_error` (R1: DRY, fara
al 3-lea decoder). Codul BRUT de catalog ramane doar pentru modal, nu pe rand.
Sir gol pe stari fara problema (queued/sending/sent); ne-gol pe error/needs_*.
Defensiv: motiv_uman nu arunca, iar starile cu problema au intotdeauna eticheta
scurta -> fallback-ul garanteaza un text ne-gol chiar la `rar_error` lipsa/corupt.
"""
if status not in _STARI_CU_PROBLEMA:
return ""
return motiv or eticheta_scurta(status)
def _submission_row_view(r) -> dict: def _submission_row_view(r) -> dict:
"""Imbogateste un rand de submission cu campuri afisabile umane (US-003/US-004).""" """Imbogateste un rand de submission cu campuri afisabile umane (US-003/US-004)."""
eticheta = eticheta_stare(r["status"]) eticheta = eticheta_stare(r["status"])
motiv = motiv_uman(r["status"], r["rar_error"])
return { return {
"id": r["id"], "id": r["id"],
"status": r["status"], "status": r["status"],
@@ -662,7 +684,9 @@ def _submission_row_view(r) -> dict:
"prez": prezentare_din_payload(r["payload_json"]), "prez": prezentare_din_payload(r["payload_json"]),
"id_prezentare": r["id_prezentare"], "id_prezentare": r["id_prezentare"],
"updated_at": format_data_rar(r["updated_at"]), "updated_at": format_data_rar(r["updated_at"]),
"motiv": motiv_uman(r["status"], r["rar_error"]), "motiv": motiv,
# US-001/R1: eticheta umana scurta a problemei sub pill (text, nu cod brut).
"eticheta_problema": _eticheta_problema(r["status"], motiv),
# US-011: randurile blocate (error/needs_data/needs_mapping) sunt selectabile # US-011: randurile blocate (error/needs_data/needs_mapping) sunt selectabile
# pentru stergere bulk; sent/sending/queued raman read-only (fara checkbox). # pentru stergere bulk; sent/sending/queued raman read-only (fara checkbox).
"gestionabil": r["status"] in _GESTIONABILE_WEB, "gestionabil": r["status"] in _GESTIONABILE_WEB,

View File

@@ -33,12 +33,12 @@
"dependsOn": [], "dependsOn": [],
"requiresBrowserCheck": false, "requiresBrowserCheck": false,
"requiresDesignReview": false, "requiresDesignReview": false,
"passes": false, "passes": true,
"failed": false, "failed": false,
"blocked": false, "blocked": false,
"retries": 0, "retries": 0,
"failureReason": "", "failureReason": "",
"notes": "" "notes": "Atins: app/web/routes.py (_eticheta_problema + _STARI_CU_PROBLEMA, camp eticheta_problema in _submission_row_view, motiv hoisted), app/web/labels.py (parse_erori expune `cod` brut aditiv pe ramurile imbogatite), tests/test_web_submissions.py (5 teste US-001). gates: tests PASS (819 suite, 22 fisier), /review (backend) PASS (no issues — DRY respectat, defensiv, aditiv non-regresie)."
}, },
{ {
"id": "US-003", "id": "US-003",

View File

@@ -5,3 +5,31 @@ Branch: ralph/5.9-ux-corectie-modal-mobil
Source PRD: docs/prd/prd-5.9-ux-corectie-modal-mobil.md Source PRD: docs/prd/prd-5.9-ux-corectie-modal-mobil.md
Note: PRD APROBAT 2026-06-24 cu revizii obligatorii R1-R12 (raport AUTOPLAN). R1-R12 au prioritate unde difera de AC original — sunt deja incorporate in acceptance criteria. Note: PRD APROBAT 2026-06-24 cu revizii obligatorii R1-R12 (raport AUTOPLAN). R1-R12 au prioritate unde difera de AC original — sunt deja incorporate in acceptance criteria.
--- ---
## Iteratie: 2026-06-24
### Story implementat: US-001 - Eticheta umana scurta pe randul de afisare + cod brut disponibil pentru modal (R1) (tags: backend)
### Status: Complete
### Gates rulate:
- Typecheck: SKIP (techStack.commands.typecheck gol)
- Lint: SKIP (techStack.commands.lint gol)
- Tests: PASS (tests/test_web_submissions.py 22; suita completa 819 passed, 1 deselected)
- /review (backend): PASS — no issues (DRY respectat, defensiv, aditiv non-regresie)
### Ce s-a schimbat:
- app/web/routes.py: helper `_eticheta_problema(status, motiv)` + constanta `_STARI_CU_PROBLEMA`;
`_submission_row_view` hoisteaza `motiv` intr-o variabila si adauga campul `eticheta_problema`
(text uman scurt: motiv || eticheta_scurta; gol pe queued/sending/sent).
- app/web/labels.py: `parse_erori` expune cheia `cod` (cod brut de catalog) pe ramurile imbogatite
(lista + dict), pentru ca modalul (US-004) sa-l deriveze via parse_erori(...)[0]['cod'] — fara decoder nou (R1 DRY).
- tests/test_web_submissions.py: 5 teste US-001 (sub_pill R1 rename, error/needs_mapping prezent, gol pe ok, defensiv JSON invalid).
### Learnings:
- `parse_erori` traieste in app/web/labels.py (nu app/errors.py cum sugera AC#4) — `app/errors.py` defineste CATALOG-ul de coduri si `eroare()`.
- `_GESTIONABILE_WEB` == aceleasi 3 stari cu problema; am definit `_STARI_CU_PROBLEMA` separat pentru claritate semantica (gestionabil != are-problema conceptual).
- Eticheta de pe rand e adaugata DOAR in view-model; randarea sub pill apartine US-002 (de aceea testele sunt unitare pe _submission_row_view).
### Next:
- US-003 (modal, ui, requiresDesignReview) — independent, priority 15.
- US-002 depinde de US-001 (acum done) + US-003.
---

View File

@@ -195,6 +195,84 @@ def test_detaliu_trimitere(client):
assert "99001" in html # nr prezentare RAR assert "99001" in html # nr prezentare RAR
# ---------------------------------------------------------------------------
# US-001 (R1): eticheta umana scurta a problemei pe randul de tabel.
# Teste pure pe _submission_row_view — randarea sub pill e US-002.
# ---------------------------------------------------------------------------
def _fake_row(status: str, *, rar_error=None, id_prezentare=None, payload=None) -> dict:
"""Rand minimal compatibil cu _submission_row_view (acces prin chei)."""
return {
"id": 1,
"status": status,
"id_prezentare": id_prezentare,
"updated_at": "2026-06-24T10:00:00",
"rar_error": rar_error,
"payload_json": json.dumps(payload or {
"vin": "WVWZZZ1JZXW000777",
"nr_inmatriculare": "B777ZZZ",
"data_prestatie": "2026-06-18",
"odometru_final": "55000",
"prestatii": [{"cod_prestatie": "OE-2", "denumire": "Revizie"}],
}),
}
def test_eticheta_umana_sub_pill():
"""R1: randul expune `eticheta_problema` umana, reutilizand motiv (nu un nou decoder)."""
from app.web.routes import _submission_row_view
v = _submission_row_view(_fake_row(
"needs_data",
rar_error=json.dumps([{"field": "odometru_final", "message": "lipsa odometru"}]),
))
# Eticheta = text uman scurt, reutilizeaza motiv (ne-gol)
assert v["eticheta_problema"], "eticheta_problema ar trebui ne-goala pe needs_data"
assert v["eticheta_problema"] == v["motiv"]
# NU expune cod brut de catalog pe rand
assert "COD_" not in v["eticheta_problema"]
assert "RAR_EROARE" not in v["eticheta_problema"]
def test_eticheta_problema_prezenta_pe_error():
"""Eticheta ne-goala pe error chiar fara rar_error (fallback pe eticheta_scurta)."""
from app.web.routes import _submission_row_view
# error cu mesaj RAR
v_err = _submission_row_view(_fake_row(
"error", rar_error=json.dumps({"cod": "RAR_EROARE_SERVER", "problema": "Eroare server RAR"}),
))
assert v_err["eticheta_problema"] == "Eroare server RAR"
# error fara rar_error -> fallback ne-gol (eticheta scurta)
v_fb = _submission_row_view(_fake_row("error", rar_error=None))
assert v_fb["eticheta_problema"] == "Eroare"
def test_eticheta_problema_prezenta_pe_needs_mapping():
"""Eticheta ne-goala pe needs_mapping (stare cu problema)."""
from app.web.routes import _submission_row_view
v = _submission_row_view(_fake_row(
"needs_mapping",
rar_error=json.dumps({"unmapped": [{"cod_op_service": "OP-9", "denumire": "X"}]}),
))
assert v["eticheta_problema"]
assert "OP-9" in v["eticheta_problema"]
def test_eticheta_problema_goala_pe_rand_ok():
"""Eticheta este sir gol pe stari fara problema (queued/sending/sent)."""
from app.web.routes import _submission_row_view
for status in ("queued", "sending", "sent"):
v = _submission_row_view(_fake_row(status))
assert v["eticheta_problema"] == "", f"{status} ar trebui sa aiba eticheta goala"
def test_eticheta_problema_defensiva_json_invalid():
"""rar_error JSON corupt pe stare cu problema -> nu ridica, eticheta ne-goala."""
from app.web.routes import _submission_row_view
v = _submission_row_view(_fake_row("error", rar_error="{invalid json[[["))
assert isinstance(v["eticheta_problema"], str)
assert v["eticheta_problema"] # fallback garanteaza text ne-gol
def test_detaliu_trimitere_404_cross_account(client): def test_detaliu_trimitere_404_cross_account(client):
"""Detaliul altui cont -> 404 (fara leak).""" """Detaliul altui cont -> 404 (fara leak)."""
acct1 = _create_account_user("d1@test.com", name="C1") acct1 = _create_account_user("d1@test.com", name="C1")