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:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user