feat(5.9): US-004 - detaliu editabil in-place + butoane consolidate in modal
- _trimitere_detaliu.html rescris pe ordinea verticala R10: header+motiv, eroare blocanta, mapare inline, formular editabil/context read-only, actiuni, <details> Detalii tehnice colapsat - zero dublare: campurile editabile apar O SINGURA DATA (nr rand propriu, VIN dedesubt, restul in grila); blocul read-only de grila contopit cu formularul - R9: operatie+cod read-only deasupra campurilor cu prez.cod_rar (fallback nemapat), fara eticheta separata Cod RAR - R2 (fix F7): buton primar conditionat de stare - error->Re-pune(/repune), needs_data/needs_mapping->Salveaza si retrimite(/corecteaza); duplicatul gol eliminat - R11: un singur Sterge outline var(--err) pe rand separat, hx-confirm specific, full-width pe mobil (.detaliu-actiuni-jos in @media 767px, base.html) - R5: hx-disabled-elt pe toate formele; inchidere pe succes prin inchideModal - R4: script modal-appropriate pastrat (curatat de US-003) - 6 teste noi in test_web_corectie.py; rutele + _detaliu_ctx NESCHIMBATE - suita 835 passed (-m 'not live'); prd.json/progress.txt US-004 passes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,72 +1,36 @@
|
||||
{% from "_eroare.html" import card_erori %}
|
||||
{% import '_macros.html' as ui %}
|
||||
{# PRD 5.9 US-003: fragmentul se swap-uieste in corpul modalului global
|
||||
(#detaliu-modal-body). Heading-ul poarta id-ul folosit de aria-labelledby al dialogului. #}
|
||||
{# PRD 5.9 US-004: detaliu editabil in-place, butoane consolidate, ordine verticala R10.
|
||||
Fragmentul se swap-uieste in corpul modalului global (#detaliu-modal-body). Heading-ul
|
||||
poarta id-ul folosit de aria-labelledby al dialogului.
|
||||
R9: operatie + cod RAR rezolvat apar IMPREUNA, read-only, folosind `prez.cod_rar`
|
||||
(fallback „nemapat"), fara eticheta separata „Cod RAR". #}
|
||||
{% set cod_afis = prez.cod_rar if (prez.cod_rar and prez.cod_rar != '—') else 'nemapat' %}
|
||||
<div class="card" id="detaliu-card-{{ id }}" style="border:none; padding:0; margin:0;">
|
||||
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0 0 12px;">
|
||||
|
||||
{# === R10 (1): header — #id + pill + motiv uman === #}
|
||||
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0 0 8px;">
|
||||
<h2 id="detaliu-modal-titlu" style="font-size:15px; margin:0;">Detaliu trimitere #{{ id }}</h2>
|
||||
<span class="pill {{ stare_css }}">{{ stare_text }}</span>
|
||||
</div>
|
||||
|
||||
{% if stare_subtext %}
|
||||
{% if motiv %}
|
||||
<p class="muted" style="margin:0 0 12px; font-size:13px;">{{ motiv }}</p>
|
||||
{% elif stare_subtext %}
|
||||
<p class="muted" style="margin:0 0 12px; font-size:13px;">{{ stare_subtext }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); gap:12px 24px;">
|
||||
<div><div class="muted" style="font-size:12px;">Numar inmatriculare</div><div>{{ prez.vehicul_nr }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">VIN (serie sasiu)</div><div style="word-break:break-all;">{{ prez.vin }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Operatie</div><div>{{ prez.operatie }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Cod RAR</div><div>{{ prez.cod }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Data prestatie</div><div>{{ prez.data_prestatie }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Odometru final</div><div>{{ prez.odometru }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Nr. prezentare RAR</div><div>{{ id_prezentare or '—' }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Cod HTTP RAR</div><div>{{ rar_status_code or '—' }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Reincercari</div><div>{{ retry_count }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Creat</div><div>{{ created_at }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Actualizat</div><div>{{ updated_at }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Urmatoarea incercare</div><div>{{ next_attempt_at }}</div></div>
|
||||
</div>
|
||||
|
||||
{# === R10 (2): bloc eroare blocanta cand exista === #}
|
||||
{% if erori_3n %}
|
||||
<div style="margin-top:12px; padding-top:10px; border-top:1px solid var(--line);">
|
||||
<div style="margin:0 0 14px;">
|
||||
{{ card_erori(erori_3n) }}
|
||||
</div>
|
||||
{% elif motiv %}
|
||||
<div style="margin-top:12px; padding-top:10px; border-top:1px solid var(--line);">
|
||||
<div class="muted" style="font-size:12px;">Motiv</div>
|
||||
<div>{{ motiv }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if rar_error %}
|
||||
<details style="margin-top:10px;">
|
||||
<summary class="muted" style="font-size:12px; cursor:pointer;">Mesaj tehnic RAR (integral)</summary>
|
||||
<pre style="white-space:pre-wrap; word-break:break-all; font-size:12px; margin:6px 0 0; color:var(--muted);">{{ rar_error }}</pre>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
{# === Lifecycle (US-011): sterge / re-pune in coada — doar randuri blocate === #}
|
||||
{% if gestionabil %}
|
||||
<div style="margin-top:14px; padding-top:12px; border-top:1px solid var(--line); display:flex; gap:10px; flex-wrap:wrap;">
|
||||
<form hx-post="/trimitere/{{ id }}/repune"
|
||||
hx-target="#detaliu-modal-body" hx-swap="innerHTML" style="margin:0;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button type="submit">Re-pune in coada</button>
|
||||
</form>
|
||||
<form hx-post="/trimitere/{{ id }}/sterge"
|
||||
hx-target="#detaliu-modal-body" hx-swap="innerHTML"
|
||||
hx-confirm="Stergi definitiv aceasta trimitere din coada?" style="margin:0;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button type="submit" style="background:var(--card); color:var(--err); border-color:var(--err);">
|
||||
Sterge
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# === Mapare inline (PRD 5.7): alege cod RAR pentru operatiile nemapate ale acestui rand === #}
|
||||
{# === R10 (3) + R9: mapare inline (PRD 5.7) — alege cod RAR pentru operatiile nemapate.
|
||||
Cand nemapate_inline, linia „Operatie: X · nemapat" apare in formularul de mai jos
|
||||
(cod_afis = nemapat), iar aici e picker-ul; dupa mapare, re-render arata codul rezolvat. === #}
|
||||
{% if nemapate_inline %}
|
||||
<div style="margin-top:14px; padding-top:12px; border-top:1px solid var(--line);">
|
||||
<div style="margin:0 0 14px; padding-bottom:12px; border-bottom:1px solid var(--line);">
|
||||
<h3 style="font-size:14px; margin:0 0 4px;">Mapeaza codul operatiei</h3>
|
||||
<p class="muted" style="margin:0 0 10px; font-size:13px;">
|
||||
Alege codul RAR pentru fiecare operatie. La salvare, randul se re-rezolva pe loc
|
||||
@@ -76,6 +40,7 @@
|
||||
{% set top = op.suggestions[0] if op.suggestions else None %}
|
||||
{% set preselect = top.cod_prestatie if (top and top.score >= 60) else '' %}
|
||||
<form hx-post="/trimitere/{{ id }}/mapeaza" hx-target="#detaliu-modal-body" hx-swap="innerHTML"
|
||||
hx-disabled-elt="find button"
|
||||
style="margin:0 0 12px; padding:10px; border:1px solid var(--line); border-radius:8px;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="hidden" name="cod_op_service" value="{{ op.cod_op_service }}">
|
||||
@@ -111,48 +76,126 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# === Corectie inline (US-010): doar randuri ne-trimise blocate === #}
|
||||
{# === R10 (4): formular editabil (needs_data/needs_mapping) SAU context read-only.
|
||||
Zero dublare: campurile vehiculului apar O SINGURA DATA — editabile cand randul e
|
||||
corectabil, altfel read-only. Operatie + cod RAR read-only deasupra campurilor. === #}
|
||||
{% if editabil %}
|
||||
{% set err_map = {} %}
|
||||
{% for e in corectie_errors %}{% if e.field %}{% set _ = err_map.update({e.field: e.message}) %}{% endif %}{% endfor %}
|
||||
<div style="margin-top:14px; padding-top:12px; border-top:1px solid var(--line);">
|
||||
<h3 style="font-size:14px; margin:0 0 8px;">Corecteaza si re-trimite</h3>
|
||||
|
||||
{% if corectie_msg %}
|
||||
<div class="flash" style="{% if corectie_error %}border-color:var(--err); background:color-mix(in srgb, var(--err) 12%, var(--card));{% endif %} margin-bottom:10px;"
|
||||
{% if corectie_error %}role="alert"{% endif %}>{{ corectie_msg }}</div>
|
||||
{% if corectie_msg %}
|
||||
<div class="flash" style="{% if corectie_error %}border-color:var(--err); background:color-mix(in srgb, var(--err) 12%, var(--card));{% endif %} margin:0 0 12px;"
|
||||
{% if corectie_error %}role="alert"{% endif %}>{{ corectie_msg }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% macro camp(nume, eticheta, valoare, tip='text') %}
|
||||
<div style="margin-bottom:10px;">
|
||||
<label for="c-{{ nume }}" class="muted" style="font-size:12px; display:block;">{{ eticheta }}</label>
|
||||
<input id="c-{{ nume }}" type="{{ tip }}" name="{{ nume }}" value="{{ valoare }}"
|
||||
style="width:100%; {% if err_map.get(nume) %}border-color:var(--err);{% endif %}"
|
||||
{% if err_map.get(nume) %}aria-invalid="true"{% endif %}>
|
||||
{% if err_map.get(nume) %}
|
||||
<div class="s-error" style="font-size:12px; margin-top:2px;">{{ err_map.get(nume) }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<form hx-post="/trimitere/{{ id }}/corecteaza"
|
||||
hx-target="#detaliu-modal-body" hx-swap="innerHTML">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:10px 16px;">
|
||||
<form hx-post="/trimitere/{{ id }}/corecteaza"
|
||||
hx-target="#detaliu-modal-body" hx-swap="innerHTML"
|
||||
hx-disabled-elt="find button">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
|
||||
{% macro camp(nume, eticheta, valoare, tip='text') %}
|
||||
<div>
|
||||
<label for="c-{{ nume }}" class="muted" style="font-size:12px; display:block;">{{ eticheta }}</label>
|
||||
<input id="c-{{ nume }}" type="{{ tip }}" name="{{ nume }}" value="{{ valoare }}"
|
||||
style="width:100%; {% if err_map.get(nume) %}border-color:var(--err);{% endif %}"
|
||||
{% if err_map.get(nume) %}aria-invalid="true"{% endif %}>
|
||||
{% if err_map.get(nume) %}
|
||||
<div class="s-error" style="font-size:12px; margin-top:2px;">{{ err_map.get(nume) }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{# Operatie + cod RAR read-only deasupra campurilor (R9, fara eticheta „Cod RAR"). #}
|
||||
<div style="margin:0 0 12px;">
|
||||
<div class="muted" style="font-size:12px;">Operatie</div>
|
||||
<div>{{ prez.operatie }} · {{ cod_afis }}</div>
|
||||
</div>
|
||||
|
||||
{{ camp('nr_inmatriculare', 'Numar inmatriculare', form_nr) }}
|
||||
{{ camp('vin', 'VIN (serie sasiu)', form_vin) }}
|
||||
{{ camp('data_prestatie', 'Data prestatie (YYYY-MM-DD)', form_data) }}
|
||||
{{ camp('odometru_final', 'Odometru final', form_odo_final) }}
|
||||
{{ camp('odometru_initial', 'Odometru initial (daca e cerut)', form_odo_initial) }}
|
||||
</div>
|
||||
<div style="margin-top:10px;">
|
||||
<button type="submit">Salveaza si re-pune in coada</button>
|
||||
</div>
|
||||
</form>
|
||||
{# Nr. inmatriculare pe rand propriu, VIN dedesubt — ambele latime plina. #}
|
||||
{{ camp('nr_inmatriculare', 'Numar inmatriculare', form_nr) }}
|
||||
{{ camp('vin', 'VIN (serie sasiu)', form_vin) }}
|
||||
|
||||
{# Restul campurilor in grila. #}
|
||||
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:0 16px;">
|
||||
{{ camp('data_prestatie', 'Data prestatie (YYYY-MM-DD)', form_data) }}
|
||||
{{ camp('odometru_final', 'Odometru final', form_odo_final) }}
|
||||
{{ camp('odometru_initial', 'Odometru initial (daca e cerut)', form_odo_initial) }}
|
||||
</div>
|
||||
|
||||
{# === R10 (5): actiune primara conditionata de stare (R2). needs_data/needs_mapping
|
||||
-> „Salveaza si retrimite" pe /corecteaza. UN SINGUR buton primar per stare. === #}
|
||||
<div style="margin-top:14px;">
|
||||
<button type="submit">Salveaza si retrimite</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
{# Context read-only pentru randuri ne-editabile (sent/sending/queued/error). #}
|
||||
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); gap:12px 24px;">
|
||||
<div style="grid-column:1 / -1;">
|
||||
<div class="muted" style="font-size:12px;">Numar inmatriculare</div><div>{{ prez.vehicul_nr }}</div>
|
||||
</div>
|
||||
<div style="grid-column:1 / -1;">
|
||||
<div class="muted" style="font-size:12px;">VIN (serie sasiu)</div>
|
||||
<div style="word-break:break-all;">{{ prez.vin }}</div>
|
||||
</div>
|
||||
<div><div class="muted" style="font-size:12px;">Operatie</div><div>{{ prez.operatie }} · {{ cod_afis }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Data prestatie</div><div>{{ prez.data_prestatie }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Odometru final</div><div>{{ prez.odometru }}</div></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# === R10 (5): actiuni de jos — primar Re-pune (doar error) + Sterge pe RAND SEPARAT (R2/R11) === #}
|
||||
{% if status == 'error' or gestionabil %}
|
||||
<div class="detaliu-actiuni-jos" style="margin-top:14px; padding-top:12px; border-top:1px solid var(--line);">
|
||||
{# R2: error -> buton primar „Re-pune in coada" pe /repune (error nu e editabil). #}
|
||||
{% if status == 'error' %}
|
||||
<form hx-post="/trimitere/{{ id }}/repune"
|
||||
hx-target="#detaliu-modal-body" hx-swap="innerHTML"
|
||||
hx-disabled-elt="find button" style="margin:0 0 10px;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button type="submit">Re-pune in coada</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{# R11: UN SINGUR Sterge, outline distructiv (var(--err)), pe rand separat, full-width pe mobil. #}
|
||||
{% if gestionabil %}
|
||||
<form hx-post="/trimitere/{{ id }}/sterge"
|
||||
hx-target="#detaliu-modal-body" hx-swap="innerHTML"
|
||||
hx-disabled-elt="find button"
|
||||
hx-confirm="Stergi definitiv trimiterea #{{ id }}? Nu se poate anula." style="margin:0;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button type="submit" class="btn-sterge"
|
||||
style="background:var(--card); color:var(--err); border-color:var(--err);">
|
||||
Sterge
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# === R10 (6): Detalii tehnice — colapsat implicit === #}
|
||||
<details style="margin-top:14px;">
|
||||
<summary class="muted" style="font-size:12px; cursor:pointer;">Detalii tehnice</summary>
|
||||
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:10px 24px; margin-top:10px;">
|
||||
<div><div class="muted" style="font-size:12px;">Nr. prezentare RAR</div><div>{{ id_prezentare or '—' }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Cod HTTP RAR</div><div>{{ rar_status_code or '—' }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Reincercari</div><div>{{ retry_count }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Creat</div><div>{{ created_at }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Actualizat</div><div>{{ updated_at }}</div></div>
|
||||
<div><div class="muted" style="font-size:12px;">Urmatoarea incercare</div><div>{{ next_attempt_at }}</div></div>
|
||||
{% if erori_3n and erori_3n[0].cod %}
|
||||
<div><div class="muted" style="font-size:12px;">Cod eroare (brut)</div><div>{{ erori_3n[0].cod }}</div></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if rar_error %}
|
||||
<div style="margin-top:10px;">
|
||||
<div class="muted" style="font-size:12px;">Mesaj RAR (integral)</div>
|
||||
<pre style="white-space:pre-wrap; word-break:break-all; font-size:12px; margin:4px 0 0; color:var(--muted);">{{ rar_error }}</pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
</details>
|
||||
</div>
|
||||
{# PRD 5.9 US-003 (R4): focus-ul dupa swap (incl. re-render corectie/mapare) e mutat in
|
||||
corpul modalului din base.html (htmx:afterSettle pe #detaliu-modal-body). Vechiul script
|
||||
inline (marcheazaDetaliuDeschis / scrollIntoView pe randul-sibling) a fost eliminat. #}
|
||||
{# PRD 5.9 US-004 (R4): scriptul inline vechi (marcheazaDetaliuDeschis / scrollIntoView pe
|
||||
randul-sibling) a fost eliminat de US-003. Focus-ul post-swap (incl. re-render corectie/
|
||||
mapare) e gestionat de htmx:afterSettle pe #detaliu-modal-body din base.html. R5: inchiderea
|
||||
modalului pe succes (queued/sterge) vine din HX-Trigger `inchideModal` emis de rute. #}
|
||||
|
||||
@@ -245,6 +245,9 @@
|
||||
padding:16px; padding-top:56px; overflow-y:auto; }
|
||||
.modal-close { width:44px; height:44px; top:8px; right:8px; font-size:24px; }
|
||||
|
||||
/* US-004 (R11): actiunile de jos din detaliu (Re-pune / Sterge) full-width stivuit pe mobil. */
|
||||
.detaliu-actiuni-jos button { width:100%; }
|
||||
|
||||
/* Header + nav colapsate: header se rupe pe linii, fara scroll orizontal de pagina;
|
||||
tintele touch (toggle tema/cont, taburi, itemi meniu cont) cresc la >=44px. */
|
||||
header { padding:12px 16px; flex-wrap:wrap; gap:8px; }
|
||||
|
||||
@@ -159,12 +159,12 @@
|
||||
],
|
||||
"requiresBrowserCheck": true,
|
||||
"requiresDesignReview": true,
|
||||
"passes": false,
|
||||
"passes": true,
|
||||
"failed": false,
|
||||
"blocked": false,
|
||||
"retries": 0,
|
||||
"failureReason": "",
|
||||
"notes": ""
|
||||
"notes": "Implementat in _trimitere_detaliu.html (ordine R10): header+motiv, eroare blocanta, mapare inline, formular editabil cu campuri o singura data (nr rand propriu, VIN dedesubt) + operatie/cod read-only (prez.cod_rar, fallback nemapat), actiuni jos, <details> Detalii tehnice. R2/F7: buton primar conditionat de stare — error->Re-pune(/repune), needs_data/needs_mapping->Salveaza si retrimite(/corecteaza). R11: Sterge outline var(--err) pe rand separat, hx-confirm specific, full-width pe mobil (regula .detaliu-actiuni-jos in @media 767px). R5: hx-disabled-elt pe toate formele; inchidere pe succes prin HX-Trigger inchideModal. _detaliu_ctx si rutele NESCHIMBATE. 6 teste noi in test_web_corectie.py; suita 835 passed (-m 'not live'). Ramane VERIFY browser/design (requiresBrowserCheck/requiresDesignReview)."
|
||||
},
|
||||
{
|
||||
"id": "US-007",
|
||||
|
||||
@@ -96,3 +96,49 @@ Note: PRD APROBAT 2026-06-24 cu revizii obligatorii R1-R12 (raport AUTOPLAN). R1
|
||||
- VERIFY: gstack browser la 375px pe `/` — fara scroll orizontal de pagina; ☰ deschide meniul cont;
|
||||
modal deschis ocupa tot ecranul si se inchide din `x` (requiresBrowserCheck + requiresDesignReview).
|
||||
---
|
||||
|
||||
## US-004: Detaliu editabil in-place + butoane consolidate (in modal), ordine verticala R10
|
||||
|
||||
### Ce s-a livrat:
|
||||
- app/web/templates/_trimitere_detaliu.html REscris pe ordinea verticala R10:
|
||||
(1) header #id + pill + motiv uman; (2) bloc eroare blocanta (card_erori) cand exista;
|
||||
(3) mapare inline (5.7) cand nemapate_inline, tintind #detaliu-modal-body; (4) formular
|
||||
editabil SAU context read-only; (5) actiuni jos; (6) <details> „Detalii tehnice" colapsat.
|
||||
- Zero dublare: blocul read-only de grila (vechiul :19-32) si formularul de corectie s-au
|
||||
contopit. Campurile editabile (nr_inmatriculare, vin, data_prestatie, odometru_final,
|
||||
odometru_initial) apar O SINGURA DATA, ca inputuri pre-completate. Nr. pe rand propriu,
|
||||
VIN dedesubt (ambele latime plina), restul in grila.
|
||||
- R9: operatie + cod RAR rezolvat read-only deasupra campurilor, folosind prez.cod_rar
|
||||
(fallback „nemapat"), fara eticheta separata „Cod RAR" (vechiul :23 eliminat).
|
||||
- R2 (fix F7): buton primar CONDITIONAT DE STARE, unul singur per stare:
|
||||
* error -> „Re-pune in coada" pe /repune (error NU e editabil, /corecteaza ar da 403);
|
||||
* needs_data/needs_mapping -> „Salveaza si retrimite" pe /corecteaza (submit-ul formularului).
|
||||
Butonul gol „Re-pune in coada" duplicat (vechiul :55-59) eliminat.
|
||||
- R11: UN SINGUR „Sterge" (outline distructiv var(--err)) pe RAND SEPARAT (clasa
|
||||
.detaliu-actiuni-jos, NU margin-left:auto), hx-confirm specific
|
||||
„Stergi definitiv trimiterea #{id}? Nu se poate anula."; full-width stivuit pe mobil
|
||||
prin regula noua `.detaliu-actiuni-jos button { width:100% }` in @media 767px (base.html).
|
||||
- R5: hx-disabled-elt="find button" pe toate cele 3 forme (corectie/repune/sterge + mapare);
|
||||
inchiderea modalului pe succes vine din HX-Trigger inchideModal emis de rute (neschimbat).
|
||||
- R4: scriptul inline modal-appropriate (curatat deja de US-003) pastrat; comentariu actualizat.
|
||||
- Detalii tehnice colapsate: Nr. prezentare RAR, Cod HTTP, Reincercari, timestamps, mesaj RAR
|
||||
brut + cod brut (erori_3n[0].cod, doar cand exista).
|
||||
- tests/test_web_corectie.py: 6 teste noi US-004 (test_camp_apare_o_singura_data,
|
||||
test_nr_si_vin_pe_randuri_separate, test_un_singur_buton_primar_per_stare,
|
||||
test_error_foloseste_repune, test_sterge_prezent_si_distinct, test_corectie_pastreaza_comportament).
|
||||
|
||||
### Learnings:
|
||||
- F7 (CRITICAL): consolidarea naiva pe /corecteaza ar fi scos retry-ul randurilor `error`
|
||||
(_CORECTABILE=needs_data/needs_mapping; post_corectie da 403 pe error). Rezolvat prin buton
|
||||
primar conditionat de stare: error pastreaza fluxul existent /repune (re-pune in coada),
|
||||
needs_data/needs_mapping merg pe /corecteaza. Logica backend a rutelor NESCHIMBATA.
|
||||
- _detaliu_ctx neatins (avea deja toate flag-urile necesare: editabil, gestionabil, status,
|
||||
prez.cod_rar, erori_3n). Zero logica noua in routes.py.
|
||||
- test_web_lifecycle.py::test_buton_sterge_doar_pe_blocate ramane verde (error arata
|
||||
„Re-pune in coada", sent nu) — fluxul error->repune e pastrat.
|
||||
|
||||
### Next:
|
||||
- VERIFY (requiresBrowserCheck + requiresDesignReview): gstack browser — needs_data arata
|
||||
fiecare camp o data (nr. rand propriu, VIN dedesubt), corectez data, „Salveaza si retrimite"
|
||||
-> queued; error arata „Re-pune in coada"; „Sterge" clar separat, rosu, full-width pe mobil.
|
||||
---
|
||||
|
||||
@@ -203,3 +203,100 @@ def test_corectie_cont_strain(client):
|
||||
})
|
||||
assert resp.status_code == 404
|
||||
assert _row(sid1)["status"] == "needs_data" # neatins
|
||||
|
||||
|
||||
# =========================================================================== #
|
||||
# US-004 (PRD 5.9): detaliu editabil in-place, zero dublare, butoane consolidate #
|
||||
# =========================================================================== #
|
||||
|
||||
def _fragment(client, sid: int) -> str:
|
||||
resp = client.get(f"/_fragments/trimitere/{sid}")
|
||||
assert resp.status_code == 200
|
||||
return resp.text
|
||||
|
||||
|
||||
def test_camp_apare_o_singura_data(client):
|
||||
"""Zero dublare: fiecare camp editabil apare exact O DATA (input editabil pre-completat),
|
||||
fara blocul read-only de grila duplicat deasupra formularului."""
|
||||
acct = _create_account_user("u1@test.com")
|
||||
sid = _insert(acct, status="needs_data", payload=_payload("WVWZZZ1JZXW0U1001", odo=""))
|
||||
_login(client, "u1@test.com")
|
||||
html = _fragment(client, sid)
|
||||
|
||||
# Fiecare camp editabil apare exact o data, ca input cu name="...".
|
||||
for camp in ("nr_inmatriculare", "vin", "data_prestatie", "odometru_final", "odometru_initial"):
|
||||
assert html.count(f'name="{camp}"') == 1, f"{camp} trebuie sa apara o singura data"
|
||||
# Nu mai exista eticheta separata „Cod RAR".
|
||||
assert "Cod RAR" not in html
|
||||
|
||||
|
||||
def test_nr_si_vin_pe_randuri_separate(client):
|
||||
"""Nr. inmatriculare pe rand propriu, VIN dedesubt — ambele inputuri latime plina,
|
||||
nr. inaintea VIN-ului in markup."""
|
||||
acct = _create_account_user("u2@test.com")
|
||||
sid = _insert(acct, status="needs_data", payload=_payload("WVWZZZ1JZXW0U2001", odo=""))
|
||||
_login(client, "u2@test.com")
|
||||
html = _fragment(client, sid)
|
||||
|
||||
poz_nr = html.find('name="nr_inmatriculare"')
|
||||
poz_vin = html.find('name="vin"')
|
||||
assert poz_nr != -1 and poz_vin != -1
|
||||
assert poz_nr < poz_vin # nr. apare inaintea VIN-ului (rand propriu, VIN dedesubt)
|
||||
|
||||
|
||||
def test_un_singur_buton_primar_per_stare(client):
|
||||
"""R2: needs_data are UN SINGUR buton primar „Salveaza si retrimite" -> /corecteaza."""
|
||||
acct = _create_account_user("u3@test.com")
|
||||
sid = _insert(acct, status="needs_data", payload=_payload("WVWZZZ1JZXW0U3001", odo=""))
|
||||
_login(client, "u3@test.com")
|
||||
html = _fragment(client, sid)
|
||||
|
||||
assert "Salveaza si retrimite" in html
|
||||
assert html.count("Salveaza si retrimite") == 1
|
||||
assert f"/trimitere/{sid}/corecteaza" in html
|
||||
# needs_data NU ofera butonul de re-pune separat (acela e doar pentru error).
|
||||
assert "Re-pune in coada" not in html
|
||||
|
||||
|
||||
def test_error_foloseste_repune(client):
|
||||
"""R2 (fix F7): un rand `error` NU are formular de corectie; primarul „Re-pune in coada"
|
||||
posteaza pe /repune (NU /corecteaza, care ar da 403)."""
|
||||
acct = _create_account_user("u4@test.com")
|
||||
sid = _insert(acct, status="error", payload=_payload("WVWZZZ1JZXW0U4001"))
|
||||
_login(client, "u4@test.com")
|
||||
html = _fragment(client, sid)
|
||||
|
||||
assert "Re-pune in coada" in html
|
||||
assert f"/trimitere/{sid}/repune" in html
|
||||
# error nu e editabil -> fara post pe /corecteaza si fara butonul de salvare.
|
||||
assert f"/trimitere/{sid}/corecteaza" not in html
|
||||
assert "Salveaza si retrimite" not in html
|
||||
|
||||
|
||||
def test_sterge_prezent_si_distinct(client):
|
||||
"""R11: UN SINGUR Sterge, outline distructiv (var(--err)), pe rand separat, cu hx-confirm
|
||||
specific; posteaza pe /sterge."""
|
||||
acct = _create_account_user("u5@test.com")
|
||||
sid = _insert(acct, status="needs_data", payload=_payload("WVWZZZ1JZXW0U5001", odo=""))
|
||||
_login(client, "u5@test.com")
|
||||
html = _fragment(client, sid)
|
||||
|
||||
assert f"/trimitere/{sid}/sterge" in html
|
||||
assert html.count(f"/trimitere/{sid}/sterge") == 1
|
||||
assert "var(--err)" in html # outline distructiv rosu
|
||||
assert f"Stergi definitiv trimiterea #{sid}? Nu se poate anula." in html
|
||||
|
||||
|
||||
def test_corectie_pastreaza_comportament(client):
|
||||
"""Regresie: retry pur (post pe /corecteaza fara modificari) pe needs_data valid ramane
|
||||
idempotent — randul ajunge queued, ca azi (comportament ruta neschimbat)."""
|
||||
acct = _create_account_user("u6@test.com")
|
||||
# needs_data complet valid (toate campurile prezente) -> retry pur il trece in queued.
|
||||
sid = _insert(acct, status="needs_data", payload=_payload("WVWZZZ1JZXW0U6001", odo="55000"))
|
||||
_login(client, "u6@test.com")
|
||||
csrf = _csrf(client)
|
||||
|
||||
resp = client.post(f"/trimitere/{sid}/corecteaza", data={"csrf_token": csrf})
|
||||
assert resp.status_code == 200
|
||||
assert _row(sid)["status"] == "queued"
|
||||
assert resp.headers.get("HX-Trigger-After-Settle") == "trimiteriChanged, inchideModal"
|
||||
|
||||
Reference in New Issue
Block a user