Implementeaza PRD 5.6 complet (14 stories, TDD). Doua axe:
Lifecycle trimiteri blocate (Val A):
- submissions_admin.py: sterge/repune scoped (404 cross-account inaintea lui 409 stare)
- reactivare dedup peste `error` cu CAS (WHERE id=? AND status='error'), creds noi in
submissions + accounts.rar_creds_enc; worker invalideaza sesiunea RAR la creds proaspete
(JWT 30h vechi nu mai trimite cu parola gresita); camp aditiv `reactivated:true`
- retentie randuri blocate 30z; purge_expired exclude queued/sending; purge_after curatat
la reactivare/requeue
- API DELETE /v1/prezentari/{id} + /repune (200+JSON); UI butoane + bulk + banner actionabil
Observabilitate:
- app/observ.py log_event: dublu canal app_events (DB) + RotatingFileHandler per-proces,
redactare creds/PII la scriere (redact_pii/vin_partial)
- request_id middleware + X-Request-ID pe toate raspunsurile
- handler global excepții -> 500 envelope 6-chei + request_id (traceback doar in jurnal)
- audit cerere API (api_prezentari/api_auth_esuat) + audit worker (rar_login/tranzitii)
- tab "Jurnal" filtrabil scoped (non-admin doar contul sau); retentie jurnal 90z
- rar_error expus in GET /v1/prezentari/{id} (recovery observabil)
pytest -q: 741 passed, 0 failed. Docs: PRD raport VERIFY, contract endpointuri noi, ROADMAP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
212 lines
7.0 KiB
Python
212 lines
7.0 KiB
Python
"""Catalog central de erori AutoPass (PRD 5.4).
|
|
|
|
Singura sursa de adevar care mapeaza fiecare cod de eroare la (problema, fix),
|
|
cu un helper care construieste obiectul de eroare pe 3 niveluri:
|
|
- nivel 1 (tehnic): `cod` + `cauza` — ce s-a intamplat exact
|
|
- nivel 2 (utilizator): `problema` — descriere scurta, inteligibila
|
|
- nivel 3 (actiune): `fix` — ce trebuie facut pentru a remedia
|
|
|
|
Modul PUR — fara import DB sau HTTP.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CATALOG
|
|
# cheie = cod (string), valoare = {"problema": str, "fix": str}
|
|
# ---------------------------------------------------------------------------
|
|
|
|
CATALOG: dict[str, dict[str, str]] = {
|
|
"VIN_FORMAT": {
|
|
"problema": "VIN invalid",
|
|
"fix": (
|
|
"Verifica VIN-ul pe talon (pozitia E) sau pe caroserie: exact 17 caractere"
|
|
" majuscule, fara spatii si fara literele O, I, Q."
|
|
),
|
|
},
|
|
"NR_INMATRICULARE_FORMAT": {
|
|
"problema": "Numar de inmatriculare invalid",
|
|
"fix": (
|
|
"Foloseste doar litere si cifre majuscule, maxim 10 caractere, fara spatii"
|
|
" sau cratima (ex. B123ABC)."
|
|
),
|
|
},
|
|
"DATA_FORMAT": {
|
|
"problema": "Data prestatiei in format gresit",
|
|
"fix": "Scrie data ca AAAA-LL-ZZ (ex. 2026-06-22).",
|
|
},
|
|
"DATA_PREA_VECHE": {
|
|
"problema": "Data prestatiei prea veche",
|
|
"fix": (
|
|
"RAR accepta prestatii doar incepand cu 01.12.2024;"
|
|
" verifica data prestatiei."
|
|
),
|
|
},
|
|
"DATA_VIITOR": {
|
|
"problema": "Data prestatiei in viitor",
|
|
"fix": "Data prestatiei nu poate fi dupa ziua de azi; corecteaza data.",
|
|
},
|
|
"ODOMETRU_FINAL_FORMAT": {
|
|
"problema": "Odometru final invalid",
|
|
"fix": (
|
|
"Scrie kilometrajul final ca numar intreg, fara zecimale sau text"
|
|
" (ex. 145000)."
|
|
),
|
|
},
|
|
"ODOMETRU_INITIAL_LIPSA": {
|
|
"problema": "Lipseste odometrul initial",
|
|
"fix": (
|
|
"Prestatiile R-ODO / I-ODO cer kilometrajul initial; completeaza-l."
|
|
),
|
|
},
|
|
"ODOMETRU_INITIAL_FORMAT": {
|
|
"problema": "Odometru initial invalid",
|
|
"fix": (
|
|
"Scrie kilometrajul initial ca numar intreg, fara zecimale sau text."
|
|
),
|
|
},
|
|
"ODOMETRU_INITIAL_ORDINE": {
|
|
"problema": "Odometru initial mai mare decat finalul",
|
|
"fix": (
|
|
"Kilometrajul initial trebuie sa fie mai mic sau egal cu cel final;"
|
|
" verifica cele doua valori."
|
|
),
|
|
},
|
|
"PRESTATII_GOALE": {
|
|
"problema": "Nicio prestatie",
|
|
"fix": "Adauga cel putin o prestatie cu cod RAR valid.",
|
|
},
|
|
"B64_INVALID": {
|
|
"problema": "Imaginea nu este base64 valid",
|
|
"fix": (
|
|
"Trimite imaginea codata base64 corect, sau omite campul daca nu ai imagine."
|
|
),
|
|
},
|
|
"COD_NEMAPAT": {
|
|
"problema": "Lipseste codul RAR al operatiei",
|
|
"fix": (
|
|
"Alege codul RAR pentru aceasta operatie in tab-ul Mapari"
|
|
" (ai sugestii automate)."
|
|
),
|
|
},
|
|
"AUTO_SEND_OPRIT": {
|
|
"problema": "Necesita confirmare manuala",
|
|
"fix": (
|
|
"Codul e mapat cu trimitere automata oprita; verifica randul si"
|
|
" pune-l manual in coada."
|
|
),
|
|
},
|
|
"RAR_VALIDARE": {
|
|
"problema": "RAR a respins prezentarea",
|
|
"fix": (
|
|
"Corecteaza campul semnalat de RAR (vezi cauza) si reincearca;"
|
|
" detaliile exacte sunt in mesajul tehnic RAR."
|
|
),
|
|
},
|
|
"RAR_CREDS_INVALIDE": {
|
|
"problema": "Credentiale RAR invalide",
|
|
"fix": (
|
|
"Verifica email-ul si parola contului RAR in tab-ul Cont;"
|
|
" trimiterea nu se reincearca automat la credentiale gresite."
|
|
),
|
|
},
|
|
"IMPORT_FISIER_PREA_MARE": {
|
|
"problema": "Fisier prea mare",
|
|
"fix": (
|
|
"Imparte fisierul in bucati de maxim 5000 de randuri si incarca-le pe rand."
|
|
),
|
|
},
|
|
"IMPORT_ANTET_NECLAR": {
|
|
"problema": "Antet de coloane neclar",
|
|
"fix": (
|
|
"Asigura-te ca primul rand contine numele coloanelor"
|
|
" (ex. VIN, Numar, Data)."
|
|
),
|
|
},
|
|
"IMPORT_ENCODING": {
|
|
"problema": "Codare de caractere nesuportata",
|
|
"fix": "Salveaza fisierul ca CSV UTF-8 (sau xlsx) si reincarca.",
|
|
},
|
|
"IMPORT_FISIER_NERECUNOSCUT": {
|
|
"problema": "Fisier nerecunoscut",
|
|
"fix": "Incarca un fisier .xlsx sau .csv valid.",
|
|
},
|
|
"IMPORT_MULTIPLE_SHEETS": {
|
|
"problema": "Mai multe foi in fisier",
|
|
"fix": "Pastreaza datele intr-o singura foaie sau alege foaia de import.",
|
|
},
|
|
"IMPORT_FARA_MAPARE_COLOANE": {
|
|
"problema": "Coloanele nu sunt mapate",
|
|
"fix": (
|
|
"Mapeaza intai coloanele fisierului la campurile cerute, apoi continua."
|
|
),
|
|
},
|
|
"IMPORT_CONFIRMARE_GRESITA": {
|
|
"problema": "Numar confirmat gresit",
|
|
"fix": (
|
|
"Numarul confirmat difera de randurile gata de trimis;"
|
|
" verifica preview-ul si reconfirma."
|
|
),
|
|
},
|
|
"IMPORT_OVERRIDE_ILIZIBIL": {
|
|
"problema": "Editarea anterioara nu se poate citi",
|
|
"fix": (
|
|
"Editarea salvata este ilizibila (probabil cheia s-a schimbat);"
|
|
" reediteaza randul."
|
|
),
|
|
},
|
|
"COLOANE_FORMAT_JSON": {
|
|
"problema": "Format de coloane (JSON) invalid",
|
|
"fix": (
|
|
"Verifica sintaxa JSON a maparii de coloane"
|
|
" (ghilimele duble, acolade inchise corect)."
|
|
),
|
|
},
|
|
"EROARE_INTERNA": {
|
|
"problema": "Eroare interna a gateway-ului",
|
|
"fix": (
|
|
"Nu e o problema de date trimise de tine. Reincearca peste cateva"
|
|
" momente; daca persista, contacteaza administratorul cu identificatorul"
|
|
" cererii (request_id) afisat."
|
|
),
|
|
},
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# eroare()
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def eroare(
|
|
cod: str,
|
|
*,
|
|
field: str | None = None,
|
|
cauza: str | None = None,
|
|
) -> dict:
|
|
"""Construieste un obiect de eroare pe 3 niveluri din CATALOG.
|
|
|
|
Parametri
|
|
---------
|
|
cod: Codul de eroare (cheie in CATALOG). Ridica KeyError daca absent.
|
|
field: Campul care a generat eroarea (optional, pentru context).
|
|
cauza: Descrierea tehnica a erorii concrete (optional).
|
|
Daca lipseste, `cauza` si `message` preiau valoarea `problema` din catalog.
|
|
|
|
Returneaza
|
|
----------
|
|
dict cu exact cheile: field, cod, problema, cauza, fix, message.
|
|
"""
|
|
entry = CATALOG[cod] # ridica KeyError daca cod absent
|
|
problema = entry["problema"]
|
|
fix = entry["fix"]
|
|
cauza_efectiva = cauza if cauza is not None else problema
|
|
message = cauza if cauza is not None else problema
|
|
return {
|
|
"field": field,
|
|
"cod": cod,
|
|
"problema": problema,
|
|
"cauza": cauza_efectiva,
|
|
"fix": fix,
|
|
"message": message,
|
|
}
|