fix(import): persista chips prestatii la salvarea din modalul de editare rand

Editorul de rand din preview afisa chips-urile operatie<->cod RAR dar salvarea
extragea doar EDIT_FIELDS — codurile alese se pierdeau si randul ramanea
needs_mapping. Starea chips se reconstruieste din form (conventia /form-chips),
se valideaza fata de nomenclator si se persista in override_json.

Caile de commit (API + web) aplica acum override-ul INAINTE de resolve_prestatii,
ca in preview — altfel prestatiile editate ajungeau nerezolvate in payload.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-07-07 17:20:01 +00:00
parent 5a2c976d46
commit c6e29aa65d
3 changed files with 262 additions and 29 deletions

View File

@@ -3806,6 +3806,51 @@ def web_rand_display(request: Request, import_id: int, row_index: int) -> HTMLRe
conn.close()
def _chips_state_from_form(form) -> list[dict[str, str]] | None:
"""Reconstruieste starea chips prestatii din inputurile paralele ale formularului.
Aceeasi conventie ca /form-chips (_chips_prestatii.html): 3 liste paralele
cod_prestatie / chip_op_service / chip_denumire. Codurile alese in pickere dar
ne-aprobate prin '+' (chips_add_cod_{i}, chips_add_cod_flat) se aplica implicit,
ca in post_corectie_trimitere. Dedup pe perechea (op, cod).
Intoarce None cand formularul nu contine deloc stare de chips (form fara
sectiunea de prestatii) — apelantul lasa prestatiile neatinse.
"""
flat_picker = str(form.get("chips_add_cod_flat") or "").strip().upper()
if "cod_prestatie" not in form and "chip_op_service" not in form and not flat_picker:
return None
cod_list = [c.strip().upper() if isinstance(c, str) else "" for c in form.getlist("cod_prestatie")]
op_list = [o.strip() if isinstance(o, str) else "" for o in form.getlist("chip_op_service")]
den_list = [d.strip() if isinstance(d, str) else "" for d in form.getlist("chip_denumire")]
n = max(len(cod_list), len(op_list), len(den_list))
chips: list[dict[str, str]] = []
for i in range(n):
cod = cod_list[i] if i < len(cod_list) else ""
if not cod:
# picker per-operatie cu cod ales dar ne-aprobat prin '+ Adauga'
cod = str(form.get(f"chips_add_cod_{i}") or "").strip().upper()
chips.append({
"cod_prestatie": cod,
"cod_op_service": op_list[i] if i < len(op_list) else "",
"denumire": den_list[i] if i < len(den_list) else "",
})
pairs = {(c["cod_op_service"], c["cod_prestatie"]) for c in chips}
if flat_picker and ("", flat_picker) not in pairs:
chips.append({"cod_prestatie": flat_picker, "cod_op_service": "", "denumire": ""})
seen: set = set()
out: list[dict[str, str]] = []
for c in chips:
if not (c["cod_prestatie"] or c["cod_op_service"] or c["denumire"]):
continue
pair = (c["cod_op_service"], c["cod_prestatie"])
if pair in seen:
continue
seen.add(pair)
out.append(c)
return out
@router.post("/_import/{import_id}/rand/{row_index}/editeaza", response_class=HTMLResponse)
async def web_editeaza_rand(request: Request, import_id: int, row_index: int) -> HTMLResponse:
"""Persista override (mutatie pura) + raspunde cu OOB rand+contoare sau erori in modal.
@@ -3825,13 +3870,50 @@ async def web_editeaza_rand(request: Request, import_id: int, row_index: int) ->
camp: (str(form.get(camp)) if form.get(camp) is not None else None)
for camp in EDIT_FIELDS
}
# Chips prestatii din modal: None = form fara sectiune de chips (neatins).
chips_state = _chips_state_from_form(form)
conn = get_connection()
try:
# Invariant ORA-12899: orice cod trimis din form se valideaza fata de
# nomenclator INAINTE de persistare (RAR accepta NUMAI coduri valide).
if chips_state is not None:
valid_codes = load_nomenclator_codes(conn) or set()
necunoscute = sorted(
{c["cod_prestatie"] for c in chips_state if c["cod_prestatie"]} - valid_codes
)
if necunoscute:
_nom = load_nomenclator(conn)
return templates.TemplateResponse("_editare_preview_modal.html", {
"request": request,
"import_id": import_id,
"row_index": row_index,
"csrf_token": get_csrf_token(request),
"vin": str(form.get("vin") or ""),
"stare_css": "",
"stare_eticheta": "",
"form_nr": str(form.get("nr_inmatriculare") or ""),
"form_vin": str(form.get("vin") or ""),
"form_data": str(form.get("data_prestatie") or ""),
"form_odo_final": str(form.get("odometru_final") or ""),
"form_odo_initial": str(form.get("odometru_initial") or ""),
"err_map": {},
"fix_map": {},
"vin_context": str(form.get("vin") or ""),
"btn_label": "Salveaza",
"message": f"Cod RAR necunoscut in nomenclator: {', '.join(necunoscute)}. "
"Alege un cod valid din lista.",
"prestatii_chips": chips_state,
"has_r_odo": _has_r_odo_chips(chips_state),
"obs_val": str(form.get("obs") or "").strip(),
"nomenclator_rar": _nom,
"form_chips_url": "/form-chips",
})
# Mutatie pura de stocare (404/409/422 -> propaga; htmx hx-on::response-error
# pastreaza formularul modal cu valorile la 4xx/5xx).
apply_row_override(
conn, import_id=import_id, account_id=account_id,
row_index=row_index, fields=fields,
row_index=row_index, fields=fields, prestatii=chips_state,
)
result, row = _preview_one_row(conn, import_id, account_id, row_index)
if row is None or isinstance(result, str):
@@ -4377,12 +4459,19 @@ async def web_confirma_import(
denumire = str(denumire_val).strip() if denumire_val not in (None, "") else str(operatie_val)
mapped["prestatii"] = [{"cod_op_service": str(operatie_val), "denumire": denumire}]
# Override editat in preview — aplicat INAINTE de resolve_prestatii,
# ca in _resolve_row_for_preview: prestatiile din override (chips
# editate in modal) trec prin ACELASI resolve.
override = item.get("override") or {}
if override:
mapped.update(override)
# Rezolva prestatii
prestatii = mapped.get("prestatii") or []
resolved_p, _ = resolve_prestatii(prestatii, mapping_ops, valid_codes, text_rules)
mapped["prestatii"] = resolved_p
# Canonicalizare
# Canonicalizare (cheia identica cu preview: canonicalize_row + build_key)
canon = canonicalize_row(mapped)
mapped.update({
"vin": canon["vin"],
@@ -4390,17 +4479,6 @@ async def web_confirma_import(
"odometru_final": canon["odometru_final"],
})
# Override editat in preview — aplicat ULTIMUL, ca in resolver.
override = item.get("override") or {}
if override:
mapped.update(override)
canon = canonicalize_row(mapped)
mapped.update({
"vin": canon["vin"],
"nr_inmatriculare": canon["nr_inmatriculare"],
"odometru_final": canon["odometru_final"],
})
key = build_key(account_id, canon, env)
rows_for_hash.append(json.dumps({