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:
Claude Agent
2026-06-29 14:44:10 +00:00
parent e1243f603e
commit 8f39dfbc1e
14 changed files with 212 additions and 130 deletions

View File

@@ -495,7 +495,9 @@ def _insert_submissions_sent(account_id: int, n: int) -> None:
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.
"""
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")
_set_trial_until(acct_id, future)
resp = client.get("/_fragments/status")
resp = client.get("/", follow_redirects=True)
assert resp.status_code == 200
html = resp.text
@@ -516,7 +518,8 @@ def test_afisaj_plan_si_zile_trial(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")
_login(client, "consumlun@test.com", "parolasecreta10")
@@ -525,7 +528,7 @@ def test_afisaj_consum_lunar(client):
# Insereaza 5 submissions sent luna asta
_insert_submissions_sent(acct_id, 5)
resp = client.get("/_fragments/status")
resp = client.get("/", follow_redirects=True)
assert resp.status_code == 200
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")
_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
html = resp.text
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")
_set_trial_until(acct_id, future_1)
resp = client.get("/_fragments/status")
resp = client.get("/", follow_redirects=True)
assert resp.status_code == 200
html = resp.text
assert "1 zi" in html, f"'1 zi' (singular) lipseste la o zi ramasa. HTML: {html[:800]}"

View File

@@ -107,8 +107,9 @@ def test_submissions_coloane_umane(client):
assert "B777ZZZ" in html, "Nr inmatriculare din payload lipseste"
assert "Reparatie frane" in html, "Operatia din payload lipseste"
# Nr. prezentare RAR accesibil pe linia meta discreta
assert "68516" in html, "Nr. prezentare RAR lipseste din linia meta"
# 5.16: #id_prezentare nu mai e pe rand (randul are MAX 2 linii) — detaliul complet
# (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):
@@ -426,9 +427,9 @@ def test_detaliu_trimitere_404_cross_account(client):
# ---------------------------------------------------------------------------
def test_rand_slim_vin_operatie_pill(client):
"""US-004: fiecare rand slim afiseaza VIN scurt in .slim-vin, operatie+ora in
.slim-meta si un pill de stare cu clasa stare_css si eticheta stare_scurt.
Lista e inconjurata de .lista-trimiteri-slim.
"""5.16: fiecare rand slim are 2 linii — L1 placuta (nr. inmatriculare) in .slim-vin,
L2 cod RAR · operatie · data in .slim-meta, plus un pill de stare cu clasa stare_css
si eticheta stare_scurt. Lista e inconjurata de .lista-trimiteri-slim.
"""
acct = _create_account_user("slim1@test.com")
_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 "trimitere-slim" in html, "trimitere-slim lipseste din raspuns"
# VIN scurt in clasa slim-vin (mono, linia 1)
assert "slim-vin" in html, "slim-vin lipseste — linia 1 VIN mono"
# L1: placuta (identificator primar) in clasa slim-vin
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)
assert "slim-meta" in html, "slim-meta lipseste — linia 2 muted"
# L2: cod RAR · operatie · data (slim-meta / slim-rand2)
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)
assert "000777" in html, "VIN scurt (ultimele 6 cifre) lipseste"
# VIN integral nu mai e pe rand (5.16) — traieste in modalul de detaliu.
assert "000777" not in html, "VIN scurt nu mai trebuie randat pe randul slim (2 linii)"
# Pill de stare: clasa CSS + eticheta scurta
assert "s-sent" in html, "clasa pill s-sent lipseste"

View File

@@ -81,12 +81,13 @@ def client(monkeypatch):
get_settings.cache_clear()
def test_vin_pe_rand_separat_sub_nr(client):
"""VIN-ul apare intr-un element block-level cu clasa slim-vin (PRD 5.15 US-004).
def test_placuta_pe_rand_identificator_primar(client):
"""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 e acum identificatorul PRINCIPAL, linia 1 a randului slim,
in <div class="slim-vin"> (mono, prominent, block-level). NU mai e muted.
PRD 5.15 (US-004): VIN era identificatorul primar pe linia 1.
5.16 (directiva user): operatorul scaneaza placuta de pe comanda, nu VIN-ul de 17
caractere — placuta devine linia 1, VIN integral se muta in modalul de detaliu.
"""
acct = _create_account_user("vin_layout@test.com")
_ins(acct, vin="WVWZZZ1JZXW000001", nr="B123XYZ")
@@ -96,46 +97,51 @@ def test_vin_pe_rand_separat_sub_nr(client):
assert resp.status_code == 200
html = resp.text
# VIN trunchiat trebuie sa apara in HTML
assert "000001" in html, "VIN-ul trunchiat trebuie sa apara in lista slim"
# Placuta trebuie sa apara in HTML
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)
# Pattern: <div class="slim-vin">...000001...</div>
vin_fragment = "000001"
# Placuta e intr-un element block-level (div cu clasa slim-vin)
plac = "B123XYZ"
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,
)
assert found_slim_vin, (
f"VIN '{vin_fragment}' trebuie sa fie in <div class=\"slim-vin\"> (block-level, "
f"mono, linia 1 a randului slim). HTML gasit: "
+ html[max(0, html.find(vin_fragment) - 80):html.find(vin_fragment) + 80]
f"placuta '{plac}' trebuie sa fie in <div class=\"slim-vin\"> (linia 1 a "
f"randului slim). HTML gasit: "
+ 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.
Fallback: slim-vin afiseaza vehicul_nr (nr. inmatriculare) cu clasa muted.
(PRD 5.15 US-004: slim-vin are garda vin != '')
def test_placuta_lipsa_nu_genereaza_rand_gol(client):
"""Cand placuta SI VIN-ul lipsesc, slim-vin nu afiseaza '' izolat ca identificator.
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")
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")
resp = client.get("/_fragments/submissions")
assert resp.status_code == 200
html = resp.text
# Randul trebuie sa existe
assert f'id="trimitere-row-{sid}"' in html
# Ambele randuri exista
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)
slim_vin_match = re.search(r'<div[^>]*class="slim-vin[^"]*"[^>]*>([^<]*)</div>', html)
assert slim_vin_match, "slim-vin lipseste din randul cu VIN gol"
slim_vin_content = slim_vin_match.group(1).strip()
assert slim_vin_content != "", (
"slim-vin afiseaza '' izolat cand VIN lipseste — "
"trebuie sa afiseze vehicul_nr ca fallback"
)
# Fallback: nr inmatriculare vizibil
assert "B999TST" in html, "Nr inmatriculare (fallback) lipseste cand VIN e gol"
# Placuta vizibila cand exista
assert "B999TST" in html, "placuta (nr. inmatriculare) lipseste de pe rand"
# Niciun slim-vin nu contine '—' izolat
for m in re.finditer(r'<div[^>]*class="slim-vin[^"]*"[^>]*>([^<]*)</div>', html):
assert m.group(1).strip() != "", "slim-vin afiseaza '' izolat ca identificator"
# Fallback neutru cand placuta + VIN lipsesc
assert "fara numar" in html, "fallback 'fara numar' lipseste cand placuta+VIN absente"