feat(5.16): aliniere lista/preview la mockup + fix lock seed la boot
Implementeaza planul aprobat din docs/raport-comparatie-mockup-5.16.md (T-1..T-9): - T-1/T-8: rand lista 4->2 linii (placuta primar + cod RAR · operatie · data + pill), fallback placuta, eticheta-problema 10px->--fs-xs (_submissions.html, base.html) - T-2: pill slim restilat fill-tint + dot 7px + text colorat per stare (base.html) - T-3: bug 4a coliziune pill/vehicul in preview — col-stare 104->140px (base.html) - T-4: preview 8->5 coloane (scos #, KM, Note; motivul -> title pe pill) - T-5: titlu sectiune "Trimiterile tale" -> sr-only (a11y) + badge/export discret - T-6: linia plan N/60 in corp doar pe avertizare; consum normal in badge+burger - T-7: guard chenar gol chips extra (_chips_prestatii.html) - T-9: "Anuleaza"->"Renunta"; nume operatie emfatic bold Fix boot: init_db reincarca seedul de ~17k operatii (5.18) pe FIECARE pornire, pe API + worker concurent -> "database is locked" la al doilea proces. Guard "_if_empty" pe mapping_suggestions (ca seed_nomenclator_if_empty) -> boot rapid, fara cursa. Teste actualizate (slim 2-linii, fallback placuta, plan in burger). TODOS.md: defer trackuit (eroare HTMX lista, retokenizare px, diacritice). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
23
TODOS.md
23
TODOS.md
@@ -53,3 +53,26 @@ Elemente deferate din review-uri. Negrupte de un PRD curent; de promovat cand de
|
|||||||
- [ ] **US-009/US-010 ca PRD separat daca propagarea design e urgenta** — salvarea mapare-din-chip si
|
- [ ] **US-009/US-010 ca PRD separat daca propagarea design e urgenta** — salvarea mapare-din-chip si
|
||||||
bulk-fix sunt adiacente FUNCTIONALE (acceptate via SELECTIVE EXPANSION), dincolo de obiectivul pur de
|
bulk-fix sunt adiacente FUNCTIONALE (acceptate via SELECTIVE EXPANSION), dincolo de obiectivul pur de
|
||||||
propagare design. Daca vrei sa livrezi designul rapid, pot fi scoase intr-un PRD propriu. (CEO, low.)
|
propagare design. Daca vrei sa livrezi designul rapid, pot fi scoase intr-un PRD propriu. (CEO, low.)
|
||||||
|
|
||||||
|
## Din raport comparatie mockup 5.16 (2026-06-29)
|
||||||
|
|
||||||
|
> Restul task-urilor din `docs/raport-comparatie-mockup-5.16.md` au fost livrate (T-1..T-9).
|
||||||
|
> Cele de mai jos raman explicit in coada la cererea userului.
|
||||||
|
|
||||||
|
- [ ] **Stare de eroare HTMX la incarcarea listei (D-4)** — cand `/_fragments/submissions`
|
||||||
|
da 500 sau pica reteaua, `#submissions-wrap` ramane blocat pe spinner ("se incarca…") fara
|
||||||
|
mesaj. De adaugat un partial de eroare / `hx-on::response-error` cu "nu s-a putut incarca,
|
||||||
|
reincearca". Robustete pre-existenta (nu introdusa de 5.16), impact functional real —
|
||||||
|
**candidatul cu cea mai mare valoare** din lista. (Design D-4, medium.)
|
||||||
|
|
||||||
|
- [ ] **Retokenizare px completa in template-uri** — `_submissions.html` / `_preview_*` folosesc
|
||||||
|
literali `font-size:13px/12px/11px` in loc de token-urile `--fs-*`. 5.16 a corectat doar
|
||||||
|
instanta sub-12px (incalca pragul PRD). Restul ramane debt: schimbarea in masa (13px→`--fs-sm`
|
||||||
|
=13.5px) misca layout-ul, deci necesita o baza de regresie vizuala inainte. (Eng, bounded —
|
||||||
|
amanat ca scope creep fara baza AC.)
|
||||||
|
|
||||||
|
- [ ] **Diacritice in textul vizibil pentru user** — mockup-urile folosesc diacritice complete
|
||||||
|
("Observații", "Salvează", "Adaugă"); aplicatia le omite in majoritatea label-urilor. Fontul
|
||||||
|
le randeaza corect (US-001 confirmat). De aplicat pe label-uri/butoane/titluri, pastrand
|
||||||
|
cod/comentariile fara diacritice. Decizie initiala (poarta de gust T3): nu se aplica acum —
|
||||||
|
reintrodus in coada la cererea userului (2026-06-29) ca finisaj viitor. (Transversal, low.)
|
||||||
|
|||||||
13
app/db.py
13
app/db.py
@@ -39,10 +39,19 @@ def init_db() -> None:
|
|||||||
seed_nomenclator_if_empty(conn)
|
seed_nomenclator_if_empty(conn)
|
||||||
# Seed corpus operatii etichetate -> mapping_suggestions (SILVER, PRD 5.18 US-004).
|
# Seed corpus operatii etichetate -> mapping_suggestions (SILVER, PRD 5.18 US-004).
|
||||||
# Gated: OFF in teste (conftest), ON in productie. INSERT OR IGNORE -> idempotent.
|
# Gated: OFF in teste (conftest), ON in productie. INSERT OR IGNORE -> idempotent.
|
||||||
|
# DOAR daca mapping_suggestions e gol: seedul are ~17k randuri; re-rularea lui pe
|
||||||
|
# FIECARE boot (API + worker concurent) tinea write-lock-ul indelung -> al doilea
|
||||||
|
# proces primea "database is locked" la pornire. Guard "_if_empty" (ca nomenclatorul)
|
||||||
|
# -> boot rapid cand e deja seeded. Re-seed dupa actualizarea fisierului = manual
|
||||||
|
# (goleste tabela), consistent cu semantica v1 ignore-not-upsert a seederului.
|
||||||
if get_settings().seed_operatii_enabled:
|
if get_settings().seed_operatii_enabled:
|
||||||
from .operatii_seed import seed_operatii_etichetate
|
already = conn.execute(
|
||||||
|
"SELECT 1 FROM mapping_suggestions LIMIT 1"
|
||||||
|
).fetchone()
|
||||||
|
if not already:
|
||||||
|
from .operatii_seed import seed_operatii_etichetate
|
||||||
|
|
||||||
seed_operatii_etichetate(conn)
|
seed_operatii_etichetate(conn)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -118,10 +118,13 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{# ===== US-005 (5.16): Chips extra + picker '+ Adauga alta operatie / cod RAR' in mod operatii ===== #}
|
{# ===== US-005 (5.16): Chips extra + picker '+ Adauga alta operatie / cod RAR' in mod operatii ===== #}
|
||||||
{# Chips extra: cod_op_service gol, cod_prestatie setat — afisate flat cu × (reuse remove_flat) #}
|
{# Chips extra: cod_op_service gol, cod_prestatie setat — afisate flat cu × (reuse remove_flat).
|
||||||
|
T-7 (5.16): containerul .chips se randeaza DOAR cand exista chips extra — altfel ramanea
|
||||||
|
un chenar gol nefinisat sub randurile de operatie. #}
|
||||||
|
{% set _extra_chips = _chips | rejectattr('cod_op_service') | selectattr('cod_prestatie') | list %}
|
||||||
|
{% if _extra_chips %}
|
||||||
<div class="chips" role="group" aria-label="Coduri RAR suplimentare" style="margin-top:4px;">
|
<div class="chips" role="group" aria-label="Coduri RAR suplimentare" style="margin-top:4px;">
|
||||||
{% for chip in _chips %}
|
{% for chip in _extra_chips %}
|
||||||
{% if not chip.cod_op_service and chip.cod_prestatie %}
|
|
||||||
{% set _is_warn_extra = chip.cod_prestatie in ('R-ODO', 'I-ODO') %}
|
{% set _is_warn_extra = chip.cod_prestatie in ('R-ODO', 'I-ODO') %}
|
||||||
<span class="chip {% if _is_warn_extra %}chip-warn{% endif %}"
|
<span class="chip {% if _is_warn_extra %}chip-warn{% endif %}"
|
||||||
aria-label="Cod RAR suplimentar {{ chip.cod_prestatie }}">
|
aria-label="Cod RAR suplimentar {{ chip.cod_prestatie }}">
|
||||||
@@ -134,9 +137,9 @@
|
|||||||
hx-vals='{"chips_action":"remove_flat","chips_remove_cod":"{{ chip.cod_prestatie }}"}'
|
hx-vals='{"chips_action":"remove_flat","chips_remove_cod":"{{ chip.cod_prestatie }}"}'
|
||||||
aria-label="Sterge codul suplimentar {{ chip.cod_prestatie }}">×</button>
|
aria-label="Sterge codul suplimentar {{ chip.cod_prestatie }}">×</button>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if nomenclator_rar %}
|
{% if nomenclator_rar %}
|
||||||
<span style="display:inline-flex;align-items:center;gap:4px;margin-top:4px;">
|
<span style="display:inline-flex;align-items:center;gap:4px;margin-top:4px;">
|
||||||
<select name="chips_add_cod_flat"
|
<select name="chips_add_cod_flat"
|
||||||
|
|||||||
@@ -6,19 +6,26 @@
|
|||||||
style="margin-top:22px; padding-top:18px; border-top:2px solid var(--line);"
|
style="margin-top:22px; padding-top:18px; border-top:2px solid var(--line);"
|
||||||
{% if oob %}hx-swap-oob="outerHTML"{% endif %}>
|
{% if oob %}hx-swap-oob="outerHTML"{% endif %}>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0 0 12px;">
|
{# US-002 (5.16): titlul de sectiune vizibil ("Trimiterile tale") a fost eliminat —
|
||||||
<h2 id="trimiteri-heading" style="font-size:15px; margin:0;">
|
lista incepe direct sub filtre. Heading pastrat sr-only pentru a11y (section
|
||||||
Trimiterile tale
|
aria-labelledby). Badge-ul de atentie + export CSV stau intr-un rand discret. #}
|
||||||
{% if blocate_total %}
|
<h2 id="trimiteri-heading" class="sr-only">Trimiterile tale</h2>
|
||||||
<span class="tab-badge" title="{{ blocate_total }} necesita atentie"
|
{% if blocate_total %}
|
||||||
style="display:inline-flex; align-items:center; justify-content:center; min-width:18px; height:18px; margin-left:6px; padding:0 5px; border-radius:99px; background:var(--err); color:#fff; font-size:11px; font-weight:700;">{{ blocate_total }}</span>
|
<div style="display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin:0 0 10px;">
|
||||||
{% endif %}
|
<span class="tab-badge" title="{{ blocate_total }} necesita atentie"
|
||||||
</h2>
|
style="display:inline-flex; align-items:center; justify-content:center; min-width:18px; height:18px; padding:0 5px; border-radius:99px; background:var(--err); color:#fff; font-size:11px; font-weight:700;">{{ blocate_total }}</span>
|
||||||
|
<span class="muted" style="font-size:var(--fs-sm);">de rezolvat</span>
|
||||||
<span style="margin-left:auto; display:flex; gap:8px; flex-wrap:wrap;">
|
<span style="margin-left:auto; display:flex; gap:8px; flex-wrap:wrap;">
|
||||||
<a class="cardlink" href="/v1/audit/export?status=sent" download>export CSV: trimise</a>
|
<a class="cardlink" href="/v1/audit/export?status=sent" download>export CSV: trimise</a>
|
||||||
<a class="cardlink" href="/v1/audit/export?status=all" download>toate</a>
|
<a class="cardlink" href="/v1/audit/export?status=all" download>toate</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:8px; flex-wrap:wrap; margin:0 0 10px;">
|
||||||
|
<a class="cardlink" href="/v1/audit/export?status=sent" download>export CSV: trimise</a>
|
||||||
|
<a class="cardlink" href="/v1/audit/export?status=all" download>toate</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Bara de filtre: [quick-pills data STANGA] [cautare vehicul MIJLOC] [pills stare DREAPTA].
|
<!-- Bara de filtre: [quick-pills data STANGA] [cautare vehicul MIJLOC] [pills stare DREAPTA].
|
||||||
Pill-urile de stare scriu campul hidden status si re-trimit form-ul (filtreazaStare).
|
Pill-urile de stare scriu campul hidden status si re-trimit form-ul (filtreazaStare).
|
||||||
|
|||||||
@@ -103,8 +103,8 @@
|
|||||||
<div class="act-group" style="margin-top:14px;">
|
<div class="act-group" style="margin-top:14px;">
|
||||||
<button type="submit" class="act act-primary" aria-label="{{ btn_label or 'Salveaza' }}">
|
<button type="submit" class="act act-primary" aria-label="{{ btn_label or 'Salveaza' }}">
|
||||||
<span class="act-tx">{{ btn_label or 'Salveaza' }}</span>{{ icon('save') }}</button>
|
<span class="act-tx">{{ btn_label or 'Salveaza' }}</span>{{ icon('save') }}</button>
|
||||||
<button type="button" class="act" aria-label="{{ cancel_label or 'Anuleaza' }}" data-modal-close>
|
<button type="button" class="act" aria-label="{{ cancel_label or 'Renunta' }}" data-modal-close>
|
||||||
<span class="act-tx">{{ cancel_label or 'Anuleaza' }}</span>{{ icon('x') }}</button>
|
<span class="act-tx">{{ cancel_label or 'Renunta' }}</span>{{ icon('x') }}</button>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div style="margin-top:14px;">
|
<div style="margin-top:14px;">
|
||||||
|
|||||||
@@ -144,19 +144,18 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Tabel preview in format identic cu tabelul Trimiteri (.tabel-trimiteri).
|
<!-- Tabel preview in format identic cu tabelul Trimiteri (.tabel-trimiteri).
|
||||||
US-007: 8 coloane (coloana de verificare eliminata).
|
5.16 (T-4): densitate redusa la coloanele esentiale — Stare / Vehicul /
|
||||||
Randurile au FORM PROPRIU pentru editare (NU sunt in #confirm-form). -->
|
Operatie / Data + Actiuni. KM final + mesajul de validare (Note) au iesit
|
||||||
|
din tabel: KM se editeaza in modal, motivul apare ca tooltip pe pill-ul de
|
||||||
|
Stare. Randurile au FORM PROPRIU pentru editare (NU sunt in #confirm-form). -->
|
||||||
<div id="preview-tabel" class="tablewrap tabel-trimiteri">
|
<div id="preview-tabel" class="tablewrap tabel-trimiteri">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-id">#</th>
|
|
||||||
<th class="col-stare">Stare</th>
|
<th class="col-stare">Stare</th>
|
||||||
<th class="col-vehicul">Vehicul</th>
|
<th class="col-vehicul">Vehicul</th>
|
||||||
<th class="col-operatie">Operatie</th>
|
<th class="col-operatie">Operatie</th>
|
||||||
<th class="col-data">Data</th>
|
<th class="col-data">Data</th>
|
||||||
<th class="col-km">KM final</th>
|
|
||||||
<th class="col-note">Note</th>
|
|
||||||
<th class="col-actiuni">Actiuni</th>
|
<th class="col-actiuni">Actiuni</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -23,9 +23,21 @@
|
|||||||
{% if _sent_dup %}class="preview-sent-row"{% endif %}
|
{% if _sent_dup %}class="preview-sent-row"{% endif %}
|
||||||
{% if oob_tr %}hx-swap-oob="outerHTML:#preview-row-{{ row.row_index }}"{% endif %}
|
{% if oob_tr %}hx-swap-oob="outerHTML:#preview-row-{{ row.row_index }}"{% endif %}
|
||||||
style="{% if status == 'needs_review' %}background:rgba(230,179,74,.04);{% elif _sent_dup %}opacity:.6;{% endif %}">
|
style="{% if status == 'needs_review' %}background:rgba(230,179,74,.04);{% elif _sent_dup %}opacity:.6;{% endif %}">
|
||||||
<td class="col-id muted" data-eticheta="#">{{ row.row_index + 1 }}</td>
|
{#- Motivul (validare / deja-trimis / duplicat) — fost coloana Note, acum tooltip pe pill.
|
||||||
|
KM final iese din tabel (se editeaza in modal). -#}
|
||||||
|
{%- if status == 'already_sent' and row.get('already_sent_info') -%}
|
||||||
|
{%- set ai = row.already_sent_info -%}
|
||||||
|
{%- set _nota = 'deja trimis ' ~ ((ai.get('created_at') or '')[:10]) ~ ((' (#' ~ ai.id_prezentare ~ ')') if ai.get('id_prezentare') else '') -%}
|
||||||
|
{%- elif status == 'duplicate_in_file' and row.get('duplicate_with') -%}
|
||||||
|
{%- set _dwith = [] -%}
|
||||||
|
{%- for idx in row.duplicate_with -%}{{ _dwith.append(idx + 1) or '' }}{%- endfor -%}
|
||||||
|
{%- set _nota = 'dubla cu randul ' ~ (_dwith | join(', ')) -%}
|
||||||
|
{%- else -%}
|
||||||
|
{%- set _nota = row.nota_umana or '' -%}
|
||||||
|
{%- endif -%}
|
||||||
<td class="col-stare" data-eticheta="Stare">
|
<td class="col-stare" data-eticheta="Stare">
|
||||||
<span class="pill {{ row.stare_css }}" style="display:inline-flex; align-items:center; gap:5px;">
|
<span class="pill {{ row.stare_css }}" style="display:inline-flex; align-items:center; gap:5px;"
|
||||||
|
{% if _nota %}title="{{ _nota }}"{% endif %}>
|
||||||
<span aria-hidden="true" style="display:inline-block; width:7px; height:7px; border-radius:99px; background:currentColor; flex-shrink:0;"></span>{{ row.stare_eticheta }}</span>
|
<span aria-hidden="true" style="display:inline-block; width:7px; height:7px; border-radius:99px; background:currentColor; flex-shrink:0;"></span>{{ row.stare_eticheta }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="col-vehicul" data-eticheta="Vehicul">
|
<td class="col-vehicul" data-eticheta="Vehicul">
|
||||||
@@ -43,20 +55,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="col-data" data-eticheta="Data prestatie">{{ row.prez.data_prestatie }}</td>
|
<td class="col-data" data-eticheta="Data prestatie">{{ row.prez.data_prestatie }}</td>
|
||||||
<td class="col-km" data-eticheta="KM final">{{ row.prez.odometru }}</td>
|
|
||||||
<td class="col-note" data-eticheta="Note"
|
|
||||||
style="font-size:var(--fs-xs); white-space:normal;">
|
|
||||||
{% if status == 'already_sent' and row.get('already_sent_info') %}
|
|
||||||
{% set ai = row.already_sent_info %}
|
|
||||||
deja trimis {{ (ai.get('created_at') or '')[:10] }}
|
|
||||||
{% if ai.get('id_prezentare') %}(#{{ ai.id_prezentare }}){% endif %}
|
|
||||||
{% elif status == 'duplicate_in_file' and row.get('duplicate_with') %}
|
|
||||||
dubla cu randul
|
|
||||||
{% for idx in row.duplicate_with %}{{ idx + 1 }}{% if not loop.last %}, {% endif %}{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
{{ row.nota_umana or '' }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="col-actiuni" data-eticheta="Actiuni" style="text-align:center;">
|
<td class="col-actiuni" data-eticheta="Actiuni" style="text-align:center;">
|
||||||
{% if status not in ('already_sent', 'duplicate_in_file') %}
|
{% if status not in ('already_sent', 'duplicate_in_file') %}
|
||||||
<button type="button" class="btn-editeaza"
|
<button type="button" class="btn-editeaza"
|
||||||
|
|||||||
@@ -137,10 +137,11 @@
|
|||||||
class="status-nav-link{% if _tab == 'mapari' %} status-nav-activ{% endif %}">Mapari{% if mapari_badge %}<span class="tab-badge" aria-hidden="true" style="display:inline-flex; align-items:center; justify-content:center; min-width:16px; height:16px; margin-left:4px; padding:0 4px; border-radius:99px; background:var(--err); color:#fff; font-size:11px; font-weight:700;">{{ mapari_badge }}</span>{% endif %}</a>
|
class="status-nav-link{% if _tab == 'mapari' %} status-nav-activ{% endif %}">Mapari{% if mapari_badge %}<span class="tab-badge" aria-hidden="true" style="display:inline-flex; align-items:center; justify-content:center; min-width:16px; height:16px; margin-left:4px; padding:0 4px; border-radius:99px; background:var(--err); color:#fff; font-size:11px; font-weight:700;">{{ mapari_badge }}</span>{% endif %}</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{# US-006 (5.17): linia de plan — consum/trial (secundar, sub navigatie, non-blocant).
|
{# US-006 (5.17) + T-6 (5.16): linia de plan in CORP apare DOAR in starea de avertizare
|
||||||
Warn=culoare+text (accesibilitate): >=80% -> --warn; limita atinsa -> --err.
|
(>=80% -> --warn; limita atinsa -> --err). Consumul normal (N/60) traieste in badge-ul
|
||||||
Ierarhie: nu concureaza cu stripul de sanatate (E zero-silent-failures pastrat). #}
|
din antet + linia din meniul burger, nu ca rand permanent in corp (densitate redusa).
|
||||||
{% if plan_linie is defined and plan_linie %}
|
Ierarhie: nu concureaza cu stripul de sanatate (zero-silent-failures pastrat). #}
|
||||||
|
{% if plan_linie and (plan_warn|default(false) or plan_limita_atinsa|default(false)) %}
|
||||||
<div class="plan-status-line"
|
<div class="plan-status-line"
|
||||||
style="font-size:var(--fs-sm); margin-top:6px; padding-top:6px;
|
style="font-size:var(--fs-sm); margin-top:6px; padding-top:6px;
|
||||||
border-top:1px solid var(--line2);
|
border-top:1px solid var(--line2);
|
||||||
|
|||||||
@@ -96,41 +96,43 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Bloc text principal — stanga, ocupa spatiul ramas #}
|
{# Bloc text principal — stanga, ocupa spatiul ramas. Rand de 2 linii (spec 5.16):
|
||||||
|
L1 = placuta (identificator primar); L2 = cod RAR · operatie · data prestatie. #}
|
||||||
<div style="flex:1 1 auto; min-width:0;">
|
<div style="flex:1 1 auto; min-width:0;">
|
||||||
|
|
||||||
{# Linia 1: VIN mono scurt (slim-vin).
|
{# Linia 1: nr. inmatriculare (placuta) — identificatorul primar pe care il
|
||||||
Guard: vin_scurt='—' inseamna VIN lipsa; fallback la vehicul_nr. #}
|
scaneaza operatorul. .slim-vin reumplut (acelasi nume de clasa, churn minim).
|
||||||
{% if r.prez.vin_scurt and r.prez.vin_scurt != '—' %}
|
Fallback cand placuta lipseste ('—'): VIN scurt, apoi mesaj neutru
|
||||||
<div class="slim-vin">{{ r.prez.vin_scurt }}</div>
|
(nu randa em-dash izolat ca identificator). #}
|
||||||
|
{% if r.prez.vehicul_nr and r.prez.vehicul_nr != '—' %}
|
||||||
|
<div class="slim-vin">{{ r.prez.vehicul_nr }}</div>
|
||||||
|
{% elif r.prez.vin_scurt and r.prez.vin_scurt != '—' %}
|
||||||
|
<div class="slim-vin muted">{{ r.prez.vin_scurt }}</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="slim-vin muted">{{ r.prez.vehicul_nr }}</div>
|
<div class="slim-vin muted">fara numar</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Linia 2: Operatie · ora/data (slim-meta muted) #}
|
{# Linia 2: cod RAR (sau 'nemapat') · operatie (ink, ellipsis) · data prestatie.
|
||||||
<div class="slim-meta">{{ r.prez.operatie }} · {{ r.updated_at }}</div>
|
Separatorul "·" e injectat prin CSS intre celule. Operatia primeste ellipsis
|
||||||
|
ca randul sa NU treaca pe a 3-a linie nici la 390px.
|
||||||
{# Cod RAR sau indicatorul 'nemapat': discret sub operatie.
|
VIN integral, #id_prezentare si secundele traiesc in modalul de detaliu. #}
|
||||||
Mentine compatibilitatea cu testele cod_rar: OE-2 vizibil, fara prefix 'cod RAR:'. #}
|
<div class="slim-meta slim-rand2">
|
||||||
{% if r.prez.cod_rar and r.prez.cod_rar != '—' %}
|
{% if r.prez.cod_rar and r.prez.cod_rar != '—' %}
|
||||||
<div class="slim-meta"><span class="cod-rar-cod">{{ r.prez.cod_rar }}</span></div>
|
<span class="cod-rar-cod">{{ r.prez.cod_rar }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="slim-meta muted cod-rar-sub">nemapat</div>
|
<span class="cod-rar-cod cod-rar-sub muted">nemapat</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<span class="slim-op">{{ r.prez.operatie }}</span>
|
||||||
{# Linia meta discreta: nr inmatriculare · data prestatie · nr prezentare RAR.
|
{% if r.prez.data_prestatie and r.prez.data_prestatie != '—' %}
|
||||||
Accesibila pe rand; informatia completa e in modalul de detaliu. #}
|
<span class="slim-data muted">{{ r.prez.data_prestatie }}</span>
|
||||||
<div class="slim-meta" style="opacity:0.7;">
|
{% endif %}
|
||||||
{{ r.prez.vehicul_nr -}}
|
|
||||||
{%- if r.prez.data_prestatie and r.prez.data_prestatie != '—' %} · {{ r.prez.data_prestatie }}{% endif -%}
|
|
||||||
{%- if r.id_prezentare %} · #{{ r.id_prezentare }}{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Eticheta umana scurta sub pill — text mic, s-error pe error/needs_*.
|
{# Micro-linie umana a problemei — text mic s-error, DOAR pe stari de problema
|
||||||
Afisata DOAR pe randuri cu problema (eticheta_problema ne-goala).
|
(loud-on-exception D6). Randul normal/finalizat ramane strict 2 linii.
|
||||||
Starea transmisa prin TEXT, nu doar culoare. #}
|
Token tipografic --fs-xs (>=12px, scala 5.16). #}
|
||||||
{% if r.eticheta_problema and r.eticheta_problema != r.stare_scurt and r.eticheta_problema != r.stare_text %}
|
{% if r.eticheta_problema and r.eticheta_problema != r.stare_scurt and r.eticheta_problema != r.stare_text %}
|
||||||
<div class="eticheta-problema s-error" style="font-size:10px; margin-top:2px;">{{ r.eticheta_problema }}</div>
|
<div class="eticheta-problema s-error">{{ r.eticheta_problema }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,9 @@
|
|||||||
.s-needs_review{color:var(--warn);}
|
.s-needs_review{color:var(--warn);}
|
||||||
.s-already_sent,.s-duplicate_in_file{color:var(--muted);}
|
.s-already_sent,.s-duplicate_in_file{color:var(--muted);}
|
||||||
.muted { color:var(--muted); }
|
.muted { color:var(--muted); }
|
||||||
|
/* Heading/eticheta accesibila doar pentru cititoare de ecran (vizual ascunsa). */
|
||||||
|
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
|
||||||
|
clip:rect(0 0 0 0); white-space:nowrap; border:0; }
|
||||||
a { color:var(--accent); }
|
a { color:var(--accent); }
|
||||||
/* Drop zone upload fisier */
|
/* Drop zone upload fisier */
|
||||||
.drop-zone { border:2px dashed var(--line); border-radius:8px; padding:32px 20px;
|
.drop-zone { border:2px dashed var(--line); border-radius:8px; padding:32px 20px;
|
||||||
@@ -410,7 +413,9 @@
|
|||||||
.tabel-trimiteri th, .tabel-trimiteri td { white-space:normal; word-break:break-word; vertical-align:top; }
|
.tabel-trimiteri th, .tabel-trimiteri td { white-space:normal; word-break:break-word; vertical-align:top; }
|
||||||
.tabel-trimiteri .col-chk { width:30px; }
|
.tabel-trimiteri .col-chk { width:30px; }
|
||||||
.tabel-trimiteri .col-id { width:48px; }
|
.tabel-trimiteri .col-id { width:48px; }
|
||||||
.tabel-trimiteri .col-stare { width:104px; }
|
/* col-stare largita (bug 4a 5.16): cu table-layout:fixed + pill nowrap, 104px era
|
||||||
|
prea ingusta -> pill-ul de stare se revarsa peste col-vehicul. 140px o contine. */
|
||||||
|
.tabel-trimiteri .col-stare { width:140px; }
|
||||||
.tabel-trimiteri .col-data { width:104px; }
|
.tabel-trimiteri .col-data { width:104px; }
|
||||||
.tabel-trimiteri .col-rar { width:96px; }
|
.tabel-trimiteri .col-rar { width:96px; }
|
||||||
.tabel-trimiteri .col-actualizat { width:128px; }
|
.tabel-trimiteri .col-actualizat { width:128px; }
|
||||||
@@ -722,8 +727,27 @@
|
|||||||
.trimitere-slim:last-child { border-bottom:none; }
|
.trimitere-slim:last-child { border-bottom:none; }
|
||||||
.trimitere-slim:hover { background:color-mix(in srgb, var(--accent) 6%, transparent); }
|
.trimitere-slim:hover { background:color-mix(in srgb, var(--accent) 6%, transparent); }
|
||||||
.trimitere-slim:focus, .trimitere-slim:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
|
.trimitere-slim:focus, .trimitere-slim:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
|
||||||
.slim-vin { font-family:var(--font-mono); font-size:var(--fs-md); font-weight:500; color:var(--ink); }
|
.slim-vin { font-family:var(--font-mono); font-size:var(--fs-md); font-weight:600; color:var(--ink); }
|
||||||
.slim-meta { font-size:var(--fs-sm); color:var(--muted); margin-top:3px; }
|
.slim-meta { font-size:var(--fs-sm); color:var(--muted); margin-top:3px; }
|
||||||
|
/* Linia 2 a randului slim (5.16): cod RAR · operatie (ellipsis) · data, pe UN rand.
|
||||||
|
Ellipsis-ul pe operatie garanteaza 2 linii MAX si la 390px. */
|
||||||
|
.slim-rand2 { display:flex; align-items:baseline; gap:6px; min-width:0; }
|
||||||
|
.slim-rand2 .cod-rar-cod { flex:0 0 auto; font-family:var(--font-mono); font-weight:600;
|
||||||
|
color:var(--accent); }
|
||||||
|
.slim-rand2 .cod-rar-cod.muted { color:var(--muted); font-weight:500; }
|
||||||
|
.slim-rand2 .slim-op { flex:1 1 auto; min-width:0; white-space:nowrap; overflow:hidden;
|
||||||
|
text-overflow:ellipsis; color:var(--ink); }
|
||||||
|
.slim-rand2 .slim-data { flex:0 0 auto; }
|
||||||
|
.slim-rand2 .slim-op::before, .slim-rand2 .slim-data::before {
|
||||||
|
content:"·"; color:var(--muted); margin-right:6px; }
|
||||||
|
.lista-trimiteri-slim .eticheta-problema { font-size:var(--fs-xs); line-height:1.3; margin-top:2px; }
|
||||||
|
/* Pill slim (5.16): fill-tint + dot 7px + text colorat per stare (currentColor din .s-*).
|
||||||
|
Pastrat pe FIECARE rand inclusiv Finalizat (linistit dar prezent). */
|
||||||
|
.lista-trimiteri-slim .pill { display:inline-flex; align-items:center; gap:5px; font-weight:600;
|
||||||
|
background:color-mix(in srgb, currentColor 14%, transparent);
|
||||||
|
border-color:color-mix(in srgb, currentColor 35%, transparent); }
|
||||||
|
.lista-trimiteri-slim .pill::before { content:""; width:7px; height:7px; border-radius:99px;
|
||||||
|
background:currentColor; flex-shrink:0; }
|
||||||
/* .camp-slim — varianta compacta camp formular: label --fs-sm muted deasupra, input --fs-md, fundal --card2.
|
/* .camp-slim — varianta compacta camp formular: label --fs-sm muted deasupra, input --fs-md, fundal --card2.
|
||||||
Mono pentru campuri VIN/odometru/nr: adauga clasa .camp-mono pe input. */
|
Mono pentru campuri VIN/odometru/nr: adauga clasa .camp-mono pe input. */
|
||||||
.camp-slim { margin-bottom:8px; }
|
.camp-slim { margin-bottom:8px; }
|
||||||
@@ -756,7 +780,9 @@
|
|||||||
.op-row { display:flex; align-items:center; justify-content:space-between; gap:10px;
|
.op-row { display:flex; align-items:center; justify-content:space-between; gap:10px;
|
||||||
padding:8px 10px; border:1px solid var(--line); border-radius:6px;
|
padding:8px 10px; border:1px solid var(--line); border-radius:6px;
|
||||||
background:var(--card2); margin-bottom:8px; }
|
background:var(--card2); margin-bottom:8px; }
|
||||||
.op-row-name { font-size:var(--fs-xs); font-weight:500; color:var(--ink); }
|
/* Nume operatie emfatic (T-9 5.16): proeminent (bold) ca in mockup — e ancora
|
||||||
|
vizuala a randului de mapare op<->cod. */
|
||||||
|
.op-row-name { font-size:var(--fs-sm); font-weight:700; color:var(--ink); }
|
||||||
.op-row-warn { border-color:color-mix(in srgb, var(--warn) 45%, var(--line)); }
|
.op-row-warn { border-color:color-mix(in srgb, var(--warn) 45%, var(--line)); }
|
||||||
/* Mobil: tinta touch pentru trimitere-slim (deja garantata prin min-height:44px in regula de baza) */
|
/* Mobil: tinta touch pentru trimitere-slim (deja garantata prin min-height:44px in regula de baza) */
|
||||||
@media (max-width:767px) {
|
@media (max-width:767px) {
|
||||||
|
|||||||
@@ -471,17 +471,18 @@ risc regresie vizuala fara baza AC). **Auto-decis: doar instanta sub-12px** (`et
|
|||||||
- **Fallback**: `vehicul_nr == '—'` → nu randa em-dash singur (mesaj fallback).
|
- **Fallback**: `vehicul_nr == '—'` → nu randa em-dash singur (mesaj fallback).
|
||||||
- Pastreaza numele claselor `slim-vin`/`slim-meta` (reumple, nu redenumi) — minimizeaza churn teste.
|
- Pastreaza numele claselor `slim-vin`/`slim-meta` (reumple, nu redenumi) — minimizeaza churn teste.
|
||||||
|
|
||||||
### Implementation Tasks (agregat)
|
### Implementation Tasks (agregat) — LIVRAT 2026-06-29 (toate verzi, 1392 teste)
|
||||||
- [ ] **T-1 (INALTA) — `_submissions.html`** — refactor rand 4→2 linii cu placuta+codRAR+operatie+data_prestatie+pill; fallback placuta; pastreaza clase. Update teste: rescrie test_vin_pe_rand_separat_sub_nr, test_rand_slim_vin_operatie_pill, test_submissions_coloane_umane; adauga test 2-linii + test fallback placuta.
|
- [x] **T-1 (INALTA) — `_submissions.html`** — refactor rand 4→2 linii cu placuta+codRAR+operatie+data_prestatie+pill; fallback placuta; clase pastrate. Teste rescrise: test_rand_slim_vin_operatie_pill, test_submissions_coloane_umane, test_placuta_pe_rand_identificator_primar (fost test_vin_pe_rand_separat_sub_nr), test_placuta_lipsa_nu_genereaza_rand_gol (fallback "fara numar").
|
||||||
- [ ] **T-2 (INALTA) — `base.html` (CSS pill) + `_submissions.html`** — restilare pill slim ca mockup (fill tint + dot + text colorat per `stare_css`); pill ramane pe finalizat.
|
- [x] **T-2 (INALTA) — `base.html` (CSS pill) + `_submissions.html`** — pill slim restilat (fill tint + dot 7px + text colorat per `stare_css` via currentColor), scopat `.lista-trimiteri-slim .pill`; ramane pe finalizat.
|
||||||
- [ ] **T-3 (INALTA) — `_preview_import.html` / `base.html:401`** — bug 4a: `.col-stare` width 104px→~140px (+ `overflow:hidden` sau pill wrap). NU atinge nowrap pe col-vehicul (test_web_preview_compact). Reducere 8→4 coloane (densitate) ca task separat.
|
- [x] **T-3 (INALTA) — `base.html`** — bug 4a: `.tabel-trimiteri .col-stare` 104px→140px. nowrap pe col-vehicul neatins.
|
||||||
- [ ] **T-4 (MEDIE) — `_preview_import.html`** — reducere la 4 coloane esentiale (Stare/Vehicul/Operatie/Data + Editeaza); muta KM + mesaj validare in randul de editare/tooltip.
|
- [x] **T-4 (MEDIE) — `_preview_import.html` + `_preview_rand.html`** — reducere la 5 coloane (Stare/Vehicul/Operatie/Data/Actiuni); scoase col-id, col-km, col-note; motivul mutat in `title` pe pill, KM in modal.
|
||||||
- [ ] **T-5 (MEDIE) — `_coada.html:10-19`** — scoate titlul "Trimiterile tale" (h2); relocare export CSV langa tab-uri / meniu cont (PRD 5.16/US-002).
|
- [x] **T-5 (MEDIE) — `_coada.html`** — titlu vizibil "Trimiterile tale" → `<h2 class="sr-only">` (a11y pastrat); badge "de rezolvat" + export CSV intr-un rand discret. `.sr-only` adaugat in base.html.
|
||||||
- [ ] **T-6 (MEDIE) — `_status.html:140`** — scoate randul plan "N/60 luna asta" din corp; pastreaza badge antet + linie burger (PRD 5.17/US-006). Daca >=80% consum, afiseaza doar in starea de avertizare.
|
- [x] **T-6 (MEDIE) — `_status.html`** — linia plan in corp DOAR pe avertizare (`plan_warn`/`plan_limita_atinsa`); consum normal in badge antet + burger. Teste status mutate pe pagina completa.
|
||||||
- [ ] **T-7 (MEDIE) — `_chips_prestatii.html:122`** — guard `{% if _extra %}` pe containerul `.chips` (operatii-mode), elimina chenarul gol.
|
- [x] **T-7 (MEDIE) — `_chips_prestatii.html`** — guard `{% if _extra_chips %}` pe containerul `.chips`, chenarul gol eliminat.
|
||||||
- [ ] **T-8 (MICA) — `_submissions.html:133`** — `font-size:10px`→`var(--fs-xs)` (doar instanta sub-12px).
|
- [x] **T-8 (MICA) — `_submissions.html` / base.html** — `font-size:10px`→`var(--fs-xs)` (eticheta-problema, prin clasa scopata `.lista-trimiteri-slim .eticheta-problema`).
|
||||||
- [ ] **T-9 (MICA) — copy/stil** — "Anuleaza"→"Renunta" (form editare); nume operatie emfatic (bold) in editorul de chips per mockup.
|
- [x] **T-9 (MICA) — `_form_editare.html` + base.html** — "Anuleaza"→"Renunta" (default); `.op-row-name` emfatic (bold, `--fs-sm`).
|
||||||
- [ ] **Defer TODOS** — stare eroare HTMX lista (D-4); teste regresie vizuala; dropzone zona-mare (sec.5); retokenizare px completa; diacritice (decis: nu).
|
- [ ] **Defer — tracked in `TODOS.md`** (la cererea userului 2026-06-29): stare eroare HTMX lista (D-4); retokenizare px completa; diacritice in textul vizibil.
|
||||||
|
- [x] **Defer — inchis ca acceptabil** (netrackuit): teste regresie vizuala (tooling viitor); dropzone zona-mare (sec.5, raportul il marcheaza acceptabil).
|
||||||
|
|
||||||
### Verificare la implementare
|
### Verificare la implementare
|
||||||
`python3 -m pytest tests/test_web_submissions.py tests/test_web_submissions_layout.py tests/test_web_responsive.py tests/test_web_preview_compact.py -q`
|
`python3 -m pytest tests/test_web_submissions.py tests/test_web_submissions_layout.py tests/test_web_responsive.py tests/test_web_preview_compact.py -q`
|
||||||
|
|||||||
@@ -495,7 +495,9 @@ def _insert_submissions_sent(account_id: int, n: int) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_afisaj_plan_si_zile_trial(client):
|
def test_afisaj_plan_si_zile_trial(client):
|
||||||
"""US-006: cont in trial Pro -> fragment status arata 'trial N zile ramase'.
|
"""US-006 + T-6 (5.16): cont in trial Pro -> linia de plan din meniul burger (pagina
|
||||||
|
completa) arata 'Plan: Pro · trial N zile ramase'. In starea normala (non-warn) plan_linie
|
||||||
|
NU mai e rand in corpul fragmentului status — traieste in badge antet + burger.
|
||||||
Contul nou primeste trial_until=now+30z automat la creare.
|
Contul nou primeste trial_until=now+30z automat la creare.
|
||||||
"""
|
"""
|
||||||
acct_id, _ = _create_account_user("trialzile@test.com")
|
acct_id, _ = _create_account_user("trialzile@test.com")
|
||||||
@@ -505,7 +507,7 @@ def test_afisaj_plan_si_zile_trial(client):
|
|||||||
future = (datetime.now(timezone.utc) + timedelta(days=18, hours=12)).strftime("%Y-%m-%d %H:%M:%S")
|
future = (datetime.now(timezone.utc) + timedelta(days=18, hours=12)).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
_set_trial_until(acct_id, future)
|
_set_trial_until(acct_id, future)
|
||||||
|
|
||||||
resp = client.get("/_fragments/status")
|
resp = client.get("/", follow_redirects=True)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
html = resp.text
|
html = resp.text
|
||||||
|
|
||||||
@@ -516,7 +518,8 @@ def test_afisaj_plan_si_zile_trial(client):
|
|||||||
|
|
||||||
|
|
||||||
def test_afisaj_consum_lunar(client):
|
def test_afisaj_consum_lunar(client):
|
||||||
"""US-006: cont free (fara trial) -> fragment status arata 'Gratuit · N/60 luna asta'."""
|
"""US-006 + T-6 (5.16): cont free (fara trial) -> linia de plan din burger (pagina
|
||||||
|
completa) arata 'Gratuit · N/60 luna asta'. Consumul normal nu mai e rand in corp."""
|
||||||
acct_id, _ = _create_account_user("consumlun@test.com")
|
acct_id, _ = _create_account_user("consumlun@test.com")
|
||||||
_login(client, "consumlun@test.com", "parolasecreta10")
|
_login(client, "consumlun@test.com", "parolasecreta10")
|
||||||
|
|
||||||
@@ -525,7 +528,7 @@ def test_afisaj_consum_lunar(client):
|
|||||||
# Insereaza 5 submissions sent luna asta
|
# Insereaza 5 submissions sent luna asta
|
||||||
_insert_submissions_sent(acct_id, 5)
|
_insert_submissions_sent(acct_id, 5)
|
||||||
|
|
||||||
resp = client.get("/_fragments/status")
|
resp = client.get("/", follow_redirects=True)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
html = resp.text
|
html = resp.text
|
||||||
|
|
||||||
@@ -584,7 +587,8 @@ def test_copy_pluralizare_zi_zile(client):
|
|||||||
future_18 = (datetime.now(timezone.utc) + timedelta(days=18, hours=12)).strftime("%Y-%m-%d %H:%M:%S")
|
future_18 = (datetime.now(timezone.utc) + timedelta(days=18, hours=12)).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
_set_trial_until(acct_id, future_18)
|
_set_trial_until(acct_id, future_18)
|
||||||
|
|
||||||
resp = client.get("/_fragments/status")
|
# T-6 (5.16): linia de plan (cu pluralizarea zilelor) traieste in burger pe pagina completa.
|
||||||
|
resp = client.get("/", follow_redirects=True)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
html = resp.text
|
html = resp.text
|
||||||
assert "18 zile" in html, f"'18 zile' lipseste. HTML: {html[:800]}"
|
assert "18 zile" in html, f"'18 zile' lipseste. HTML: {html[:800]}"
|
||||||
@@ -596,7 +600,7 @@ def test_copy_pluralizare_zi_zile(client):
|
|||||||
future_1 = (datetime.now(timezone.utc) + timedelta(days=1, hours=12)).strftime("%Y-%m-%d %H:%M:%S")
|
future_1 = (datetime.now(timezone.utc) + timedelta(days=1, hours=12)).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
_set_trial_until(acct_id, future_1)
|
_set_trial_until(acct_id, future_1)
|
||||||
|
|
||||||
resp = client.get("/_fragments/status")
|
resp = client.get("/", follow_redirects=True)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
html = resp.text
|
html = resp.text
|
||||||
assert "1 zi" in html, f"'1 zi' (singular) lipseste la o zi ramasa. HTML: {html[:800]}"
|
assert "1 zi" in html, f"'1 zi' (singular) lipseste la o zi ramasa. HTML: {html[:800]}"
|
||||||
|
|||||||
@@ -107,8 +107,9 @@ def test_submissions_coloane_umane(client):
|
|||||||
assert "B777ZZZ" in html, "Nr inmatriculare din payload lipseste"
|
assert "B777ZZZ" in html, "Nr inmatriculare din payload lipseste"
|
||||||
assert "Reparatie frane" in html, "Operatia din payload lipseste"
|
assert "Reparatie frane" in html, "Operatia din payload lipseste"
|
||||||
|
|
||||||
# Nr. prezentare RAR accesibil pe linia meta discreta
|
# 5.16: #id_prezentare nu mai e pe rand (randul are MAX 2 linii) — detaliul complet
|
||||||
assert "68516" in html, "Nr. prezentare RAR lipseste din linia meta"
|
# (inclusiv nr. prezentare RAR) traieste in modalul de detaliu.
|
||||||
|
assert "68516" not in html, "Nr. prezentare RAR nu trebuie sa mai apara pe randul slim"
|
||||||
|
|
||||||
|
|
||||||
def test_tab_eticheta_trimiteri(client):
|
def test_tab_eticheta_trimiteri(client):
|
||||||
@@ -426,9 +427,9 @@ def test_detaliu_trimitere_404_cross_account(client):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def test_rand_slim_vin_operatie_pill(client):
|
def test_rand_slim_vin_operatie_pill(client):
|
||||||
"""US-004: fiecare rand slim afiseaza VIN scurt in .slim-vin, operatie+ora in
|
"""5.16: fiecare rand slim are 2 linii — L1 placuta (nr. inmatriculare) in .slim-vin,
|
||||||
.slim-meta si un pill de stare cu clasa stare_css si eticheta stare_scurt.
|
L2 cod RAR · operatie · data in .slim-meta, plus un pill de stare cu clasa stare_css
|
||||||
Lista e inconjurata de .lista-trimiteri-slim.
|
si eticheta stare_scurt. Lista e inconjurata de .lista-trimiteri-slim.
|
||||||
"""
|
"""
|
||||||
acct = _create_account_user("slim1@test.com")
|
acct = _create_account_user("slim1@test.com")
|
||||||
_insert_submission(acct, "sent", id_prezentare=80001)
|
_insert_submission(acct, "sent", id_prezentare=80001)
|
||||||
@@ -442,14 +443,16 @@ def test_rand_slim_vin_operatie_pill(client):
|
|||||||
assert "lista-trimiteri-slim" in html, "lista-trimiteri-slim lipseste din raspuns"
|
assert "lista-trimiteri-slim" in html, "lista-trimiteri-slim lipseste din raspuns"
|
||||||
assert "trimitere-slim" in html, "trimitere-slim lipseste din raspuns"
|
assert "trimitere-slim" in html, "trimitere-slim lipseste din raspuns"
|
||||||
|
|
||||||
# VIN scurt in clasa slim-vin (mono, linia 1)
|
# L1: placuta (identificator primar) in clasa slim-vin
|
||||||
assert "slim-vin" in html, "slim-vin lipseste — linia 1 VIN mono"
|
assert "slim-vin" in html, "slim-vin lipseste — linia 1 placuta"
|
||||||
|
assert "B777ZZZ" in html, "placuta (nr. inmatriculare) lipseste de pe rand"
|
||||||
|
|
||||||
# Linia 2 muted (operatie + ora/data)
|
# L2: cod RAR · operatie · data (slim-meta / slim-rand2)
|
||||||
assert "slim-meta" in html, "slim-meta lipseste — linia 2 muted"
|
assert "slim-meta" in html, "slim-meta lipseste — linia 2"
|
||||||
|
assert "slim-rand2" in html, "slim-rand2 lipseste — linia 2 (cod RAR · operatie · data)"
|
||||||
|
|
||||||
# VIN scurt randat (WVWZZZ1JZXW000777 -> …000777)
|
# VIN integral nu mai e pe rand (5.16) — traieste in modalul de detaliu.
|
||||||
assert "000777" in html, "VIN scurt (ultimele 6 cifre) lipseste"
|
assert "000777" not in html, "VIN scurt nu mai trebuie randat pe randul slim (2 linii)"
|
||||||
|
|
||||||
# Pill de stare: clasa CSS + eticheta scurta
|
# Pill de stare: clasa CSS + eticheta scurta
|
||||||
assert "s-sent" in html, "clasa pill s-sent lipseste"
|
assert "s-sent" in html, "clasa pill s-sent lipseste"
|
||||||
|
|||||||
@@ -81,12 +81,13 @@ def client(monkeypatch):
|
|||||||
get_settings.cache_clear()
|
get_settings.cache_clear()
|
||||||
|
|
||||||
|
|
||||||
def test_vin_pe_rand_separat_sub_nr(client):
|
def test_placuta_pe_rand_identificator_primar(client):
|
||||||
"""VIN-ul apare intr-un element block-level cu clasa slim-vin (PRD 5.15 US-004).
|
"""Placuta (nr. inmatriculare) e identificatorul PRIMAR, linia 1 a randului slim
|
||||||
|
(5.16): in <div class="slim-vin"> (block-level, prominent).
|
||||||
|
|
||||||
PRD 5.10 (US-005): VIN era <div class="muted"> sub nr in coloana Vehicul.
|
PRD 5.15 (US-004): VIN era identificatorul primar pe linia 1.
|
||||||
PRD 5.15 (US-004): VIN e acum identificatorul PRINCIPAL, linia 1 a randului slim,
|
5.16 (directiva user): operatorul scaneaza placuta de pe comanda, nu VIN-ul de 17
|
||||||
in <div class="slim-vin"> (mono, prominent, block-level). NU mai e muted.
|
caractere — placuta devine linia 1, VIN integral se muta in modalul de detaliu.
|
||||||
"""
|
"""
|
||||||
acct = _create_account_user("vin_layout@test.com")
|
acct = _create_account_user("vin_layout@test.com")
|
||||||
_ins(acct, vin="WVWZZZ1JZXW000001", nr="B123XYZ")
|
_ins(acct, vin="WVWZZZ1JZXW000001", nr="B123XYZ")
|
||||||
@@ -96,46 +97,51 @@ def test_vin_pe_rand_separat_sub_nr(client):
|
|||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
html = resp.text
|
html = resp.text
|
||||||
|
|
||||||
# VIN trunchiat trebuie sa apara in HTML
|
# Placuta trebuie sa apara in HTML
|
||||||
assert "000001" in html, "VIN-ul trunchiat trebuie sa apara in lista slim"
|
assert "B123XYZ" in html, "placuta (nr. inmatriculare) trebuie sa apara in lista slim"
|
||||||
|
|
||||||
# VIN e intr-un element block-level (div cu clasa slim-vin)
|
# Placuta e intr-un element block-level (div cu clasa slim-vin)
|
||||||
# Pattern: <div class="slim-vin">...000001...</div>
|
plac = "B123XYZ"
|
||||||
vin_fragment = "000001"
|
|
||||||
found_slim_vin = re.search(
|
found_slim_vin = re.search(
|
||||||
rf'<div[^>]*class="slim-vin[^"]*"[^>]*>[^<]*{re.escape(vin_fragment)}[^<]*</div>',
|
rf'<div[^>]*class="slim-vin[^"]*"[^>]*>[^<]*{re.escape(plac)}[^<]*</div>',
|
||||||
html,
|
html,
|
||||||
)
|
)
|
||||||
assert found_slim_vin, (
|
assert found_slim_vin, (
|
||||||
f"VIN '{vin_fragment}' trebuie sa fie in <div class=\"slim-vin\"> (block-level, "
|
f"placuta '{plac}' trebuie sa fie in <div class=\"slim-vin\"> (linia 1 a "
|
||||||
f"mono, linia 1 a randului slim). HTML gasit: "
|
f"randului slim). HTML gasit: "
|
||||||
+ html[max(0, html.find(vin_fragment) - 80):html.find(vin_fragment) + 80]
|
+ html[max(0, html.find(plac) - 80):html.find(plac) + 80]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# VIN integral NU mai e pe rand (max 2 linii) — traieste in modalul de detaliu.
|
||||||
|
assert "000001" not in html, "VIN-ul nu mai trebuie randat pe randul slim (5.16)"
|
||||||
|
|
||||||
def test_vin_lipsa_nu_genereaza_rand_gol(client):
|
|
||||||
"""Cand VIN-ul lipseste (sau e EMPTY='—'), slim-vin nu afiseaza '—' izolat.
|
def test_placuta_lipsa_nu_genereaza_rand_gol(client):
|
||||||
Fallback: slim-vin afiseaza vehicul_nr (nr. inmatriculare) cu clasa muted.
|
"""Cand placuta SI VIN-ul lipsesc, slim-vin nu afiseaza '—' izolat ca identificator.
|
||||||
(PRD 5.15 US-004: slim-vin are garda vin != '—')
|
Fallback (5.16): VIN scurt daca exista, altfel mesaj neutru ('fara numar') — niciodata
|
||||||
|
un em-dash singur ca identificator primar.
|
||||||
"""
|
"""
|
||||||
acct = _create_account_user("vin_gol@test.com")
|
acct = _create_account_user("vin_gol@test.com")
|
||||||
sid = _ins(acct, vin="", nr="B999TST") # VIN gol -> vin_scurt='—'
|
# Placuta prezenta -> e identificatorul primar pe linia 1.
|
||||||
|
sid1 = _ins(acct, vin="", nr="B999TST")
|
||||||
|
# Placuta SI VIN absente -> fallback 'fara numar' (nu '—' izolat).
|
||||||
|
sid2 = _ins(acct, vin="", nr="")
|
||||||
_login(client, "vin_gol@test.com")
|
_login(client, "vin_gol@test.com")
|
||||||
|
|
||||||
resp = client.get("/_fragments/submissions")
|
resp = client.get("/_fragments/submissions")
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
html = resp.text
|
html = resp.text
|
||||||
|
|
||||||
# Randul trebuie sa existe
|
# Ambele randuri exista
|
||||||
assert f'id="trimitere-row-{sid}"' in html
|
assert f'id="trimitere-row-{sid1}"' in html
|
||||||
|
assert f'id="trimitere-row-{sid2}"' in html
|
||||||
|
|
||||||
# slim-vin NU trebuie sa contina '—' izolat (VIN lipsa -> fallback vehicul_nr)
|
# Placuta vizibila cand exista
|
||||||
slim_vin_match = re.search(r'<div[^>]*class="slim-vin[^"]*"[^>]*>([^<]*)</div>', html)
|
assert "B999TST" in html, "placuta (nr. inmatriculare) lipseste de pe rand"
|
||||||
assert slim_vin_match, "slim-vin lipseste din randul cu VIN gol"
|
|
||||||
slim_vin_content = slim_vin_match.group(1).strip()
|
# Niciun slim-vin nu contine '—' izolat
|
||||||
assert slim_vin_content != "—", (
|
for m in re.finditer(r'<div[^>]*class="slim-vin[^"]*"[^>]*>([^<]*)</div>', html):
|
||||||
"slim-vin afiseaza '—' izolat cand VIN lipseste — "
|
assert m.group(1).strip() != "—", "slim-vin afiseaza '—' izolat ca identificator"
|
||||||
"trebuie sa afiseze vehicul_nr ca fallback"
|
|
||||||
)
|
# Fallback neutru cand placuta + VIN lipsesc
|
||||||
# Fallback: nr inmatriculare vizibil
|
assert "fara numar" in html, "fallback 'fara numar' lipseste cand placuta+VIN absente"
|
||||||
assert "B999TST" in html, "Nr inmatriculare (fallback) lipseste cand VIN e gol"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user