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:
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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.
|
||||||
|
---
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user