feat(editor): editor prestatii unificat in modal - un select, un rand de chips (PRD 5.23)

Inlocuieste cele 3 controale de asociere cod RAR cu un singur rand de chips
+ un singur select care adauga instant la change. Stare = un hidden chips_state
JSON versionat; post_form_chips redus la 2 actiuni (add/remove). Optgroup
Sugestii (fuzzy/k-NN) + optiune "Nu se declara la RAR" in select, cu tinta
implicita evidentiata si placeholder care o numeste. exclus persistat prin
payload_json (treapta noua de precedenta in resolve_prestatii, round-trip complet).

Siguranta: itemii exclusi sunt scosi din payload la momentul trimiterii
(worker split_prestatii_excluse inainte de build_rar_payload + filtru defensiv),
ca sa nu ajunga NICIODATA la RAR ca codPrestatie:null.

Suita: 1680 passed, 1 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-07-08 15:45:34 +00:00
parent 0bc618763e
commit 56ebe1f7cb
20 changed files with 2839 additions and 552 deletions

View File

@@ -212,7 +212,7 @@ def test_camp_observatii_prezent(client):
# --------------------------------------------------------------------------- #
def test_chips_multi_select_prestatii(client):
"""Submission cu cod_prestatie setat afiseaza chip cu hidden input."""
"""Submission cu cod_prestatie setat afiseaza chip cu OE-1 in starea hidden chips_state."""
acct = _create_account_user("chips.test@test.com")
_login(client, "chips.test@test.com")
_seed_cod("OE-1", "Schimb ulei motor")
@@ -222,14 +222,13 @@ def test_chips_multi_select_prestatii(client):
assert resp.status_code == 200
html = resp.text
# Trebuie sa existe un input (de obicei hidden) cu name="cod_prestatie" si valoarea "OE-1"
has_cod_prestatie_chip = bool(
re.search(r'<input[^>]+name="cod_prestatie"[^>]+value="OE-1"', html) or
re.search(r'<input[^>]+value="OE-1"[^>]+name="cod_prestatie"', html)
)
assert has_cod_prestatie_chip, (
"Formularul trebuie sa contina un hidden input cu name='cod_prestatie' value='OE-1' "
"reprezentand chip-ul de prestatie."
# Starea chips traieste intr-UN SINGUR hidden JSON versionat (D4, PRD 5.23).
m = re.search(r'name="chips_state"\s+value=\'([^\']*)\'', html)
assert m, "Formularul trebuie sa contina hidden input name='chips_state'"
payload = json.loads(m.group(1).replace("&#34;", '"'))
coduri = [it["cod"] for it in payload["items"]]
assert "OE-1" in coduri, (
f"chips_state trebuie sa contina codul OE-1 al prestatiei. items={payload['items']}"
)
@@ -244,19 +243,19 @@ def test_adauga_sterge_chip(client):
_seed_cod("OE-1", "Schimb ulei motor")
csrf = _csrf(client)
# POST /form-chips: adauga OE-1 la prima operatie (index 0)
# POST /form-chips: adauga OE-1 la prima operatie (index 0), stare curenta = un
# singur hidden chips_state (JSON versionat, D4).
state = json.dumps({"v": 1, "items": [
{"cod": "", "op": "Op-A", "den": "Schimb ulei motor", "exclus": 0},
]})
resp = client.post(
"/form-chips",
data={
"csrf_token": csrf,
# Starea curenta: o operatie fara cod
"chip_op_service": ["Op-A"],
"chip_denumire": ["Schimb ulei motor"],
"cod_prestatie": [""], # nemaapat initial
# Actiunea
"chips_state": state,
"chips_action": "add",
"chips_add_op_index": "0",
"chips_add_cod_0": "OE-1",
"chips_target_index": "0",
"chips_pick": "OE-1",
},
)
assert resp.status_code == 200, f"/form-chips a returnat {resp.status_code}: {resp.text[:400]}"
@@ -266,34 +265,31 @@ def test_adauga_sterge_chip(client):
assert "OE-1" in html, (
f"Dupa add, OE-1 trebuie sa apara in raspunsul /form-chips. html[:500]={html[:500]}"
)
# Si hidden input cu valoarea OE-1
has_hidden = bool(
re.search(r'<input[^>]+name="cod_prestatie"[^>]+value="OE-1"', html) or
re.search(r'<input[^>]+value="OE-1"[^>]+name="cod_prestatie"', html)
)
assert has_hidden, (
"Dupa add, trebuie sa existe un input cu name='cod_prestatie' value='OE-1' "
f"in raspunsul /form-chips. html[:600]={html[:600]}"
# Si in starea hidden chips_state
m = re.search(r'name="chips_state"\s+value=\'([^\']*)\'', html)
assert m, f"hidden chips_state lipsa. html[:600]={html[:600]}"
payload = json.loads(m.group(1).replace("&#34;", '"'))
assert payload["items"][0]["cod"] == "OE-1", (
f"Dupa add, chips_state trebuie sa contina OE-1. items={payload['items']}"
)
def test_sterge_chip(client):
"""POST /form-chips cu action=remove sterge chip-ul la indexul dat."""
"""POST /form-chips cu action=remove sterge codul chip-ului la indexul dat."""
acct = _create_account_user("form.chips.del@test.com")
_login(client, "form.chips.del@test.com")
_seed_cod("OE-1", "Schimb ulei motor")
csrf = _csrf(client)
# POST /form-chips: sterge chip-ul de la index 0 (OE-1 existent)
# POST /form-chips: sterge codul chip-ului de la index 0 (OE-1 existent, mapat pe Op-A)
state = json.dumps({"v": 1, "items": [
{"cod": "OE-1", "op": "Op-A", "den": "Schimb ulei motor", "exclus": 0},
]})
resp = client.post(
"/form-chips",
data={
"csrf_token": csrf,
# Starea curenta: OE-1 mapat pe Op-A
"chip_op_service": ["Op-A"],
"chip_denumire": ["Schimb ulei motor"],
"cod_prestatie": ["OE-1"],
# Actiunea: sterge indexul 0
"chips_state": state,
"chips_action": "remove",
"chips_remove_index": "0",
},
@@ -301,15 +297,15 @@ def test_sterge_chip(client):
assert resp.status_code == 200, f"/form-chips remove a returnat {resp.status_code}: {resp.text[:400]}"
html = resp.text
# Dupa remove, OE-1 nu mai apare ca chip (input hidden cu acea valoare)
has_oe1_chip = bool(
re.search(r'<input[^>]+name="cod_prestatie"[^>]+value="OE-1"', html) or
re.search(r'<input[^>]+value="OE-1"[^>]+name="cod_prestatie"', html)
)
assert not has_oe1_chip, (
"Dupa remove, OE-1 NU mai trebuie sa apara ca chip "
f"(hidden input cu cod_prestatie=OE-1). html[:500]={html[:500]}"
# Dupa remove, OE-1 nu mai apare ca cod in starea chips (operatia RAMANE, nemapata)
m = re.search(r'name="chips_state"\s+value=\'([^\']*)\'', html)
assert m
payload = json.loads(m.group(1).replace("&#34;", '"'))
coduri = [it["cod"] for it in payload["items"]]
assert "OE-1" not in coduri, (
f"Dupa remove, OE-1 NU mai trebuie sa apara ca cod in chips_state. items={payload['items']}"
)
assert payload["items"][0]["op"] == "Op-A", "Operatia trebuie sa ramana (doar codul dispare)"
# --------------------------------------------------------------------------- #
@@ -437,11 +433,16 @@ def test_form_chips_reveal_r_odo(client):
# --------------------------------------------------------------------------- #
# Test 8: Picker per operatie (E4 binding) -- format op-row #
# Test 8: Chip warning pentru operatie nemapata (control unificat, PRD 5.23) #
# --------------------------------------------------------------------------- #
def test_picker_per_operatie_in_form(client):
"""Operatie nemapata (needs_mapping) -> formularul afiseaza picker pe operatie."""
"""Operatie nemapata (needs_mapping) -> formularul afiseaza chip warning + selectul unic.
PRD 5.23 US-002: controlul unificat elimina `.op-row` (rand per operatie cu
picker propriu); operatia nemapata devine un chip warning in containerul
unic `.chips`, iar selectul e unul singur (`chips_pick`, id `chips-picker`).
"""
acct = _create_account_user("picker.op@test.com")
_login(client, "picker.op@test.com")
# NU seed-uim nicio mapare -> operatia ramane nemapata
@@ -455,17 +456,15 @@ def test_picker_per_operatie_in_form(client):
assert resp.status_code == 200
html = resp.text
# Operatia REVIZIE PERIODICA trebuie sa apara in form (op-row cu clasa specifica)
# clasa "op-row" din CSS base.html e adaugata NUMAI de chips_prestatii.html
has_op_row = "op-row" in html
assert has_op_row, (
"Formularul trebuie sa contina clasa 'op-row' "
"pentru picker-ul per-operatie. "
"Aceasta clasa e adaugata de _chips_prestatii.html. "
# Controlul unificat (PRD 5.23): fara .op-row, UN singur container .chips.
assert "op-row" not in html, (
"op-row a fost eliminat de controlul unificat (PRD 5.23 US-002). "
f"html[:600]={html[:600]}"
)
# Operatia REVIZIE PERIODICA trebuie sa apara in context op-row
assert 'class="chips"' in html
assert 'id="chips-picker"' in html
# Operatia REVIZIE PERIODICA trebuie sa apara ca chip warning
assert "REVIZIE PERIODICA" in html, (
"Operatia 'REVIZIE PERIODICA' trebuie sa apara in formularul de editare (op-row). "
f"html[:500]={html[:500]}"
"Operatia 'REVIZIE PERIODICA' trebuie sa apara in formularul de editare "
f"(chip warning). html[:500]={html[:500]}"
)