""" labels.py — traducere stari tehnice in text uman + clasa CSS (US-001, PRD 3.4). Functii pure: fara DB, fara request. Usor de testat unitar si de importat in template-uri. Sursa de adevar pentru texte: tabelul din PRD 3.4 §3 US-001. """ import json from datetime import datetime from typing import Tuple # --------------------------------------------------------------------------- # Tipul returnat: (text_principal, subtext_tooltip, css_class) # --------------------------------------------------------------------------- Eticheta = Tuple[str, str, str] # --------------------------------------------------------------------------- # Etichete stari submissions # Clasele CSS corespund celor definite in base.html: # s-queued (accent/albastru), s-sending (warn/galben), s-sent (ok/verde), # s-error, s-needs_data, s-needs_mapping (err/rosu). # --------------------------------------------------------------------------- STARI_SUBMISSION: dict[str, Eticheta] = { "queued": ( "In asteptare sa fie trimise", "", "s-queued", ), "sending": ( "Se trimite acum", "", "s-sending", ), "sent": ( "Declarate la RAR (finalizate)", "Confirmate cu numar de prezentare; nu se mai pot modifica.", "s-sent", ), "needs_mapping": ( "Lipseste codul prestatiei", "Alege codul RAR in tab-ul Mapari.", "s-needs_mapping", ), "needs_data": ( "Date incomplete (respinse de RAR)", "Corecteaza randul si reimporta.", "s-needs_data", ), "error": ( "Eroare la trimitere", "Trimiterea a esuat si nu se mai reincearca automat. Vezi detaliul randului; " "daca tine de credentialele RAR, corecteaza-le in Cont.", "s-error", ), } def eticheta_stare(status: str) -> Eticheta: """ Returneaza (text, subtext, css_class) pentru o stare de submission. Arunca KeyError daca starea nu este mapata — intentionat, ca sa prinda stari noi adaugate in schema fara mapare corespunzatoare. """ try: return STARI_SUBMISSION[status] except KeyError: raise KeyError( f"Starea de submission {status!r} nu are eticheta umana in labels.py. " "Adauga-o in STARI_SUBMISSION." ) # --------------------------------------------------------------------------- # Etichete worker (viu / mort) # --------------------------------------------------------------------------- def eticheta_worker(viu: bool) -> Eticheta: """ Returneaza (text, subtext, css_class) pentru starea worker-ului. viu=True => "Trimitere automata: activa" (clasa s-sent / verde) viu=False => "Trimitere automata: oprita" (clasa s-error / rosu) """ if viu: return ( "Trimitere automata: activa", "Sistemul verifica coada si trimite la RAR la fiecare cateva secunde.", "s-sent", ) return ( "Trimitere automata: oprita", "Nimic nu pleaca spre RAR pana reporneste. Anunta administratorul.", "s-error", ) # --------------------------------------------------------------------------- # Etichete conexiune RAR (ok / indisponibil) # --------------------------------------------------------------------------- def eticheta_rar(stare: str) -> Eticheta: """ Returneaza (text, subtext, css_class) pentru starea conexiunii cu RAR. stare="ok" => "Legatura cu RAR: functionala" (s-sent / verde) stare="indisponibil" => "Legatura cu RAR: indisponibila" (s-error / rosu) """ if stare == "ok": return ( "Legatura cu RAR: functionala", "Portalul AUTOPASS raspunde.", "s-sent", ) return ( "Legatura cu RAR: indisponibila", "Portalul RAR nu raspunde acum; coada se reia automat cand revine.", "s-error", ) # --------------------------------------------------------------------------- # Format data RAR (US-001, PRD 3.5) # --------------------------------------------------------------------------- def format_data_rar(raw: object) -> str: """Formateaza un timestamp ISO ca `dd.mm.yyyy hh24:mi:ss` (ora romaneasca). - Valoare lipsa (None / "") -> "—". - ISO valid (cu sau fara timezone / 'Z' / microsecunde) -> data formatata, fara fractiuni de secunda. - Format invalid -> fallback grijuliu: intoarce stringul brut (nu arunca), ca operatorul sa vada totusi ceva, nu o pagina rupta. """ if raw is None: return "—" s = str(raw).strip() if not s: return "—" iso = s.replace("Z", "+00:00") if s.endswith("Z") else s try: dt = datetime.fromisoformat(iso) except (ValueError, TypeError): return s return dt.strftime("%d.%m.%Y %H:%M:%S") # --------------------------------------------------------------------------- # Motiv uman din rar_error (US-004, PRD 3.5) # --------------------------------------------------------------------------- def motiv_uman(status: str, rar_error: object) -> str: """Transforma `rar_error` (JSON tehnic) intr-un motiv lizibil pentru coloana Motiv. Formele intalnite (vezi router.py / mapping.py): - validare continut: list[{field, message}] -> mesajele concatenate. - operatie nemapata: {"unmapped": [{cod_op_service, denumire}]}. - auto-send oprit: {"auto_send": "..."}. - eroare RAR: text simplu sau dict generic. Fara rar_error -> "". Nu arunca niciodata (degradeaza la text brut trunchiat). """ if not rar_error: return "" raw = rar_error if isinstance(rar_error, str) else str(rar_error) try: data = json.loads(raw) except (ValueError, TypeError): return raw[:160] if isinstance(data, dict): if "unmapped" in data: ops = data.get("unmapped") or [] nume = ", ".join( (o.get("cod_op_service") or "") for o in ops if isinstance(o, dict) ).strip(", ") return f"Cod RAR lipsa pentru: {nume}" if nume else "Cod RAR lipsa" if "auto_send" in data: return "Necesita confirmare manuala (auto-send oprit pentru cod)" if "problema" in data: return str(data.get("problema") or "")[:200] parti = [f"{k}: {v}" for k, v in data.items()] return "; ".join(parti)[:200] if isinstance(data, list): msgs: list[str] = [] for e in data: if isinstance(e, dict): msgs.append( str(e.get("message") or e.get("msg") or "; ".join(str(v) for v in e.values())) ) else: msgs.append(str(e)) return "; ".join(m for m in msgs if m)[:200] return str(data)[:160] # --------------------------------------------------------------------------- # parse_erori — transforma rar_error in lista 3-niveluri (US-006, PRD 5.4) # --------------------------------------------------------------------------- def parse_erori(rar_error: object) -> list[dict]: """Transforma `rar_error` (JSON stocat) intr-o lista de erori 3-niveluri. Fiecare element al listei are cheile: problema, cauza, fix, field (sau None). Functie PURA — nu arunca niciodata exceptii; degradeaza gratios pe orice forma. Forme recunoscute: - None / "" / falsy -> lista goala [] - array imbogatit (au cod sau problema) -> un element per eroare - dict cu cod specific -> 1 element cu cele 3 niveluri din dict - dict fara cod (forma veche: unmapped / auto_send) -> 1 element cu problema din context - lista cu {field, message} fara cod -> degradare: problema=message, cauza/fix="" - string plain -> 1 element cu problema=text, cauza/fix="" - JSON corupt -> 1 element cu problema=text brut, cauza/fix="" """ if not rar_error: return [] raw = rar_error if isinstance(rar_error, str) else str(rar_error) # Incercare parsare JSON try: data = json.loads(raw) except (ValueError, TypeError): # String plain sau JSON corupt: degradare gratuoasa return [{"problema": raw[:200], "cauza": "", "fix": "", "field": None}] # --- Forma: array de erori --- if isinstance(data, list): rezultat = [] for e in data: if not isinstance(e, dict): rezultat.append({"problema": str(e)[:200], "cauza": "", "fix": "", "field": None}) continue # Eroare imbogatita (are cod sau problema) if e.get("cod") or e.get("problema"): rezultat.append({ "problema": e.get("problema") or e.get("cod") or "", "cauza": e.get("cauza") or e.get("message") or "", "fix": e.get("fix") or "", "field": e.get("field"), }) else: # Forma veche: {field, message} fara cod msg = str(e.get("message") or e.get("msg") or "; ".join(str(v) for v in e.values())) elem = { "problema": msg[:200], "cauza": "", "fix": "", "field": e.get("field"), } # Filtreaza elementele complet goale (problema/cauza/fix toate vide) if not ( elem["problema"].strip() == "" and elem["cauza"].strip() == "" and elem["fix"].strip() == "" ): rezultat.append(elem) return rezultat # --- Forma: dict --- if isinstance(data, dict): # Dict imbogatit cu cod explicit if data.get("cod") or data.get("problema"): return [{ "problema": data.get("problema") or data.get("cod") or "", "cauza": data.get("cauza") or "", "fix": data.get("fix") or "", "field": data.get("field"), }] # Dict vechi: unmapped if "unmapped" in data: ops = data.get("unmapped") or [] coduri = ", ".join( (o.get("cod_op_service") or "") for o in ops if isinstance(o, dict) ).strip(", ") problema = f"Cod RAR lipsa pentru: {coduri}" if coduri else "Cod RAR lipsa" return [{"problema": problema, "cauza": "", "fix": "", "field": None}] # Dict vechi: auto_send if "auto_send" in data: return [{"problema": "Necesita confirmare manuala (auto-send oprit pentru cod)", "cauza": "", "fix": "", "field": None}] # Dict generic necunoscut parti = "; ".join(f"{k}: {v}" for k, v in data.items()) if not parti.strip(): return [] return [{"problema": parti[:200], "cauza": "", "fix": "", "field": None}] # Scalar (nr, bool, etc.) return [{"problema": str(data)[:200], "cauza": "", "fix": "", "field": None}] # --------------------------------------------------------------------------- # Constante auxiliare (microcopy fix, fara logica) # --------------------------------------------------------------------------- ETICHETA_ULTIMA_AUTENTIFICARE_RAR = "Ultima autentificare la RAR"