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:
514
docs/prd/prd-5.23-editor-prestatii-unificat.md
Normal file
514
docs/prd/prd-5.23-editor-prestatii-unificat.md
Normal file
@@ -0,0 +1,514 @@
|
||||
<!-- /autoplan restore point: /home/claude/.gstack/projects/romfast-rar-autopass/main-autoplan-restore-20260707-194818.md -->
|
||||
# PRD 5.23 — Editor prestatii unificat in modalul "Editare rand" (un select, un rand de chips)
|
||||
|
||||
**Stare**: inchis (2026-07-08; executie echipa agenti Sonnet TDD, VERIFY context curat PASS,
|
||||
code-review CLOSE = 1 blocker de siguranta prins + reparat. Vezi Anexa /autoplan, GSTACK REVIEW
|
||||
REPORT, Raport VERIFY; mockup final aprobat: select cu placeholder-tinta)
|
||||
|
||||
> Proces: `docs/ROADMAP.md` §5. Contract RAR: `docs/api-rar-contract.md`.
|
||||
> Context vizual: screenshot `2026-07-07-003.jpg` (modal "Editare rand 4", badge "Cod RAR lipsa").
|
||||
|
||||
## 1. Obiectiv
|
||||
|
||||
Modalul "Editare rand" (import preview + corectie Trimiteri) are azi TREI controale pentru aceeasi
|
||||
sarcina — asocierea codurilor RAR la prestatii: (1) rand per operatie cu select "— alege cod RAR —"
|
||||
+ buton "Adauga", (2) container separat de chips pentru coduri libere (ex. `OE-1 ×`), (3) select
|
||||
"+ Adauga alta operatie / cod RAR" cu buton "+". Utilizatorul nu stie care control face ce, iar
|
||||
chips-urile apar in containere diferite, pe randuri diferite.
|
||||
|
||||
Livrabila: **un singur control** — un rand unic de chips (toate codurile, legate de operatie sau
|
||||
libere) + **un singur select** care adauga chip instant la selectie (fara butoane "Adauga"/"+").
|
||||
Selectul primeste sugestii fuzzy/k-NN pentru operatia nemapata si optiunea "Nu se declara la RAR".
|
||||
|
||||
## 2. Non-Goals (anti scope-creep)
|
||||
|
||||
- NU se schimba panoul de mapare (`_mapari.html`, `_preview_import.html`). EXCEPTIE confirmata in
|
||||
review (eng, 2026-07-07): `resolve_prestatii` primeste O SINGURA treapta noua de precedenta —
|
||||
`exclus=1` explicit pe item (din override/corectie) supravietuieste re-rezolvarii (azi
|
||||
`it.pop("exclus", None)` la `mapping.py:298` il sterge, ceea ce face US-004 imposibil altfel).
|
||||
Restul precedentei ramane neatins.
|
||||
- NU se schimba contractul API `POST /v1/prezentari`. `override_json["prestatii"]` castiga campul
|
||||
`exclus` pe item; itemii exclusi RAMAN in `payload_json` (adnotati), nu mai sunt eliminati la
|
||||
salvare — `split_prestatii_excluse` ii scoate doar din payload-ul trimis la RAR.
|
||||
- NU se schimba validarea codului fata de nomenclator la salvare (invariant ORA-12899) — ramane.
|
||||
- NU se adauga cautare/autocomplete text in select (ramane `<select>` nativ; imbunatatire separata).
|
||||
- NU se atinge butonul "salveaza ca regula" de la Trimiteri — ramane, doar mutat pe chip-ul nou.
|
||||
|
||||
## 3. Comportamentul controlului unificat (spec)
|
||||
|
||||
```
|
||||
Prestatii — coduri RAR
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ [⚠ OP-ITP · fara cod] [OE-1 ×] [— alege cod RAR — ▾] │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **Un singur container** `.chips` cu toate elementele: chips-uri cod (× sterge), chips-uri warning
|
||||
pentru operatii nemapate, si selectul unic la capat. Wrap natural doar la lipsa de spatiu —
|
||||
niciodata containere/chenare separate pe zone.
|
||||
- **Chip cod legat de operatie**: eticheta `COD`, titlu/tooltip cu denumirea operatiei sursa
|
||||
(ex. `OE-8 — Inlocuire anvelope`). `×` scoate codul; daca operatia ramane fara cod, reapare
|
||||
chip-ul warning al operatiei.
|
||||
- **Chip cod liber** (fara operatie sursa): identic vizual, tooltip "cod adaugat manual".
|
||||
- **Chip warning** (`⚠ OP-ITP · fara cod`): reprezinta operatia din import nemapata. Click pe el
|
||||
il face **tinta activa** (evidentiat); implicit tinta = prima operatie nemapata.
|
||||
- **Selectul unic**, `hx-trigger="change"` → `POST /form-chips` cu `chips_action=add`:
|
||||
- daca exista tinta activa (operatie nemapata) → codul se leaga de acea operatie
|
||||
(chip warning → chip cod); tinta trece automat la urmatoarea operatie nemapata;
|
||||
- **placeholder-ul selectului numeste tinta** (decizie gate D2, 2026-07-07): cu tinta activa →
|
||||
`Cod pentru OP-X — alege`; fara tinta → `— adauga cod RAR —`. Legatura cod↔operatie e
|
||||
explicita la momentul alegerii, fara text suplimentar sub control;
|
||||
- fara operatii nemapate → codul se adauga ca chip liber;
|
||||
- dupa adaugare selectul revine la placeholder; chip-ul nou primeste evidentierea `.chip-nou`.
|
||||
- **Continutul selectului**, cand tinta e o operatie nemapata:
|
||||
- `optgroup "Sugestii"` sus: sugestia principala + fuzzy/k-NN top-3 pentru denumirea operatiei
|
||||
(aceleasi surse ca in `_preview_import.html:89-121`);
|
||||
- optiunea `"Nu se declara la RAR (exclude operatia)"` = sentinel `__NEDECLARAT__`
|
||||
(`EXCLUDE_SENTINEL`, `app/mapping.py:44`) → operatia primeste chip distinct `Nedeclarat`
|
||||
(stil mut, ×-ul readuce chip-ul warning), item anotat `exclus` in starea chips;
|
||||
- restul nomenclatorului, ca azi.
|
||||
Fara tinta (doar cod liber): fara optgroup Sugestii si fara `__NEDECLARAT__`.
|
||||
- **Dedup**: acelasi `(operatie, cod)` nu se adauga de doua ori (comportament existent pastrat).
|
||||
- **Fara text explicativ permanent** sub control (decizie utilizator la aprobarea mockup-ului,
|
||||
2026-07-07): starea tintei se comunica DOAR vizual (stilul chip-ului warning evidentiat);
|
||||
mesajele `chips_ok`/`chips_error` raman exclusiv ca feedback tranzitoriu la actiuni.
|
||||
- Mockup aprobat: artifact "Mockup — Editor prestatii unificat (PRD 5.23)".
|
||||
- **Modul plat** (corectii fara operatii din import) foloseste ACELASI control: zero chips warning,
|
||||
selectul adauga doar coduri libere. Logica `has_r_odo`/`data-has-r-odo` (reveal odometru initial
|
||||
pe R-ODO/I-ODO) si stilul `chip-warn` pe aceste coduri se pastreaza neschimbate.
|
||||
|
||||
### 3.1 Specificatie vizuala si de stari (din review-ul de design, 2026-07-07)
|
||||
|
||||
**Scara de dominanta vizuala — UN SINGUR tratament puternic simultan:**
|
||||
|
||||
| Element | Fundal / chenar / text | Nota |
|
||||
|---|---|---|
|
||||
| Chip tinta activa | fundal warn-soft, chenar SOLID 1px `--warn` + inel 2px warn-transparent | singurul element "tare" din rand |
|
||||
| Chip warning (ne-tinta) | fundal warn-soft, chenar DASHED 1px `--warn` | click → devine tinta |
|
||||
| Chip cod | fundal `--card2`-albastru (`--accent` 15%), text `--accent`, chenar subtil | eticheta `COD` mono |
|
||||
| Chip Nedeclarat | fundal gri mut, text taiat (line-through), chenar dashed gri | NU discret pana la invizibil |
|
||||
| `.chip-nou` | animatie de emfaza >=1.5s (fade), tranzitorie | nu concureaza cu tinta |
|
||||
| Chip R-ODO/I-ODO | `chip-warn` existent, neschimbat | reveal odometru pastrat |
|
||||
|
||||
**Invariante de interactiune:**
|
||||
- Ordinea chips e STABILA: operatiile in ordinea din import, codurile libere la coada; o rezolvare
|
||||
nu re-ordoneaza randul (warning → cod in acelasi slot).
|
||||
- Cat exista operatii nemapate, EXISTA tinta (implicit prima nemapata); click pe tinta activa =
|
||||
no-op (nu se poate "deselecta" — adaugarile libere sunt posibile doar fara warnings).
|
||||
- Click pe chip warning = comutare de tinta CLIENT-SIDE (toggle clasa + hidden `chips_target_index`),
|
||||
fara round-trip; indexul tintei pleaca la server la urmatorul add.
|
||||
- Selectul are id stabil (`chips-picker`); dupa swap `outerHTML`, focusul revine pe select.
|
||||
- Optiunile selectului: `COD — denumire nomenclator`; in optgroup "Sugestii" acelasi format
|
||||
(optgroup-ul e singura marca de sugestie). Eticheta chip warning: max ~18ch + ellipsis,
|
||||
text complet in `title`.
|
||||
|
||||
**Tabel de stari (ce VEDE utilizatorul):**
|
||||
|
||||
| Stare | Ce se vede |
|
||||
|---|---|
|
||||
| Loading (add in zbor) | selectul disabled (`hx-disabled-elt="this"`) + stil `.htmx-request` pe sectiune; dedup face inofensiv un dublu-fire |
|
||||
| Eroare add (400/500/retea) | mesaj `chips_error` SUB control (pattern 5.22, persista pana la urmatoarea actiune); selectul re-activat; tinta neschimbata; `hx-on::response-error` acopera esecul de retea |
|
||||
| Gol (mod plat, zero chips) | doar selectul cu placeholder "— adauga cod RAR —"; container cu min-height (modalul nu salta la primul chip) |
|
||||
| Succes partial | chip-ul nou cu emfaza >=1.5s; tinta avanseaza (vezi decizia de la gate) |
|
||||
| Complet (zero nemapate) | mesaj tranzitoriu "Toate operatiile au cod" (`chips_ok`); optgroup Sugestii si `__NEDECLARAT__` dispar din select |
|
||||
| Nedeclarat | mesajul numeste operatia: "OP-X nu se va declara la RAR" |
|
||||
| Nomenclator gol | empty state existent (pastrat) |
|
||||
|
||||
## 4. Stories atomice
|
||||
|
||||
### US-001: Stare chips extinsa cu `exclus` + actiuni add/remove unificate in backend
|
||||
**Ca** dezvoltator **vreau** o singura pereche de actiuni `add`/`remove` pe starea chips (cu tinta
|
||||
optionala si suport sentinel `__NEDECLARAT__`) **pentru ca** azi exista 4 actiuni suprapuse
|
||||
(`add`, `add_flat`, `add_extra`, `remove_flat`) care dubleaza logica.
|
||||
|
||||
- **Depinde de**: —
|
||||
- **Fisiere**: `app/web/routes.py` (`post_form_chips` :2402-2521, `_chips_state_from_form` :3862-3918), `tests/test_web_chips.py` (~2 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_chips.py` — `test_add_cu_tinta_leaga_de_operatie`,
|
||||
`test_add_fara_tinta_adauga_cod_liber`, `test_add_nedeclarat_marcheaza_exclus`,
|
||||
`test_remove_cod_readuce_operatia_nemapata`, `test_dedup_operatie_cod`
|
||||
- **Acceptance criteria**:
|
||||
- [x] Starea chips serializata intr-UN SINGUR hidden `chips_state` = JSON versionat
|
||||
(`{"v":1,"items":[{"cod":"","op":"","den":"","exclus":0},...]}`) — decizie gate D4:
|
||||
elimina structural clasa de bug-uri de aliniere pe index (2 regresii in 2 release-uri).
|
||||
`_chips_state_from_form` parseaza JSON-ul; JSON invalid → eroare vizibila, nu stare goala.
|
||||
- [x] Compat tranzitorie la deploy: daca `chips_state` lipseste dar exista listele vechi
|
||||
(`cod_prestatie`/`chip_op_service`/`chip_denumire`), parserul cade inapoi pe ele cu
|
||||
`exclus=0` (form-uri deschise peste deploy); fallback-ul se sterge in release-ul urmator.
|
||||
- [x] `chips_state` prezent mereu (lista goala = `{"v":1,"items":[]}`): golirea intentionata a
|
||||
chips se persista la salvare (inlocuieste santinela `chips_present`).
|
||||
- [x] `chips_action=add` + `chips_target_index=i` leaga codul de operatia i; fara target →
|
||||
chip liber; cod `__NEDECLARAT__` + target → item cu `exclus=1` si fara `cod_prestatie`.
|
||||
- [x] `chips_action=remove` (index unic) acopera si fostul `remove_flat`; actiunile
|
||||
`add_flat`/`add_extra`/`chips_add_cod_{i}`/`chips_add_cod_flat` sunt eliminate.
|
||||
- [x] Tinta implicita dupa un add reusit = urmatoarea operatie nemapata (sau nici una).
|
||||
- [x] Validare tinta SEMANTICA, nu doar in-range: `chips_target_index` trebuie sa arate spre o
|
||||
operatie nemapata si ne-exclusa; altfel `chips_error` (acopera POST fabricat si index
|
||||
stale dupa un remove).
|
||||
- [x] `chip_exclus` parsat strict ("1"→1, orice altceva→0); `__NEDECLARAT__` fara tinta valida →
|
||||
`chips_error` explicit.
|
||||
- [x] Testele existente pe actiunile vechi (`tests/test_web_corectie_prestatii.py`,
|
||||
`tests/test_web_form_editare_slim.py`) sunt ACTUALIZATE in acelasi val (suita nu ramane
|
||||
rosie intre valuri).
|
||||
- [x] Emfaza chip nou pe INDEX (`chips_added_index`), nu pe valoarea codului (doua operatii pot
|
||||
avea legitim acelasi cod).
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: acoperit de US-005.
|
||||
|
||||
### US-002: Template unificat — un container, un select, fara butoane Adauga/+
|
||||
**Ca** operator **vreau** un singur rand de chips si un singur select care adauga instant
|
||||
**pentru ca** azi nu stiu care din cele 3 controale trebuie folosit.
|
||||
|
||||
- **Depinde de**: US-001
|
||||
- **Fisiere**: `app/web/templates/_chips_prestatii.html` (rescriere), `app/web/templates/base.html`
|
||||
(CSS `.chips`/`.chip*` :815-843, eliminare `.op-row`), `tests/test_web_chips.py` (~3 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_chips.py` — `test_render_un_singur_container_chips`,
|
||||
`test_render_fara_op_row_si_fara_butoane_adauga`, `test_chip_warning_pentru_operatie_nemapata`
|
||||
- **Acceptance criteria**:
|
||||
- [x] Un singur container `.chips` contine: chips cod (cu tooltip operatia sursa), chips warning
|
||||
operatii nemapate, chip `Nedeclarat` pentru itemi exclusi, selectul unic la capat.
|
||||
- [x] Selectul (nume camp: `chips_pick`, id stabil `chips-picker`) are `hx-trigger="change"`,
|
||||
`hx-post="/form-chips"`, `hx-target="#chips-section"`, `hx-swap="outerHTML"`,
|
||||
`hx-include="closest form"` (transporta CSRF + starea hidden — omiterea = 403) si
|
||||
`hx-disabled-elt="this"` (capcana htmx din 5.22 — NU mosteni de la form).
|
||||
- [x] Selectul revine la placeholder dupa ORICE raspuns (succes SI eroare); valoarea ramasa in
|
||||
`chips_pick` NU e aplicata la Salveaza pe niciuna din cai (regresia c6e29aa nu reapare
|
||||
sub alt nume — codul se adauga DOAR prin actiunea add).
|
||||
- [x] Pe durata unui request /form-chips, INTREAGA sectiune e inerta (`pointer-events:none` pe
|
||||
`.htmx-request` sau echivalent) — click pe × / chip warning mid-flight nu pierde actiuni.
|
||||
- [x] Click pe chip warning comuta tinta CLIENT-SIDE (clasa + hidden `chips_target_index`),
|
||||
fara round-trip; click pe tinta activa = no-op.
|
||||
- [x] Focus restaurat pe `#chips-picker` dupa swap.
|
||||
- [x] Zero aparitii `op-row`, `Adauga`, `add_flat`, `chips_add_cod_` in template.
|
||||
- [x] `chips_submission_id` e emis ca hidden in sectiune si ecouat de `/form-chips`, astfel
|
||||
incat "salveaza ca regula" ramane disponibil si DUPA re-randari (azi dispare la prima
|
||||
interactiune — bug perpetuat altfel).
|
||||
- **Verificare E2E**: acoperit de US-005.
|
||||
|
||||
### US-003: Sugestii fuzzy/k-NN + optiunea "Nu se declara la RAR" in selectul unic
|
||||
**Ca** operator **vreau** sugestiile de mapare direct in selectul din modal **pentru ca** azi ele
|
||||
exista doar in panoul de mapare, iar in modal caut manual prin tot nomenclatorul.
|
||||
|
||||
- **Depinde de**: US-001, US-002
|
||||
- **Fisiere**: `app/web/routes.py` (`post_form_chips` — construire sugestii pentru operatia tinta,
|
||||
refolosind sursele din preview), `app/web/templates/_chips_prestatii.html`, `tests/test_web_chips.py` (~3 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_chips.py` — `test_select_are_optgroup_sugestii_pt_tinta`,
|
||||
`test_select_are_nedeclarat_doar_cu_tinta`, `test_select_fara_tinta_fara_sugestii`
|
||||
- **Acceptance criteria**:
|
||||
- [x] Cu tinta activa: `optgroup "Sugestii"` cu sugestia principala + top-3 fuzzy/k-NN pentru
|
||||
`denumire`-a operatiei; sub el optiunea `__NEDECLARAT__`; apoi nomenclatorul complet.
|
||||
- [x] Fara tinta: selectul listeaza doar nomenclatorul (fara Sugestii, fara `__NEDECLARAT__`).
|
||||
- [x] Sugestiile folosesc aceleasi functii ca `_preview_import.html` (fara drum nou de cod).
|
||||
- [x] Degradare gratioasa: motor de sugestii rece/dezactivat → select DOAR cu nomenclatorul
|
||||
(fara optgroup), fara eroare si fara blocare pe warmup (lock non-blocking, PRD 5.21).
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: acoperit de US-005.
|
||||
|
||||
### US-004: Persistarea `exclus` prin salvare (import + Trimiteri) — cu round-trip complet
|
||||
**Ca** operator **vreau** ca excluderea aleasa in modal sa se pastreze la salvare, sa produca
|
||||
starea corecta si sa fie REVERSIBILA la redeschidere **pentru ca** altfel optiunea din select ar fi
|
||||
cosmetica sau, mai rau, operatia ar fi declarata contra intentiei mele (RAR e ireversibil).
|
||||
|
||||
> Corectie din review (eng, CONFIRMATA in cod): pipeline-ul actual sterge `exclus` per-item
|
||||
> (`resolve_prestatii` face `it.pop("exclus", None)`, `mapping.py:298`) si elimina itemii exclusi
|
||||
> din `payload_json` la corectie (`routes.py:1744`). Fara fix-urile de mai jos, un item exclus din
|
||||
> modal ar reveni `needs_mapping` sau — daca operatia are mapare pe cont — ar fi DECLARAT la RAR.
|
||||
|
||||
- **Depinde de**: US-001
|
||||
- **Fisiere**: `app/mapping.py` (`resolve_prestatii` — treapta noua: item-exclus explicit
|
||||
supravietuieste), `app/web/routes.py` (`web_editeaza_rand`, `post_corectie_trimitere` :1619,
|
||||
`_prestatii_chips_from_payload` :1336 + toti constructorii de context chips),
|
||||
`app/api/v1/import_router.py` (`apply_row_override` :319-366), `tests/test_web_editare_rand.py` (~4 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_editare_rand.py` — `test_salvare_pastreaza_exclus_in_override`,
|
||||
`test_exclus_supravietuieste_resolve_chiar_cu_mapare_pe_cont`,
|
||||
`test_redeschidere_modal_arata_chip_nedeclarat_si_x_readuce_warning`,
|
||||
`test_rand_toate_operatiile_excluse_devine_excluded`, `test_salvare_trimitere_cu_exclus`
|
||||
- **Acceptance criteria**:
|
||||
- [x] `resolve_prestatii` pastreaza `exclus=1` venit explicit pe item (precedenta: item-exclus >
|
||||
cod explicit > regula exclus cont > mapare > reguli text); restul treptelor neschimbate.
|
||||
- [x] Itemii exclusi RAMAN in `payload_json`/`override_json` adnotati cu `exclus=1`;
|
||||
`split_prestatii_excluse` ii scoate DOAR din payload-ul trimis la RAR si din cheia idempotenta.
|
||||
- [x] Toti constructorii de chips (`_prestatii_chips_from_payload`, preview, re-render eroare)
|
||||
propaga `exclus` → chip `Nedeclarat` reapare la redeschidere; `×` il readuce la warning.
|
||||
- [x] Rand cu toate operatiile excluse → stare preview `excluded` ("Nedeclarat"); la Trimiteri →
|
||||
`needs_data` cu motiv explicit (invariantele existente).
|
||||
- [x] Validarea cod-vs-nomenclator la salvare ramane; sentinelul `__NEDECLARAT__` e RESPINS
|
||||
explicit daca apare ca `cod_prestatie` in starea hidden (nu doar in select), pe ambele cai.
|
||||
- [x] `python3 -m pytest -q` trece.
|
||||
- **Verificare E2E**: acoperit de US-005.
|
||||
|
||||
### US-005: Verificare E2E browser pe ambele fluxuri
|
||||
**Ca** lead **vreau** dovada in browser ca editarea e acum un singur control coerent
|
||||
**pentru ca** bug-ul raportat e de UX vizual, nu doar de logica.
|
||||
|
||||
- **Depinde de**: US-002, US-003, US-004
|
||||
- **Fisiere**: — (doar verificare; eventuale fix-uri in fisierele de mai sus)
|
||||
- **Test intai (RED)**: —
|
||||
- **Acceptance criteria**:
|
||||
- [x] Import preview: rand cu operatie nemapata + cod liber → modalul arata UN rand de chips
|
||||
(warning + cod + select), fara chenare/zone separate; screenshot atasat la raport.
|
||||
- [x] Selectarea unui cod din Sugestii transforma chip-ul warning in chip cod, instant, fara "+".
|
||||
- [x] Alegerea "Nu se declara la RAR" produce chip `Nedeclarat`; salvare → rand "Nedeclarat".
|
||||
- [x] Trimiteri (`/trimitere/{id}` corectie): acelasi control, "salveaza ca regula" functional.
|
||||
- [x] Salvarea persista chips-urile (regresie c6e29aa) si nu reapar controale duble la re-deschidere.
|
||||
- **Verificare E2E**: browser HTMX pe http://localhost:8010/ (import xlsx test + o trimitere
|
||||
`needs_mapping`), conform pasilor de mai sus.
|
||||
|
||||
## 5. Riscuri
|
||||
|
||||
- **`hx-disabled-elt` mostenit de la form** a mai rupt butoanele chips o data (5.22) — selectul unic
|
||||
primeste explicit `hx-disabled-elt="this"`; test de template pentru atribut.
|
||||
- **`change` pe select nativ** se declanseaza si la navigarea cu tastatura pe unele browsere vechi;
|
||||
acceptat (comportamentul modern e la confirmare), iar chip-ul gresit se sterge cu un click.
|
||||
- **Compatibilitate stare veche**: submission-uri/override-uri existente fara `chip_exclus` trebuie
|
||||
citite ca `exclus=0` (default la deserializare) — altfel salvarea de pe randuri vechi crapa.
|
||||
- **Doua endpoint-uri de save** (import + Trimiteri) cu acelasi contract de campuri — orice
|
||||
schimbare de nume de camp se face simultan in ambele, plus `_chips_state_from_form` unic partajat.
|
||||
|
||||
## 6. Intrebari deschise
|
||||
|
||||
> Rezolvate cu utilizatorul la aprobare (2026-07-07):
|
||||
|
||||
- `×` pe un chip `Nedeclarat` → **readuce chip-ul warning** al operatiei (operatia NU se sterge).
|
||||
- Selectul unic ramane `<select>` nativ cu tot nomenclatorul — **acceptat**; autocomplete ramane
|
||||
non-goal.
|
||||
|
||||
## 7. Valuri de executie (graful de dependente)
|
||||
|
||||
```
|
||||
Val 1: [US-001] ← fara dependente
|
||||
Val 2: [US-002] [US-004] ← deblocate de US-001, fisiere distincte → paralel
|
||||
Val 3: [US-003] ← deblocat de US-002
|
||||
Val 4: [US-005] ← verificare finala E2E
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Anexa /autoplan — Review CEO (Faza 1, 2026-07-07, mod SELECTIVE EXPANSION, voci: subagent-only)
|
||||
|
||||
### NOT in scope (decizii din review)
|
||||
- Autocomplete/cautare in picker — non-goal confirmat de utilizator.
|
||||
- Schimbarea panoului de mapare / precedentei `resolve_prestatii` — non-goal PRD.
|
||||
- Badge-uri sursa sugestie (GOLD/SILVER) in select — respins (P5): optgroup "Sugestii" e suficient.
|
||||
- "Bulk: aplica maparea la N randuri identice" in preview — deferat TODOS (in afara blast radius).
|
||||
- Metrici de baza (deschideri modal / % randuri mapate manual) — deferat TODOS.
|
||||
|
||||
### What already exists (refolosit, nu reconstruit)
|
||||
- Stare chips in hidden inputs + `/form-chips` stateless (`_chips_prestatii.html`, `routes.py:2402`).
|
||||
- Validare cod vs nomenclator la add si la save (c6e29aa); mesaje `chips_error`/`chips_ok` (5.22).
|
||||
- Sugestii: `suggest_codes` + `sugestie_principala` (`mapping.py:117,868`) — aceleasi surse ca preview.
|
||||
- `EXCLUDE_SENTINEL`/`split_prestatii_excluse` (0bc6187); `apply_row_override` (c6e29aa).
|
||||
- Empty state nomenclator lipsa; `has_r_odo`/reveal odometru; buton "salveaza ca regula" (Trimiteri).
|
||||
|
||||
### Dream state delta
|
||||
Planul muta editorul de la 3 controale/4 actiuni la 1 control/2 actiuni si aduce sugestiile in
|
||||
modal — pas direct spre idealul 12 luni (o singura componenta de mapare peste tot). Ramase pe
|
||||
traiectorie: autocomplete (daca nomenclatorul creste), componenta partajata cu panoul de mapare.
|
||||
|
||||
### Arhitectura (Sectiunea 1)
|
||||
```
|
||||
[modal Editare rand]──include──▶[_form_editare]──include──▶[_chips_prestatii (REWRITE)]
|
||||
│ │ hx-post change/×
|
||||
▼ ▼
|
||||
POST editeaza (import) ─┐ POST /form-chips (add|remove)
|
||||
POST corecteaza (Trim.) ─┴─▶ _chips_state_from_form ──▶ apply_row_override(prestatii+exclus)
|
||||
│ (commit)
|
||||
suggest_codes/mapping.py ◀──────┘ resolve + split_prestatii_excluse
|
||||
```
|
||||
Cuplaj NOU: /form-chips → motorul de sugestii (pana acum doar preview-ul) — justificat (reuse),
|
||||
cu conditia caii NE-blocante pe request (lock embeddings non-blocking, PRD 5.21). Fara migrare DB
|
||||
(`exclus` traieste in override_json). Rollback: git revert simplu. Fereastra de deploy: un modal
|
||||
deschis peste deploy trimite campuri vechi (`chips_add_cod_{i}`) pe care handlerul nou le ignora
|
||||
— utilizatorul redeschide editarea; acceptat (sesiune de editare scurta).
|
||||
|
||||
### Registru erori & rescue (Sectiunea 2)
|
||||
```
|
||||
CODEPATH | CE POATE ESUA | TRATARE | USER VEDE
|
||||
--------------------------|------------------------------------|--------------------------------|-----------
|
||||
/form-chips add | cod gol / necunoscut | chips_error (exista) | mesaj rosu
|
||||
/form-chips add | index tinta iesit din forma | chips_error (exista) | mesaj rosu
|
||||
/form-chips add NED | __NEDECLARAT__ fara tinta | GAP -> chips_error nou | mesaj rosu
|
||||
/form-chips render | motor sugestii rece/dezactivat | GAP -> degradare la nomenclator| select fara optgroup
|
||||
/form-chips | nomenclator gol | empty state (exista) | avertisment
|
||||
save (ambele) | cod necunoscut in stare | re-render cu eroare (exista) | modal + eroare
|
||||
save (ambele) | stare veche fara chip_exclus | default exclus=0 (PRD risc) | nimic (compat)
|
||||
save (ambele) | CSRF invalid/expirat | 403 (existent) | eroare form
|
||||
enqueue | toate operatiile excluse | excluded / needs_data (exista) | stare explicita
|
||||
```
|
||||
GAP-uri de inchis in implementare: NED-fara-tinta si degradarea sugestiilor (ambele = criterii noi).
|
||||
|
||||
### Securitate (Sectiunea 3)
|
||||
Fara endpoint nou. Input nou `chip_exclus`: parsare stricta ("1" -> 1, orice altceva -> 0).
|
||||
Sentinelul `__NEDECLARAT__` nu ajunge NICIODATA in payload RAR (split inainte de enqueue —
|
||||
invariant existent; test explicit). XSS: `denumire` in options/title — autoescape Jinja (exista).
|
||||
Sugestii cross-account: politica 5.14 (suggestion-only) neschimbata. CSRF pe /form-chips: exista.
|
||||
Nicio amenintare High nemitigata.
|
||||
|
||||
### Edge cases date/interactiune (Sectiunea 4)
|
||||
- Dublu `change` in zbor: `hx-disabled-elt="this"` pe select (criteriu US-002).
|
||||
- Focus pierdut dupa swap `outerHTML`: focusul revine pe select dupa add (criteriu nou US-002).
|
||||
- Navigare cu tastatura prin select declanseaza change pe unele browsere: acceptat (PRD risc),
|
||||
chip-ul gresit se scoate cu un click.
|
||||
- Iesire din modal mid-edit: stare pierduta (comportament existent, form-local) — neschimbat.
|
||||
- Dedup (operatie,cod) si cod liber duplicat: exista, se pastreaza.
|
||||
- Mod plat + `has_r_odo`: pastrate explicit (spec actualizat).
|
||||
|
||||
### Calitate (Sectiunea 5)
|
||||
4 actiuni -> 2 reduce ramificarea din `post_form_chips` (~120 linii azi). `_chips_state_from_form`
|
||||
ramane UNICUL parser, partajat de ambele endpoint-uri de save (risc PRD; test comun).
|
||||
Alternativa "un singur camp hidden JSON" in loc de 4 liste paralele — decizie de gust (vezi gate).
|
||||
|
||||
### Teste (Sectiunea 6) — completari la RED-urile din PRD
|
||||
- `test_add_nedeclarat_fara_tinta_da_eroare` (US-001)
|
||||
- `test_sugestii_indisponibile_degradare_fara_optgroup` (US-003)
|
||||
- `test_mod_plat_fara_operatii_select_doar_coduri_libere` (US-002)
|
||||
- `test_has_r_odo_pastrat_dupa_actiuni` (US-002)
|
||||
- `test_sentinel_nedeclarat_nu_ajunge_in_payload` (US-004)
|
||||
- Template: `test_select_are_hx_disabled_elt_this` (capcana 5.22)
|
||||
|
||||
### Performanta (Sectiunea 7)
|
||||
Sugestiile se calculeaza la FIECARE re-render /form-chips pentru operatia tinta — bounded
|
||||
(matvec numpy 5.21, corpus 17k, <100ms tipic); calea request NU asteapta warmup (lock non-blocking).
|
||||
18 optiuni in select — trivial. Fara probleme.
|
||||
|
||||
### Observabilitate (Sectiunea 8)
|
||||
Starea randurilor e deja vizibila in dashboard (needs_mapping/excluded). Gap acceptat si deferat
|
||||
TODOS: metrica "deschideri modal / sugestie acceptata" (leaga de decizia de produs, nu de 5.23).
|
||||
|
||||
### Deploy (Sectiunea 9)
|
||||
Fara migrare, fara feature flag (UI inlocuit atomic in acelasi template). Worker neatins.
|
||||
Post-deploy: smoke = deschide un rand needs_mapping, adauga un cod, salveaza.
|
||||
|
||||
### Traiectorie (Sectiunea 10)
|
||||
Reversibilitate 4/5 (revert curat; starile cu exclus persistate raman valide — split exista
|
||||
independent). Datorie REDUSA (actiuni duplicate eliminate). Comentariul de header din
|
||||
`_chips_prestatii.html` se rescrie (diagrama noua a starii — parte din US-002).
|
||||
|
||||
### Design/UX la nivel CEO (Sectiunea 11)
|
||||
Ierarhie: chips (starea) inaintea selectului (actiunea) — corect. Stari acoperite: gol (hint),
|
||||
nomenclator lipsa, eroare, succes, exclus. Mobil: rand cu wrap; latimea selectului pe 390px =
|
||||
criteriu in faza Design. Accesibilitate: aria-labels existente se pastreaza; focus dupa swap.
|
||||
Riscul de mis-binding (tinta implicita + fara text permanent) — ridicat de vocea externa,
|
||||
decizie la gate. Detaliu complet in Faza 2 (plan-design-review).
|
||||
|
||||
### Decision Audit Trail (/autoplan)
|
||||
|
||||
| # | Faza | Decizie | Clasificare | Principiu | Rationament | Respins |
|
||||
|---|------|---------|-------------|-----------|-------------|---------|
|
||||
| 1 | CEO | Abordare A (rescriere template + unificare actiuni) | Mecanica | P1,P5 | B pastreaza confuzia; C incalca non-goal aprobat | B, C |
|
||||
| 2 | CEO | A11y tastatura + focus dupa swap → criterii US-002 | Mecanica | P1 | in blast radius, 2 fisiere | — |
|
||||
| 3 | CEO | Badge sursa sugestie in select → respins | Mecanica | P5 | optgroup "Sugestii" spune deja asta | badge-uri |
|
||||
| 4 | CEO | Bulk-apply mapare pe randuri identice → TODOS | Mecanica | P2,P3 | in afara blast radius (panou preview = non-goal) | — |
|
||||
| 5 | CEO | Metrici deschideri-modal/sugestii → TODOS | Mecanica | P3 | produs, nu 5.23 | — |
|
||||
| 6 | CEO | GAP-uri erori (NED-fara-tinta, degradare sugestii) → criterii noi | Mecanica | P1 | zero silent failures | — |
|
||||
| 7 | CEO | Auto-advance tinta + fara eticheta pe select | TASTE (gate) | — | voce externa: risc mis-binding pe declaratii ireversibile | — |
|
||||
| 8 | CEO | Paleta click 18 coduri vs select nativ | TASTE (gate) | — | premisa "tot nomenclatorul" e falsa (18 coduri fixe) | — |
|
||||
| 9 | CEO | Stare chips: 4 liste hidden vs un camp JSON | TASTE (gate) | — | a 3-a regresie in zona; JSON = sursa unica | — |
|
||||
| 10 | CEO | Test cu operator real in US-005 | Mecanica | P1 | verifica direct riscul de mis-binding; cost ~0 | — |
|
||||
| 11 | Design | Scara de dominanta vizuala (tabel §3.1) | Mecanica | P1,P5 | 3 tratamente concurente fara ierarhie = confuzia initiala reambalata | — |
|
||||
| 12 | Design | Stari loading/eroare/gol/complet specificate (tabel §3.1) | Mecanica | P1 | eroarea de retea lasa selectul disabled permanent altfel | — |
|
||||
| 13 | Design | Tinta invarianta (exista cat sunt nemapate; click pe tinta = no-op) | Mecanica | P5 | interzice starea "cod liber cu warnings inca pe rand" tacuta | — |
|
||||
| 14 | Design | Comutare tinta CLIENT-SIDE (fara round-trip) | Mecanica | P3 | round-trip pe click de highlight = lent + race cu add-ul in zbor | server-side |
|
||||
| 15 | Design | Ordine chips stabila + focus pe select dupa swap | Mecanica | P1 | reflow-ul + focusul pierdut rup fluxul de tastatura pe 10 randuri | — |
|
||||
| 16 | Design | Optiuni `COD — denumire`; chip warning ellipsis 18ch | Mecanica | P5 | 18 coduri criptice fara denumire = memorare fortata | — |
|
||||
| 17 | Design | Nedeclarat vizibil (line-through, nu mut) + mesaj cu numele operatiei | Mecanica | P1 | cea mai riscanta actiune legala nu poate fi cea mai discreta | stil mut |
|
||||
| 18 | Design | Mesaje chips_ok/err raman pattern 5.22 (persista pana la actiune) | Mecanica | P4 | timer de auto-dismiss = JS nou fara valoare | timer 4s |
|
||||
| 19 | Design | Auto-advance tinta: pastrat cu emfaza vs eliminat | TASTE (gate) | — | CEO-voice: elimina (mis-binding); Design-voice: pastreaza (momentum) + emfaza | — |
|
||||
| 20 | Design | Touch targets × pe chips sub 44px (UI compact 11px existent) | Mecanica | P4 | consecvent cu design-ul compact existent; nota in TODOS a11y | 44px |
|
||||
| 21 | Eng | `exclus` supravietuieste `resolve_prestatii` (treapta noua, non-goal amendat) | Mecanica | P1 | P1 confirmat: `it.pop("exclus")` mapping.py:298 face US-004 imposibil; alternativa (regula pe cont) schimba semantica aprobata | regula per-cont; taierea US-004 |
|
||||
| 22 | Eng | Itemii exclusi raman in payload_json + `exclus` in toti constructorii de chips | Mecanica | P1 | P1 confirmat: routes.py:1744 ii elimina → × pe Nedeclarat imposibil dupa salvare | — |
|
||||
| 23 | Eng | `chips_submission_id` hidden + ecou in /form-chips ("salveaza ca regula" persistent) | Mecanica | P1,P3 | butonul dispare azi la prima re-randare; fluxul nou trece TOT prin /form-chips | — |
|
||||
| 24 | Eng | `chips_pick` reset dupa orice raspuns; valoarea NEaplicata la Salveaza | Mecanica | P5 | clasa de bug c6e29aa; sentinel NED pozitional pe calea corectie | aplicare implicita |
|
||||
| 25 | Eng | Validare semantica tinta + chip_exclus strict + sectiune inerta in request | Mecanica | P1 | POST fabricat / index stale / race mid-flight leaga codul de alt item | — |
|
||||
| 26 | Eng | `chips_present=1` — golirea listei se persista (nu no-op) | Mecanica | P1 | `_chips_state_from_form` intoarce None pe form fara chips → golire imposibila | no-op actual |
|
||||
| 27 | Eng | Actualizarea testelor existente pe actiunile vechi in acelasi val | Mecanica | P6 | altfel suita e rosie intre Val 1 si Val 2 | — |
|
||||
| 28 | Eng | Emfaza chip nou pe index, nu pe cod | Mecanica | P5 | doua operatii cu acelasi cod = dubla evidentiere falsa | — |
|
||||
| 29 | Eng | Fara memoizare sugestii per re-render | Mecanica | P3 | rapidfuzz pe 18 randuri + k-NN gardat = bounded; memoizarea = complexitate fara castig | memoizare |
|
||||
| D2 | Gate | Auto-advance pastrat + placeholder-ul selectului numeste tinta | USER | — | leaga explicit codul de operatie la momentul alegerii, fara text sub control | doar emfaza; fara auto-advance |
|
||||
| D3 | Gate | Select nativ pastrat (mockup v1); paleta v2 vazuta si RESPINSA de utilizator | USER | — | directia utilizatorului; paleta ramane alternativa daca selectul se dovedeste lent (US-005 cu operator) | paleta 18 coduri |
|
||||
| D4 | Gate | Stare chips = UN hidden `chips_state` JSON versionat + fallback tranzitoriu liste vechi | USER | — | elimina clasa de regresii de aliniere pe index | 4 liste paralele |
|
||||
| D5 | Gate | Plan revizuit APROBAT de utilizator | USER | — | — | — |
|
||||
|
||||
### Anexa Eng (Faza 3) — diagrama de acoperire teste
|
||||
|
||||
```
|
||||
CODE PATHS USER FLOWS
|
||||
[+] post_form_chips (rescris, 2 actiuni) [+] Corectie rand cu N operatii nemapate
|
||||
├── add cu tinta valida [RED planificat] ├── sugestie→chip instant [RED + E2E US-005]
|
||||
├── add fara tinta (liber) [RED planificat] ├── comutare tinta client [E2E US-005]
|
||||
├── add NED cu/fara tinta [RED planificat] ├── × pe cod → warning [RED planificat]
|
||||
├── tinta semantica invalida [RED planificat] ├── NED → Nedeclarat → × [RED planificat]
|
||||
├── remove index/liber [RED planificat] ├── eroare retea la add [→E2E] [GAP inchis §3.1]
|
||||
└── dedup + cod necunoscut [exista, se muta] └── golire totala + salvare [RED chips_present]
|
||||
[+] resolve_prestatii treapta item-exclus [RED planificat — REGRESIUNE potentiala: IRON RULE,
|
||||
test obligatoriu ca exclus NU e sters si ca restul precedentei ramane identic]
|
||||
[+] _chips_state_from_form (4 liste + compat vechi + liste scurte) [RED planificat]
|
||||
[+] save x2 (import/corectie): exclus round-trip, NED respins, chips_pick ignorat [RED planificat]
|
||||
[+] template: hx-atribute, focus, sectiune inerta, mod plat, has_r_odo [RED planificat]
|
||||
COVERAGE planificata: 100% din ramurile noi au RED numit in stories. Fara eval-uri LLM (nu se
|
||||
ating prompturi). Regresiune-cheie: precedenta resolve_prestatii → test dedicat blocant.
|
||||
```
|
||||
|
||||
ENG DUAL VOICES — CONSENS (subagent-only):
|
||||
```
|
||||
Dimensiune Claude Codex Consens
|
||||
1. Arhitectura solida? DA* N/A OK (directia 4→2 actiuni corecta; * dupa fix P1-uri)
|
||||
2. Acoperire teste suficienta? NU→DA N/A FLAGGED→INCHIS (6 teste noi adaugate in stories)
|
||||
3. Riscuri de performanta? NU N/A OK (18 coduri, k-NN gardat, bounded)
|
||||
4. Securitate acoperita? PARTIAL→DA N/A INCHIS (validare semantica tinta, chip_exclus strict, NED respins la save)
|
||||
5. Drumuri de eroare tratate? PARTIAL→DA N/A INCHIS (§3.1 + criterii noi)
|
||||
6. Risc de deploy gestionabil? DA N/A OK (fara migrare; fereastra modal-peste-deploy acceptata)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Raport VERIFY
|
||||
|
||||
Verificator independent (subagent context curat, doar PRD + §5.6), 2026-07-08. **VERDICT: PASS**
|
||||
pe toate criteriile testabile; regresia de aur RAR live = NEPROBAT (contul QA fara creds RAR).
|
||||
|
||||
- **Suita**: `python3 -m pytest -q` → verde. La VERIFY: 1672 passed, 1 skipped. Dupa fix-urile
|
||||
de code-review CLOSE: **1680 passed, 1 skipped** (+45 teste noi fata de baseline 1635).
|
||||
- **US-001..US-004** PASS cu dovezi de cod + teste (chips_state JSON unic, 2 actiuni add/remove,
|
||||
exclus supravietuieste resolve_prestatii cu test de regresie blocant pe precedenta, sentinel
|
||||
respins pe ambele cai).
|
||||
- **US-005 E2E browser** (import preview + Trimiteri corectie, Playwright): PASS pe (a) un singur
|
||||
rand de chips cu tinta evidentiata + placeholder-tinta + optgroup Sugestii + optiune "Nu se
|
||||
declara la RAR" la deschiderea initiala; (b) sugestie→chip cod instant fara "+"; (c) Nedeclarat
|
||||
→ salvare → rand "Nedeclarat", redeschidere arata chipul, × readuce warning; (d) acelasi control
|
||||
la Trimiteri, "salveaza ca regula" persistent; (e) fara controale duble, zero erori JS de flux.
|
||||
- **Gap prins in executie** (dupa US-003, reparat inainte de VERIFY): tinta implicita + sugestiile
|
||||
nu se calculau la randarea initiala a modalului — helper partajat `_chips_target_si_sugestii`
|
||||
in toate caile de randare.
|
||||
|
||||
### Code-review CLOSE (high, 8 unghiuri + verify) — 1 BLOCKER de siguranta + 2 findings, toate reparate
|
||||
|
||||
- **BLOCKER (CONFIRMED, reparat)**: itemii exclusi (`exclus=1`, cod null) ramaneau in `payload_json`
|
||||
pe calea de corectie, iar worker-ul ii trimitea la RAR ca `{codPrestatie: null}` → risc
|
||||
ORA-12899 / FINALIZATA partial ireversibil. Nimic nu facea `split_prestatii_excluse` la MOMENTUL
|
||||
trimiterii. Fix la chokepoint unic (worker, inainte de `build_rar_payload`) — acopera toate
|
||||
canalele (API/import/corectie) — + filtru defensiv in `build_rar_payload` (sare itemii exclusi /
|
||||
fara cod) + teste care dovedesc ca itemul exclus NU apare in payload-ul RAR.
|
||||
- **Consistenta round-trip (reparat)**: `commit_import`/`web_confirma_import` pastreaza acum
|
||||
prestatiile complete in `payload_json` (cheia idempotentei = doar declarabile, identica cu
|
||||
preview); bonus: bug latent de idempotenta preview↔commit in `_web_compute_preview` reparat.
|
||||
- **Conventii CLAUDE.md (reparat)**: referinte PRD/US-xxx/decizii scoase din comentariile noi.
|
||||
- **Robustete tinta (reparat)**: un item fara `cod_op_service` nu mai poate deveni tinta
|
||||
invizibila; sugestiile intorc gol pe denumire goala.
|
||||
|
||||
## GSTACK REVIEW REPORT
|
||||
|
||||
| Review | Trigger | Why | Runs | Status | Findings |
|
||||
|--------|---------|-----|------|--------|----------|
|
||||
| CEO Review | `/plan-ceo-review` | Scope & strategy | 1 | CLEAR (PLAN via /autoplan) | 5 proposals, 2 accepted, 3 deferred; 8 constatari voce externa |
|
||||
| Codex Review | `/codex review` | Independent 2nd opinion | 0 | UNAVAILABLE (usage limit pana 2026-07-18) | voci = subagent-only |
|
||||
| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | CLEAR (PLAN via /autoplan) | 8 issues (4×P1 confirmate in cod), 0 critical gaps ramase |
|
||||
| Design Review | `/plan-design-review` | UI/UX gaps | 1 | CLEAR (FULL via /autoplan) | score: 6/10 → 9/10, 10 decizii in §3.1 |
|
||||
| DX Review | `/plan-devex-review` | Developer experience gaps | 0 | SKIPPED | fara scope developer-facing |
|
||||
|
||||
- **CROSS-MODEL:** indisponibil (Codex la limita de utilizare); toate vocile externe = subagenti
|
||||
Claude independenti (context curat, fara review-ul anterior).
|
||||
- **VERDICT:** CEO + ENG + DESIGN CLEARED — gata de implementare. Decizii gate aplicate:
|
||||
D2 placeholder-tinta, D3 select (paleta respinsa), D4 stare JSON, D5 aprobat.
|
||||
|
||||
NO UNRESOLVED DECISIONS
|
||||
|
||||
Reference in New Issue
Block a user