diff --git a/app/payload_view.py b/app/payload_view.py index 06aafa2..cf64781 100644 --- a/app/payload_view.py +++ b/app/payload_view.py @@ -59,6 +59,19 @@ def _vin_scurt(vin: str) -> str: return "…" + vin[-6:] +def _coduri_rar(prestatii: Any) -> list[str]: + """Codurile RAR (cod_prestatie) din toate prestatiile: ordonate, dedup.""" + coduri: list[str] = [] + if isinstance(prestatii, list): + for item in prestatii: + if not isinstance(item, dict): + continue + cod = _clean_cod_rar(item.get("cod_prestatie")) + if cod and cod not in coduri: + coduri.append(cod) + return coduri + + def _prima_prestatie(prestatii: Any) -> dict[str, Any]: """Primul item de prestatie ca dict, defensiv (lista goala/non-dict -> {}).""" if isinstance(prestatii, list): @@ -83,10 +96,11 @@ def _payload_dict(payload: str | dict | None) -> dict[str, Any]: return {} -def prezentare_din_payload(payload: str | dict | None) -> dict[str, str]: +def prezentare_din_payload(payload: str | dict | None) -> dict[str, Any]: """Campuri afisabile dintr-un payload de submission. - Intoarce un dict cu chei stabile (toate string-uri, EMPTY cand lipsesc): + Intoarce un dict cu chei stabile (string-uri, EMPTY cand lipsesc; exceptie + `coduri_rar` = list[str]): vehicul_nr, vin, vin_scurt, operatie, data_prestatie, odometru, cod. `operatie` = denumire prestatiei daca exista, altfel codul; `cod` = codul RAR @@ -138,6 +152,9 @@ def prezentare_din_payload(payload: str | dict | None) -> dict[str, str]: "odometru": odo or EMPTY, "cod": cod or EMPTY, "cod_rar": cod_rar or EMPTY, + # Lista ordonata, fara duplicate, a codurilor RAR din TOATE prestatiile + # (goala cand nimic nu e mapat). Randul compact afiseaza primul + "+N". + "coduri_rar": _coduri_rar(data.get("prestatii")), # Chei cu conventie goala "" (nu EMPTY) — vezi comentariu de mai sus "op_service_cod": op_service_cod, "op_service_denumire": op_service_denumire, diff --git a/app/web/labels.py b/app/web/labels.py index bc03819..2045e44 100644 --- a/app/web/labels.py +++ b/app/web/labels.py @@ -359,6 +359,31 @@ def parse_erori(rar_error: object) -> list[dict]: return [{"problema": str(data)[:200], "cauza": "", "fix": "", "field": None}] +# --------------------------------------------------------------------------- +# eroare_bruta — textul compact al erorii pentru sub-linia randului de tabel +# --------------------------------------------------------------------------- + +# Stari cu sub-linie de eroare pe rand. needs_mapping e EXCLUS intentionat: +# "nemapat" e deja vizibil in coloana Cod RAR, sub-linia ar fi redundanta. +_STARI_CU_EROARE_BRUTA = ("error", "needs_data") + + +def eroare_bruta(status: str, rar_error: object) -> str: + """Textul brut, compact, al primei erori — pentru sub-linia randului. + + Prefera cauza tehnica (mesajul serverului RAR, ex. ORA-12899) si cade pe + problema. Sir gol in afara starilor error/needs_data. Nu arunca niciodata + (reuse parse_erori, degradare gratioasa). + """ + if status not in _STARI_CU_EROARE_BRUTA: + return "" + erori = parse_erori(rar_error) + if not erori: + return "" + e = erori[0] + return (str(e.get("cauza") or "").strip() or str(e.get("problema") or "").strip())[:200] + + # --------------------------------------------------------------------------- # Etichete stari preview import (vocabular DIFERIT de starile de submission) # diff --git a/app/web/routes.py b/app/web/routes.py index b821cf9..b081d71 100644 --- a/app/web/routes.py +++ b/app/web/routes.py @@ -32,6 +32,7 @@ from ..web.csrf import get_csrf_token, verify_csrf from .labels import ( ETICHETA_ULTIMA_AUTENTIFICARE_RAR, STARI_PREVIEW, + eroare_bruta, eticheta_env, eticheta_rar, eticheta_scurta, @@ -1131,6 +1132,8 @@ def _submission_row_view(r) -> dict: "motiv": motiv, # eticheta umana scurta a problemei sub pill (text, nu cod brut). "eticheta_problema": _eticheta_problema(r["status"], motiv), + # textul brut compact al erorii (mesaj RAR/validare) — sub-linia randului. + "eroare_bruta": eroare_bruta(r["status"], r["rar_error"]), # randurile blocate (error/needs_data/needs_mapping) sunt selectabile pentru # stergere bulk; sent/sending/queued raman read-only (fara checkbox). "gestionabil": r["status"] in _GESTIONABILE_WEB, @@ -2397,9 +2400,19 @@ async def post_form_chips(request: Request) -> HTMLResponse: }) action = str(form.get("chips_action") or "").strip() - chips_extra_error = False # T-C1/T-E4 (5.16): semnal pentru add_extra esuat + # Orice adaugare are un efect vizibil: mesaj de refuz (chips_error) sau + # confirmare + chip evidentiat (chips_ok / chips_added_cod). Fara no-op silentios. + chips_error = "" + chips_ok = "" + chips_added_cod = "" conn = get_connection() + + def _cod_valid(cod: str) -> bool: + return conn.execute( + "SELECT 1 FROM nomenclator_rar WHERE cod_prestatie=?", (cod,) + ).fetchone() is not None + try: if action == "add": # Adauga cod la operatia specificata prin chips_add_op_index @@ -2408,44 +2421,37 @@ async def post_form_chips(request: Request) -> HTMLResponse: except (ValueError, TypeError): op_idx = 0 add_cod = str(form.get(f"chips_add_cod_{op_idx}") or "").strip().upper() - if add_cod and 0 <= op_idx < len(chips): - exists = conn.execute( - "SELECT 1 FROM nomenclator_rar WHERE cod_prestatie=?", (add_cod,) - ).fetchone() - if exists: - chips[op_idx]["cod_prestatie"] = add_cod - - elif action == "add_flat": - # Adauga cod nou in lista plata (fara op_service) - add_cod_flat = str(form.get("chips_add_cod_flat") or "").strip().upper() - if add_cod_flat: - exists = conn.execute( - "SELECT 1 FROM nomenclator_rar WHERE cod_prestatie=?", (add_cod_flat,) - ).fetchone() - if exists: - chips.append({"cod_prestatie": add_cod_flat, "cod_op_service": "", "denumire": ""}) - - elif action == "add_extra": - # Adauga cod RAR liber (extra, fara op_service) in modul operatii. - # Refoloseste `chips_add_cod_flat` (acelasi select; dedup per-item pastrat). - # Select gol sau cod invalid → chips_extra_error = True (semnal vizibil). - add_cod_extra = str(form.get("chips_add_cod_flat") or "").strip().upper() - if add_cod_extra: - exists = conn.execute( - "SELECT 1 FROM nomenclator_rar WHERE cod_prestatie=?", (add_cod_extra,) - ).fetchone() - if exists: - # Dedup per-item (E4): nu adauga un chip extra identic deja existent - existing_pairs = { - (c.get("cod_op_service", ""), c.get("cod_prestatie", "")) - for c in chips - } - if ("", add_cod_extra) not in existing_pairs: - chips.append({"cod_prestatie": add_cod_extra, "cod_op_service": "", "denumire": ""}) - else: - chips_extra_error = True # cod necunoscut in nomenclator + if not add_cod: + chips_error = "Selecteaza un cod RAR din lista inainte de a adauga." + elif not (0 <= op_idx < len(chips)): + chips_error = "Operatia nu mai exista in formular — reincarca editarea." + elif not _cod_valid(add_cod): + chips_error = f"Cod necunoscut in nomenclator: {add_cod}." else: - chips_extra_error = True # select gol + chips[op_idx]["cod_prestatie"] = add_cod + chips_ok = f"{add_cod} adaugat." + chips_added_cod = add_cod + + elif action in ("add_flat", "add_extra"): + # Adauga cod RAR liber (fara op_service): add_flat = mod plat, + # add_extra = mod operatii. Acelasi select (chips_add_cod_flat), + # aceeasi validare si acelasi dedup per-item (E4). + add_cod_flat = str(form.get("chips_add_cod_flat") or "").strip().upper() + if not add_cod_flat: + chips_error = "Selecteaza un cod RAR din lista inainte de a adauga." + elif not _cod_valid(add_cod_flat): + chips_error = f"Cod necunoscut in nomenclator: {add_cod_flat}." + else: + existing_pairs = { + (c.get("cod_op_service", ""), c.get("cod_prestatie", "")) + for c in chips + } + if ("", add_cod_flat) in existing_pairs: + chips_error = f"{add_cod_flat} este deja in lista — nu a fost adaugat inca o data." + else: + chips.append({"cod_prestatie": add_cod_flat, "cod_op_service": "", "denumire": ""}) + chips_ok = f"{add_cod_flat} adaugat." + chips_added_cod = add_cod_flat elif action == "remove": # Sterge codul de la indexul dat (lasa op_service intact -> operatie ramane nemapata) @@ -2480,7 +2486,9 @@ async def post_form_chips(request: Request) -> HTMLResponse: "has_r_odo": has_r_odo, "form_chips_url": "/form-chips", "chips_section_id": "chips-section", - "chips_extra_error": chips_extra_error, + "chips_error": chips_error, + "chips_ok": chips_ok, + "chips_added_cod": chips_added_cod, }) diff --git a/app/web/templates/_chips_prestatii.html b/app/web/templates/_chips_prestatii.html index 9d0db05..bd574df 100644 --- a/app/web/templates/_chips_prestatii.html +++ b/app/web/templates/_chips_prestatii.html @@ -46,6 +46,7 @@ {% if chip.cod_op_service %} {% set _is_warn = chip.cod_prestatie in ('R-ODO', 'I-ODO') %} {% set _nemapat = not chip.cod_prestatie %} + {% set _e_nou = (chips_added_cod is defined) and chips_added_cod and chip.cod_prestatie == chips_added_cod %}
{{ chip.cod_op_service }} @@ -59,11 +60,12 @@ {% if chip.cod_prestatie %} {# ===== Operatie mapata: chip cu × ===== #} - {{ chip.cod_prestatie }}