feat(errors): erori pe 3 niveluri (problema+cauza+fix) pe API si UI (PRD 5.4)
Catalog central pur app/errors.py ca sursa unica cod->{problema,fix},
consumat de API+UI+worker. Aditiv (field/message pastrate la octet) +
rar_error stocat superset. Scope: fluxul de declarare; login/signup/CSRF
neatinse. labels.parse_erori degradeaza gratios; UI progresiv AA light+dark.
631 teste.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,8 @@ import re
|
||||
from datetime import date
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from app.errors import eroare as _eroare
|
||||
|
||||
# VIN: 17 caractere, majuscule, fara O/I/Q (plan §2 + contract).
|
||||
VIN_RE = re.compile(r"^[A-HJ-NPR-Z0-9]{17}$")
|
||||
# Numar inmatriculare: max 10, litere + cifre majuscule.
|
||||
@@ -64,36 +66,54 @@ def validate_prezentare(content: dict) -> list[dict]:
|
||||
# --- VIN ---
|
||||
vin = _norm(content.get("vin"))
|
||||
if not VIN_RE.match(vin):
|
||||
errors.append({
|
||||
"field": "vin",
|
||||
"message": "VIN trebuie sa aiba exact 17 caractere majuscule, fara spatii/caractere speciale si fara O, I, Q.",
|
||||
})
|
||||
errors.append(_eroare(
|
||||
"VIN_FORMAT",
|
||||
field="vin",
|
||||
cauza="VIN trebuie sa aiba exact 17 caractere majuscule, fara spatii/caractere speciale si fara O, I, Q.",
|
||||
))
|
||||
|
||||
# --- nrInmatriculare ---
|
||||
nrinm = _norm(content.get("nr_inmatriculare"))
|
||||
if not NRINM_RE.match(nrinm):
|
||||
errors.append({
|
||||
"field": "nr_inmatriculare",
|
||||
"message": "Numarul de inmatriculare trebuie sa aiba max 10 caractere, doar litere si cifre majuscule.",
|
||||
})
|
||||
errors.append(_eroare(
|
||||
"NR_INMATRICULARE_FORMAT",
|
||||
field="nr_inmatriculare",
|
||||
cauza="Numarul de inmatriculare trebuie sa aiba max 10 caractere, doar litere si cifre majuscule.",
|
||||
))
|
||||
|
||||
# --- dataPrestatie ∈ [2024-12-01, azi] TZ Bucuresti ---
|
||||
raw_data = str(content.get("data_prestatie") or "").strip()
|
||||
try:
|
||||
d = date.fromisoformat(raw_data)
|
||||
except ValueError:
|
||||
errors.append({"field": "data_prestatie", "message": "Format data invalid; foloseste YYYY-MM-DD."})
|
||||
errors.append(_eroare(
|
||||
"DATA_FORMAT",
|
||||
field="data_prestatie",
|
||||
cauza="Format data invalid; foloseste YYYY-MM-DD.",
|
||||
))
|
||||
d = None
|
||||
if d is not None:
|
||||
if d < MIN_DATA_PRESTATIE:
|
||||
errors.append({"field": "data_prestatie", "message": "Data prestatiei nu poate fi anterioara datei de 01.12.2024."})
|
||||
errors.append(_eroare(
|
||||
"DATA_PREA_VECHE",
|
||||
field="data_prestatie",
|
||||
cauza="Data prestatiei nu poate fi anterioara datei de 01.12.2024.",
|
||||
))
|
||||
elif d > today_bucuresti():
|
||||
errors.append({"field": "data_prestatie", "message": "Data prestatiei nu poate fi in viitor."})
|
||||
errors.append(_eroare(
|
||||
"DATA_VIITOR",
|
||||
field="data_prestatie",
|
||||
cauza="Data prestatiei nu poate fi in viitor.",
|
||||
))
|
||||
|
||||
# --- odometruFinal (string numeric) ---
|
||||
odo_final = _parse_int(content.get("odometru_final"))
|
||||
if odo_final is None:
|
||||
errors.append({"field": "odometru_final", "message": "odometruFinal trebuie sa fie un numar intreg (ca string)."})
|
||||
errors.append(_eroare(
|
||||
"ODOMETRU_FINAL_FORMAT",
|
||||
field="odometru_final",
|
||||
cauza="odometruFinal trebuie sa fie un numar intreg (ca string).",
|
||||
))
|
||||
|
||||
# --- odometruInitial: obligatoriu daca prestatii ∋ R-ODO/I-ODO; <= odometruFinal ---
|
||||
codes = _codes(content.get("prestatii"))
|
||||
@@ -101,26 +121,43 @@ def validate_prezentare(content: dict) -> list[dict]:
|
||||
raw_initial = content.get("odometru_initial")
|
||||
has_initial = str(raw_initial or "").strip() != ""
|
||||
if needs_initial and not has_initial:
|
||||
errors.append({
|
||||
"field": "odometru_initial",
|
||||
"message": "odometruInitial este obligatoriu cand prestatiile contin R-ODO sau I-ODO.",
|
||||
})
|
||||
errors.append(_eroare(
|
||||
"ODOMETRU_INITIAL_LIPSA",
|
||||
field="odometru_initial",
|
||||
cauza="odometruInitial este obligatoriu cand prestatiile contin R-ODO sau I-ODO.",
|
||||
))
|
||||
if has_initial:
|
||||
odo_initial = _parse_int(raw_initial)
|
||||
if odo_initial is None:
|
||||
errors.append({"field": "odometru_initial", "message": "odometruInitial trebuie sa fie un numar intreg."})
|
||||
errors.append(_eroare(
|
||||
"ODOMETRU_INITIAL_FORMAT",
|
||||
field="odometru_initial",
|
||||
cauza="odometruInitial trebuie sa fie un numar intreg.",
|
||||
))
|
||||
elif odo_final is not None and odo_initial > odo_final:
|
||||
errors.append({"field": "odometru_initial", "message": "odometruInitial trebuie sa fie <= odometruFinal."})
|
||||
errors.append(_eroare(
|
||||
"ODOMETRU_INITIAL_ORDINE",
|
||||
field="odometru_initial",
|
||||
cauza="odometruInitial trebuie sa fie <= odometruFinal.",
|
||||
))
|
||||
|
||||
# --- prestatii nevide ---
|
||||
if not codes:
|
||||
errors.append({"field": "prestatii", "message": "Lista de prestatii nu poate fi goala."})
|
||||
errors.append(_eroare(
|
||||
"PRESTATII_GOALE",
|
||||
field="prestatii",
|
||||
cauza="Lista de prestatii nu poate fi goala.",
|
||||
))
|
||||
|
||||
# --- b64Image: optional, dar daca e prezent trebuie base64 valid ---
|
||||
b64 = content.get("b64_image")
|
||||
if b64:
|
||||
if not _is_valid_base64(str(b64)):
|
||||
errors.append({"field": "b64_image", "message": "b64Image nu este base64 valid."})
|
||||
errors.append(_eroare(
|
||||
"B64_INVALID",
|
||||
field="b64_image",
|
||||
cauza="b64Image nu este base64 valid.",
|
||||
))
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
Reference in New Issue
Block a user