feat(ux): import compact + preview format Trimiteri + navigatie + scoatere auto_send (5.11)
8 stories TDD (echipa Sonnet, lead orchestreaza). US-001 scoate hold-ul auto_send din mapare (has_no_auto_send->False, simbol pastrat; cod rezolvat->queued). US-002 scoate bifa auto_send din UI. US-003 preview pas 3 in format .tabel-trimiteri (STARI_PREVIEW + nota_umana_preview, fara repr Python; view-model prez). US-004 filtre layout/stil ca referinta + buton Custom. US-005 navigatie Trimiteri/Mapari sub contoare pe toate paginile. US-006 import <details> nativ colapsabil. US-007 post-commit reveal (OOB _coada/_status + HX-Trigger). US-008 auto-refresh dupa actiuni (nudge eliminat). VERIFY context curat PASS (8/8). /code-review high: 3 buguri reparate (tab nav la self-refresh, pill Custom valori stale, nota_umana_preview precedenta needs_mapping). 934 passed, 1 skipped. Backend trimitere + schema NEATINSE. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,12 +29,14 @@ from ..payload_view import prezentare_din_payload
|
||||
from ..web.csrf import get_csrf_token, verify_csrf
|
||||
from .labels import (
|
||||
ETICHETA_ULTIMA_AUTENTIFICARE_RAR,
|
||||
STARI_PREVIEW,
|
||||
eticheta_rar,
|
||||
eticheta_scurta,
|
||||
eticheta_stare,
|
||||
eticheta_worker,
|
||||
format_data_rar,
|
||||
motiv_uman,
|
||||
nota_umana_preview,
|
||||
parse_erori,
|
||||
)
|
||||
from ..web.session import require_login
|
||||
@@ -593,6 +595,40 @@ def _pills_categorii(counts: dict[str, int]) -> list[dict]:
|
||||
]
|
||||
|
||||
|
||||
def _build_status_ctx(request: Request, conn, account_id: int, *, oob: bool = False, tab_activ: str = "acasa") -> dict:
|
||||
"""Construieste dictionarul de context pentru _status.html.
|
||||
|
||||
Accepta o conexiune deja deschisa (nu deschide alta). Folosit de fragment_status
|
||||
si de web_confirma_import (OOB swap dupa commit).
|
||||
"""
|
||||
counts = _status_counts(conn, account_id)
|
||||
hb = read_heartbeat(conn)
|
||||
worker_alive = _worker_alive(hb)
|
||||
rar_state = _rar_state(hb, worker_alive)
|
||||
worker_lbl = eticheta_worker(worker_alive)
|
||||
rar_ok = rar_state == "ok"
|
||||
rar_lbl = eticheta_rar("ok" if rar_ok else rar_state)
|
||||
blocate_total = sum(counts.get(s, 0) for s in _BLOCKED)
|
||||
return {
|
||||
"request": request,
|
||||
"worker_lbl": worker_lbl,
|
||||
"rar_lbl": rar_lbl,
|
||||
"worker_ok": worker_alive,
|
||||
"rar_ok": rar_ok,
|
||||
"eticheta_ultima_auth": ETICHETA_ULTIMA_AUTENTIFICARE_RAR,
|
||||
"last_login": format_data_rar(hb["last_rar_login_ok"] if hb else None),
|
||||
"counts_queued": counts.get("queued", 0),
|
||||
"counts_sent": counts.get("sent", 0),
|
||||
"blocate_total": blocate_total,
|
||||
"blocate_defalcat": _blocate_defalcat(counts),
|
||||
"pills_categorii": _pills_categorii(counts),
|
||||
"account_active": _account_active(conn, account_id),
|
||||
"tab_activ": tab_activ,
|
||||
"mapari_badge": counts.get("needs_mapping", 0),
|
||||
"oob": oob,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/_fragments/status", response_class=HTMLResponse)
|
||||
def fragment_status(request: Request) -> HTMLResponse:
|
||||
"""Bara de status persistenta cu etichete umane.
|
||||
@@ -604,34 +640,9 @@ def fragment_status(request: Request) -> HTMLResponse:
|
||||
account_id = require_login(request)
|
||||
conn = get_connection()
|
||||
try:
|
||||
counts = _status_counts(conn, account_id)
|
||||
hb = read_heartbeat(conn)
|
||||
worker_alive = _worker_alive(hb)
|
||||
rar_state = _rar_state(hb, worker_alive)
|
||||
|
||||
# Etichete umane pre-calculate (nu logica in template)
|
||||
worker_lbl = eticheta_worker(worker_alive)
|
||||
# eticheta_rar accepta "ok" sau orice alt string -> indisponibil/necunoscut
|
||||
rar_ok = rar_state == "ok"
|
||||
rar_lbl = eticheta_rar("ok" if rar_ok else rar_state)
|
||||
blocate_total = sum(counts.get(s, 0) for s in _BLOCKED)
|
||||
|
||||
return templates.TemplateResponse("_status.html", {
|
||||
"request": request,
|
||||
"worker_lbl": worker_lbl,
|
||||
"rar_lbl": rar_lbl,
|
||||
# Stari binare pentru bife accesibile: glifa + culoare
|
||||
"worker_ok": worker_alive,
|
||||
"rar_ok": rar_ok,
|
||||
"eticheta_ultima_auth": ETICHETA_ULTIMA_AUTENTIFICARE_RAR,
|
||||
"last_login": format_data_rar(hb["last_rar_login_ok"] if hb else None),
|
||||
"counts_queued": counts.get("queued", 0),
|
||||
"counts_sent": counts.get("sent", 0),
|
||||
"blocate_total": blocate_total,
|
||||
"blocate_defalcat": _blocate_defalcat(counts),
|
||||
"pills_categorii": _pills_categorii(counts),
|
||||
"account_active": _account_active(conn, account_id),
|
||||
})
|
||||
tab_activ = request.query_params.get("tab", "acasa")
|
||||
ctx = _build_status_ctx(request, conn, account_id, tab_activ=tab_activ)
|
||||
return templates.TemplateResponse("_status.html", ctx)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@@ -1163,21 +1174,7 @@ async def post_corectie_trimitere(request: Request, submission_id: int) -> HTMLR
|
||||
message="Lipseste inca un cod RAR — alege-l mai jos sau in tab-ul Mapari."),
|
||||
)
|
||||
|
||||
if has_no_auto_send(resolved, mapping_meta):
|
||||
conn.execute(
|
||||
"UPDATE submissions SET status='needs_mapping', payload_json=?, rar_error=?, "
|
||||
"updated_at=datetime('now') WHERE id=?",
|
||||
(payload_json,
|
||||
json.dumps({"auto_send": "cod mapat cu auto_send=0; review manual inainte de trimitere"},
|
||||
ensure_ascii=False),
|
||||
row["id"]),
|
||||
)
|
||||
row2 = _fetch_submission_scoped(conn, account_id, submission_id)
|
||||
return templates.TemplateResponse(
|
||||
"_trimitere_detaliu.html",
|
||||
_detaliu_ctx(request, row2, error=True,
|
||||
message="Cod cu auto-send oprit — confirma manual din tab-ul Mapari."),
|
||||
)
|
||||
# US-001 (PRD 5.11): ramura auto_send eliminata din corectie.
|
||||
|
||||
errors = validate_prezentare(content)
|
||||
if errors:
|
||||
@@ -2025,6 +2022,29 @@ def _web_compute_preview(
|
||||
except Exception:
|
||||
conn.execute("ROLLBACK")
|
||||
|
||||
# Enrichment UI: adauga campuri pre-computate necesare template-ului.
|
||||
# Toate consumatorii (preview complet, rand single via _preview_one_row)
|
||||
# obtin automat campurile adaugate aici.
|
||||
for row in preview_rows:
|
||||
# view-model prez (vehicul/operatie/cod RAR) — prezentare_din_payload
|
||||
# accepta dict direct (nu e nevoie de serializare/deserializare JSON).
|
||||
row["prez"] = prezentare_din_payload(row["resolved"])
|
||||
# Eticheta umana + clasa CSS pentru pill — din STARI_PREVIEW, nu STARI_SUBMISSION
|
||||
# (eticheta_stare ridica KeyError pe ok/already_sent/duplicate_in_file).
|
||||
_etq, _css = STARI_PREVIEW.get(
|
||||
row["resolved_status"],
|
||||
(row["resolved_status"], f"s-{row['resolved_status']}"),
|
||||
)
|
||||
row["stare_eticheta"] = _etq
|
||||
row["stare_css"] = _css
|
||||
# Nota umana formatata — errors e lista Python, NU JSON string;
|
||||
# nota_umana_preview o trateaza corect (fara repr Python brut in Note).
|
||||
row["nota_umana"] = nota_umana_preview(
|
||||
row["resolved_status"],
|
||||
row.get("errors") or [],
|
||||
row.get("flags") or [],
|
||||
)
|
||||
|
||||
nomenclator = load_nomenclator(conn)
|
||||
return {
|
||||
"rows": preview_rows,
|
||||
@@ -2750,18 +2770,33 @@ async def web_confirma_import(
|
||||
(n_enqueued, import_id),
|
||||
)
|
||||
|
||||
# Succes → bara de upload slim cu mesaj de confirmare. are_trimiteri=True:
|
||||
# contul tocmai a pus randuri in coada -> bara ramane slim si dezvaluie
|
||||
# sectiunea "Trimiterile tale" de pe Acasa.
|
||||
# Succes → bara de upload slim cu mesaj de confirmare + OOB swap al
|
||||
# #trimiteri-section (injecteaza _coada.html cu lista proaspata) +
|
||||
# header HX-Trigger: trimiteriChanged (declanseza reincarcarea automata).
|
||||
toctou_msg = f" ({len(toctou)} coliziuni TOCTOU excluse)" if toctou else ""
|
||||
return templates.TemplateResponse("_upload.html", _ctx(
|
||||
request,
|
||||
are_trimiteri=True,
|
||||
message=(
|
||||
f"S-au pus in coada {n_enqueued} prezentari{toctou_msg}. "
|
||||
f"Procesarea incepe in cateva secunde — vezi mai jos, in Trimiterile tale."
|
||||
),
|
||||
))
|
||||
succes_msg = (
|
||||
f"S-au pus in coada {n_enqueued} prezentari{toctou_msg}. "
|
||||
f"Procesarea incepe in cateva secunde — vezi mai jos, in Trimiterile tale."
|
||||
)
|
||||
|
||||
# Calculeaza contextele (necesita conn deschis) inainte de finally.
|
||||
acasa_ctx = _get_acasa_context(request, conn, account_id)
|
||||
acasa_ctx["status_filtru"] = ""
|
||||
acasa_ctx["oob"] = True # adauga hx-swap-oob="outerHTML" la <section>
|
||||
|
||||
status_ctx = _build_status_ctx(request, conn, account_id, oob=True)
|
||||
|
||||
# Randeaza imediat (conn inca deschis — query-urile s-au facut mai sus).
|
||||
upload_html = templates.get_template("_upload.html").render(
|
||||
_ctx(request, are_trimiteri=True, message=succes_msg)
|
||||
)
|
||||
coada_html = templates.get_template("_coada.html").render(acasa_ctx)
|
||||
status_html = templates.get_template("_status.html").render(status_ctx)
|
||||
|
||||
return HTMLResponse(
|
||||
content=upload_html + "\n" + coada_html + "\n" + status_html,
|
||||
headers={"HX-Trigger": "trimiteriChanged"},
|
||||
)
|
||||
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
Reference in New Issue
Block a user