""" 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", "Vezi detaliul randului; se reincearca automat sau necesita corectie.", "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)" 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] # --------------------------------------------------------------------------- # Constante auxiliare (microcopy fix, fara logica) # --------------------------------------------------------------------------- ETICHETA_ULTIMA_AUTENTIFICARE_RAR = "Ultima autentificare la RAR"