feat(import): confirmare valori needs_review in bloc + rapid din tabel (B1)
Randurile needs_review (flaguri de coercion: VIN citit ca numar, odometru
float, data ambigua) cereau confirmare umana DOAR prin modalul de editare,
un rand pe rand. Adauga doua cai care pastreaza omul in bucla (fara
auto-accept):
- Buton "Confirma toate valorile" (bulk): ruta noua
POST /_import/{id}/confirma-toate-review marcheaza reviewed=1 pe TOATE
randurile needs_review din batch cu un click. Scoped pe cont (404
cross-account, 409 batch comis), o singura recompute + re-randare
#import-section, dupa modelul web_mapare_operatii.
- Confirm rapid per-rand direct din tabel: buton in coloana Actiuni pe
randurile needs_review, refoloseste ruta existenta /confirma-review
(reviewed=1 pe un singur rand) cu hx-swap="none"; HX-Trigger
reincarcaPreview reincarca sectiunea cu contoare/banner corecte.
Butonul bulk e randat in bannerul de discoverability (si in varianta OOB
din _preview_rand.html). Editarea unei valori reseteaza reviewed=0 ca
inainte (D#9, neschimbat).
Teste noi (tests/test_import_review.py): bulk marcheaza toate randurile,
guard committed 409, scoping 404 cross-account (fara efect pe randurile
altui cont), prezenta butonului rapid in tabel, confirm rapid per-rand
seteaza reviewed=1 fara a atinge alte randuri.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3611,6 +3611,63 @@ async def web_confirma_review(
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/_import/{import_id}/confirma-toate-review", response_class=HTMLResponse)
|
||||||
|
async def web_confirma_toate_review(
|
||||||
|
request: Request,
|
||||||
|
import_id: int,
|
||||||
|
) -> HTMLResponse:
|
||||||
|
"""Confirma in bloc TOATE randurile needs_review din batch → reviewed=1 (B1).
|
||||||
|
|
||||||
|
Un singur click marcheaza reviewed=1 pe toate randurile cu resolved_status='needs_review'
|
||||||
|
din batch-ul curent (om in bucla: operatorul confirma explicit intreg lotul, fara
|
||||||
|
auto-accept). Refoloseste EXACT logica din /confirma-review (reviewed=1 = marcaj separat,
|
||||||
|
NU camp de continut), aplicata in masa. La recalcul (_web_compute_preview) randurile cu
|
||||||
|
reviewed=1 si fara erori reale devin ok.
|
||||||
|
|
||||||
|
CSRF + scoped sesiune (404 cross-account) + guard committed (409). O singura recompute
|
||||||
|
+ re-randare #import-section la final (identic cu web_mapare_operatii).
|
||||||
|
"""
|
||||||
|
account_id = require_login(request)
|
||||||
|
conn = get_connection()
|
||||||
|
try:
|
||||||
|
form = await request.form()
|
||||||
|
verify_csrf(request, str(form.get("csrf_token") or ""))
|
||||||
|
|
||||||
|
# Guard batch: 404 cross-account (nu confirmam existenta), 409 committed.
|
||||||
|
acct = account_or_default(account_id)
|
||||||
|
batch = conn.execute(
|
||||||
|
"SELECT id, status FROM import_batches WHERE id=? AND account_id=?",
|
||||||
|
(import_id, acct),
|
||||||
|
).fetchone()
|
||||||
|
if not batch:
|
||||||
|
raise HTTPException(status_code=404, detail="batch de import inexistent sau inaccesibil")
|
||||||
|
if batch["status"] == "committed":
|
||||||
|
raise HTTPException(status_code=409, detail="batch deja comis; confirmarea nu mai are efect")
|
||||||
|
|
||||||
|
# Marcheaza reviewed=1 pe toate randurile needs_review ale batch-ului (scoped pe batch,
|
||||||
|
# deja verificat ca apartine contului). Acelasi marcaj ca /confirma-review, in masa.
|
||||||
|
cur = conn.execute(
|
||||||
|
"UPDATE import_rows SET reviewed=1 "
|
||||||
|
"WHERE batch_id=? AND resolved_status='needs_review'",
|
||||||
|
(import_id,),
|
||||||
|
)
|
||||||
|
n_confirmate = cur.rowcount if cur.rowcount is not None and cur.rowcount >= 0 else 0
|
||||||
|
|
||||||
|
message = (
|
||||||
|
f"Confirmate {n_confirmate} randuri cu valori de verificat."
|
||||||
|
if n_confirmate
|
||||||
|
else "Niciun rand de confirmat."
|
||||||
|
)
|
||||||
|
result = _web_compute_preview(conn, import_id, account_id)
|
||||||
|
if isinstance(result, str):
|
||||||
|
return templates.TemplateResponse("_upload.html", _ctx(request, error=result))
|
||||||
|
return templates.TemplateResponse("_preview_import.html", _ctx(
|
||||||
|
request, import_id=import_id, message=message, **result
|
||||||
|
))
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/_import/{import_id}/mapare-operatie", response_class=HTMLResponse)
|
@router.post("/_import/{import_id}/mapare-operatie", response_class=HTMLResponse)
|
||||||
async def web_mapare_operatie(
|
async def web_mapare_operatie(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|||||||
@@ -132,9 +132,24 @@
|
|||||||
style="margin-bottom:12px; padding:8px 14px; border-radius:6px;
|
style="margin-bottom:12px; padding:8px 14px; border-radius:6px;
|
||||||
background:color-mix(in srgb, var(--warn, #e6b34a) 12%, var(--card));
|
background:color-mix(in srgb, var(--warn, #e6b34a) 12%, var(--card));
|
||||||
border:1px solid var(--warn, #e6b34a); font-size:13px;">
|
border:1px solid var(--warn, #e6b34a); font-size:13px;">
|
||||||
|
<div>
|
||||||
Randurile cu <span class="pill s-needs_review" style="font-size:11px;">Verifica valori</span>
|
Randurile cu <span class="pill s-needs_review" style="font-size:11px;">Verifica valori</span>
|
||||||
nu pleaca la RAR pana le deschizi in modal si confirmi in modal
|
nu pleaca la RAR pana confirmi valorile. Verifica-le (butonul <strong>Confirma valorile</strong>
|
||||||
cu butonul <strong>Confirma valorile</strong>.
|
de pe rand sau in modal) sau, daca lotul e in regula, confirma-le pe toate deodata.
|
||||||
|
</div>
|
||||||
|
{# B1: buton bulk — un click marcheaza reviewed=1 pe toate randurile needs_review.
|
||||||
|
hx-swap="none": raspunsul re-randeaza #import-section prin outerHTML pe tinta. #}
|
||||||
|
<button type="button"
|
||||||
|
hx-post="/_import/{{ import_id }}/confirma-toate-review"
|
||||||
|
hx-vals='{"csrf_token": "{{ csrf_token or '' }}"}'
|
||||||
|
hx-target="#import-section"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-confirm="Confirmi valorile pentru toate cele {{ summary.get('needs_review', 0) }} randuri de verificat? Devin gata de trimis la RAR."
|
||||||
|
hx-disabled-elt="this"
|
||||||
|
style="margin-top:10px; min-height:40px; padding:8px 18px;
|
||||||
|
background:var(--ok, #2a7); color:#fff; border-color:transparent; font-size:13px;">
|
||||||
|
Confirma toate valorile ({{ summary.get('needs_review', 0) }})
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -62,6 +62,22 @@
|
|||||||
<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-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') %}
|
||||||
|
<div style="display:inline-flex; gap:6px; flex-wrap:wrap; justify-content:center;">
|
||||||
|
{# B1: confirm rapid per-rand direct din tabel (fara a deschide modalul).
|
||||||
|
Refoloseste ruta /confirma-review (reviewed=1 pe un singur rand). hx-swap="none":
|
||||||
|
raspunsul (empty div) NU se insereaza nicaieri; HX-Trigger reincarcaPreview
|
||||||
|
reincarca sectiunea (contoare + banner corecte). Editarea reseteaza reviewed=0. #}
|
||||||
|
{% if status == 'needs_review' %}
|
||||||
|
<button type="button" class="btn-confirma-rapid"
|
||||||
|
style="min-height:36px; padding:6px 14px; font-size:13px;
|
||||||
|
background:var(--ok, #2a7); color:#fff; border-color:transparent;"
|
||||||
|
hx-post="/_import/{{ import_id }}/rand/{{ row.row_index }}/confirma-review"
|
||||||
|
hx-vals='{"csrf_token": "{{ csrf_token or '' }}"}'
|
||||||
|
hx-swap="none" hx-disabled-elt="this"
|
||||||
|
aria-label="Confirma valorile randului {{ row.row_index + 1 }} (VIN: {{ res.get('vin', '') }})">
|
||||||
|
Confirma valorile
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
<button type="button" class="btn-editeaza"
|
<button type="button" class="btn-editeaza"
|
||||||
style="min-height:36px; padding:6px 14px; font-size:13px;
|
style="min-height:36px; padding:6px 14px; font-size:13px;
|
||||||
background:transparent; border-color:var(--line); color:var(--ink);"
|
background:transparent; border-color:var(--line); color:var(--ink);"
|
||||||
@@ -70,6 +86,7 @@
|
|||||||
aria-label="Editeaza randul {{ row.row_index + 1 }} (VIN: {{ res.get('vin', '') }})">
|
aria-label="Editeaza randul {{ row.row_index + 1 }} (VIN: {{ res.get('vin', '') }})">
|
||||||
Editeaza
|
Editeaza
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -93,9 +110,22 @@
|
|||||||
style="margin-bottom:12px; padding:8px 14px; border-radius:6px;
|
style="margin-bottom:12px; padding:8px 14px; border-radius:6px;
|
||||||
background:color-mix(in srgb, var(--warn, #e6b34a) 12%, var(--card));
|
background:color-mix(in srgb, var(--warn, #e6b34a) 12%, var(--card));
|
||||||
border:1px solid var(--warn, #e6b34a); font-size:13px;">
|
border:1px solid var(--warn, #e6b34a); font-size:13px;">
|
||||||
|
<div>
|
||||||
Randurile cu <span class="pill s-needs_review" style="font-size:11px;">Verifica valori</span>
|
Randurile cu <span class="pill s-needs_review" style="font-size:11px;">Verifica valori</span>
|
||||||
nu pleaca la RAR pana le deschizi in modal si confirmi in modal
|
nu pleaca la RAR pana confirmi valorile. Verifica-le (butonul <strong>Confirma valorile</strong>
|
||||||
cu butonul <strong>Confirma valorile</strong>.
|
de pe rand sau in modal) sau, daca lotul e in regula, confirma-le pe toate deodata.
|
||||||
|
</div>
|
||||||
|
<button type="button"
|
||||||
|
hx-post="/_import/{{ import_id }}/confirma-toate-review"
|
||||||
|
hx-vals='{"csrf_token": "{{ csrf_token or '' }}"}'
|
||||||
|
hx-target="#import-section"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-confirm="Confirmi valorile pentru toate cele {{ summary.get('needs_review', 0) }} randuri de verificat? Devin gata de trimis la RAR."
|
||||||
|
hx-disabled-elt="this"
|
||||||
|
style="margin-top:10px; min-height:40px; padding:8px 18px;
|
||||||
|
background:var(--ok, #2a7); color:#fff; border-color:transparent; font-size:13px;">
|
||||||
|
Confirma toate valorile ({{ summary.get('needs_review', 0) }})
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -583,6 +583,228 @@ def test_confirma_review_form_nu_foloseste_hx_swap_none():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _upload_and_preview_rows(client: TestClient, rows: list[dict]) -> int:
|
||||||
|
"""Upload CSV cu `rows` + salveaza mapare fara format_data -> preview.
|
||||||
|
|
||||||
|
Generalizarea lui `_upload_and_preview_needs_review` pentru mai multe randuri
|
||||||
|
(fiecare cu data ambigua -> needs_review). Intoarce import_id.
|
||||||
|
"""
|
||||||
|
csv_data = _csv_bytes(rows)
|
||||||
|
csrf = _get_csrf(client)
|
||||||
|
r = client.post(
|
||||||
|
"/_import/upload",
|
||||||
|
files={"file": ("test.csv", io.BytesIO(csv_data), "text/csv")},
|
||||||
|
data={"csrf_token": csrf},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200, r.text
|
||||||
|
m = re.search(r"/_import/(\d+)/mapare-coloane", r.text)
|
||||||
|
assert m, f"import_id negasit in raspuns: {r.text[:300]}"
|
||||||
|
iid = int(m.group(1))
|
||||||
|
colnames = list(rows[0].keys())
|
||||||
|
canons = [_MAP_COLS[c] for c in colnames]
|
||||||
|
csrf2 = _get_csrf(client)
|
||||||
|
r2 = client.post(f"/_import/{iid}/mapare-coloane", data={
|
||||||
|
"colname": colnames,
|
||||||
|
"canon": canons,
|
||||||
|
"format_data": "", # fara format -> ambiguous -> needs_review
|
||||||
|
"csrf_token": csrf2,
|
||||||
|
})
|
||||||
|
assert r2.status_code == 200, r2.text
|
||||||
|
return iid
|
||||||
|
|
||||||
|
|
||||||
|
# Trei randuri, VIN-uri distincte (nu se dedupe ca duplicat), toate cu data ambigua.
|
||||||
|
_ROWS_MULTI_NEEDS_REVIEW = [
|
||||||
|
{"VIN": "WVWZZZ1KZAW000123", "Nr": "B001TST", "Data": "05.06.2026", "KM": "123456", "Operatie": "OP-1"},
|
||||||
|
{"VIN": "WVWZZZ1KZAW000456", "Nr": "B002TST", "Data": "07.08.2026", "KM": "223456", "Operatie": "OP-1"},
|
||||||
|
{"VIN": "WVWZZZ1KZAW000789", "Nr": "B003TST", "Data": "09.10.2026", "KM": "323456", "Operatie": "OP-1"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirma_toate_review_marcheaza_toate(client):
|
||||||
|
"""B1 bulk: POST /_import/{id}/confirma-toate-review seteaza reviewed=1 pe TOATE
|
||||||
|
randurile needs_review din batch cu un singur click.
|
||||||
|
|
||||||
|
Verifica:
|
||||||
|
- Raspuns 200 cu preview re-randat (fragment #import-section)
|
||||||
|
- reviewed=1 in DB pentru toate cele 3 randuri
|
||||||
|
- dupa recalcul, randurile nu mai sunt needs_review (devin ok -> gata de trimis)
|
||||||
|
"""
|
||||||
|
_seed_op1()
|
||||||
|
iid = _upload_and_preview_rows(client, _ROWS_MULTI_NEEDS_REVIEW)
|
||||||
|
|
||||||
|
# Toate randurile pornesc needs_review (reviewed=0)
|
||||||
|
for i in range(3):
|
||||||
|
assert _get_reviewed(iid, i) == 0, f"randul {i} trebuie sa fie reviewed=0 initial"
|
||||||
|
|
||||||
|
csrf = _get_csrf(client)
|
||||||
|
r = client.post(f"/_import/{iid}/confirma-toate-review", data={"csrf_token": csrf})
|
||||||
|
assert r.status_code == 200, r.text
|
||||||
|
# Raspunsul re-randeaza sectiunea de preview
|
||||||
|
assert "import-section" in r.text, "bulk trebuie sa re-randeze #import-section"
|
||||||
|
|
||||||
|
# Toate randurile confirmate in DB
|
||||||
|
for i in range(3):
|
||||||
|
assert _get_reviewed(iid, i) == 1, \
|
||||||
|
f"randul {i} trebuie sa fie reviewed=1 dupa confirmarea in bloc"
|
||||||
|
|
||||||
|
# Recalcul: niciun rand nu mai e needs_review (toate ok)
|
||||||
|
r2 = client.get(f"/_import/{iid}/preview")
|
||||||
|
assert r2.status_code == 200, r2.text
|
||||||
|
assert 'data-status="needs_review"' not in r2.text, \
|
||||||
|
"dupa confirmarea in bloc, niciun rand nu mai trebuie sa fie needs_review"
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirma_toate_review_guard_committed_409(client):
|
||||||
|
"""POST confirma-toate-review pe batch deja comis -> 409."""
|
||||||
|
_seed_op1()
|
||||||
|
iid = _upload_and_preview_rows(client, _ROWS_MULTI_NEEDS_REVIEW)
|
||||||
|
|
||||||
|
from app.db import get_connection
|
||||||
|
conn = get_connection()
|
||||||
|
try:
|
||||||
|
conn.execute("UPDATE import_batches SET status='committed' WHERE id=?", (iid,))
|
||||||
|
conn.commit()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
csrf = _get_csrf(client)
|
||||||
|
r = client.post(f"/_import/{iid}/confirma-toate-review", data={"csrf_token": csrf})
|
||||||
|
assert r.status_code == 409, \
|
||||||
|
f"confirma-toate-review pe batch committed trebuie sa returneze 409, got {r.status_code}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirma_toate_review_scoped_404_alt_cont():
|
||||||
|
"""POST confirma-toate-review pe batch-ul altui cont -> 404, iar randurile
|
||||||
|
contului A raman needs_review (reviewed=0): bulk-ul altui cont NU le atinge."""
|
||||||
|
tmp = tempfile.mkdtemp()
|
||||||
|
env_patch = {
|
||||||
|
"AUTOPASS_DB_PATH": os.path.join(tmp, "scope_bulk.db"),
|
||||||
|
"AUTOPASS_WEB_AUTH_REQUIRED": "true",
|
||||||
|
}
|
||||||
|
for k, v in env_patch.items():
|
||||||
|
os.environ[k] = v
|
||||||
|
from app.config import get_settings
|
||||||
|
get_settings.cache_clear()
|
||||||
|
from app.crypto import reset_cache
|
||||||
|
reset_cache()
|
||||||
|
from app.web import ratelimit
|
||||||
|
ratelimit._hits.clear()
|
||||||
|
from app.main import app
|
||||||
|
|
||||||
|
try:
|
||||||
|
with TestClient(app, follow_redirects=False) as c:
|
||||||
|
from app.db import get_connection
|
||||||
|
from app.accounts import create_account
|
||||||
|
from app.users import create_user
|
||||||
|
conn = get_connection()
|
||||||
|
try:
|
||||||
|
acct1 = create_account(conn, "Firma A", active=True)
|
||||||
|
create_user(conn, acct1, "userA@test.com", "parola123secure")
|
||||||
|
acct2 = create_account(conn, "Firma B", active=True)
|
||||||
|
create_user(conn, acct2, "userB@test.com", "parola123secure")
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) "
|
||||||
|
"VALUES ('R-FRANE','Reparatie frane')"
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR IGNORE INTO operations_mapping "
|
||||||
|
"(account_id, cod_op_service, cod_prestatie, auto_send) "
|
||||||
|
"VALUES (?, 'OP-1', 'R-FRANE', 1)", (acct1,)
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def _login(client, email, pwd="parola123secure"):
|
||||||
|
resp = client.get("/login")
|
||||||
|
m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text) or \
|
||||||
|
re.search(r'value="([^"]+)"\s+name="csrf_token"', resp.text)
|
||||||
|
assert m
|
||||||
|
client.post("/login", data={"email": email, "parola": pwd, "csrf_token": m.group(1)})
|
||||||
|
|
||||||
|
def _csrf():
|
||||||
|
r = c.get("/")
|
||||||
|
m = re.search(r'name="csrf_token"\s+value="([^"]+)"', r.text) or \
|
||||||
|
re.search(r'value="([^"]+)"\s+name="csrf_token"', r.text)
|
||||||
|
return m.group(1) if m else ""
|
||||||
|
|
||||||
|
# userA creeaza batch cu randuri needs_review
|
||||||
|
_login(c, "userA@test.com")
|
||||||
|
rows = _ROWS_MULTI_NEEDS_REVIEW
|
||||||
|
csrf = _csrf()
|
||||||
|
r = c.post(
|
||||||
|
"/_import/upload",
|
||||||
|
files={"file": ("test.csv", io.BytesIO(_csv_bytes(rows)), "text/csv")},
|
||||||
|
data={"csrf_token": csrf},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
m = re.search(r"/_import/(\d+)/mapare-coloane", r.text)
|
||||||
|
assert m
|
||||||
|
iid = int(m.group(1))
|
||||||
|
colnames = list(rows[0].keys())
|
||||||
|
canons = [_MAP_COLS[c] for c in colnames]
|
||||||
|
csrf2 = _csrf()
|
||||||
|
c.post(f"/_import/{iid}/mapare-coloane", data={
|
||||||
|
"colname": colnames, "canon": canons, "format_data": "", "csrf_token": csrf2,
|
||||||
|
})
|
||||||
|
|
||||||
|
# userB incearca bulk pe batch-ul lui A -> 404
|
||||||
|
_login(c, "userB@test.com")
|
||||||
|
csrf3 = _csrf()
|
||||||
|
r2 = c.post(f"/_import/{iid}/confirma-toate-review", data={"csrf_token": csrf3})
|
||||||
|
assert r2.status_code == 404, \
|
||||||
|
f"confirma-toate-review cross-account trebuie sa returneze 404, got {r2.status_code}"
|
||||||
|
|
||||||
|
# Randurile lui A raman needs_review (reviewed=0): nu au fost atinse.
|
||||||
|
conn = get_connection()
|
||||||
|
try:
|
||||||
|
n = conn.execute(
|
||||||
|
"SELECT COUNT(*) AS c FROM import_rows WHERE batch_id=? AND reviewed=1",
|
||||||
|
(iid,),
|
||||||
|
).fetchone()["c"]
|
||||||
|
assert n == 0, "bulk-ul altui cont NU trebuie sa marcheze reviewed pe randurile lui A"
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
finally:
|
||||||
|
for k in env_patch:
|
||||||
|
if k in os.environ:
|
||||||
|
del os.environ[k]
|
||||||
|
ratelimit._hits.clear()
|
||||||
|
get_settings.cache_clear()
|
||||||
|
reset_cache()
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirm_rapid_buton_prezent_in_tabel(client):
|
||||||
|
"""B1 quick: randul needs_review are butonul de confirm rapid direct in tabel,
|
||||||
|
care POST-eaza pe ruta /confirma-review (fara a deschide modalul)."""
|
||||||
|
_seed_op1()
|
||||||
|
iid = _upload_and_preview_needs_review(client)
|
||||||
|
|
||||||
|
r = client.get(f"/_import/{iid}/preview")
|
||||||
|
assert r.status_code == 200, r.text
|
||||||
|
html = r.text
|
||||||
|
assert "btn-confirma-rapid" in html, \
|
||||||
|
"randul needs_review trebuie sa aiba butonul de confirm rapid in tabel"
|
||||||
|
assert f"/_import/{iid}/rand/0/confirma-review" in html, \
|
||||||
|
"butonul de confirm rapid trebuie sa POST-eze pe ruta /confirma-review"
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirm_rapid_per_rand_seteaza_reviewed(client):
|
||||||
|
"""B1 quick: POST /confirma-review din tabel (fara modal) seteaza reviewed=1 pe un
|
||||||
|
singur rand si NU atinge celelalte randuri needs_review din batch."""
|
||||||
|
_seed_op1()
|
||||||
|
iid = _upload_and_preview_rows(client, _ROWS_MULTI_NEEDS_REVIEW)
|
||||||
|
|
||||||
|
csrf = _get_csrf(client)
|
||||||
|
r = client.post(f"/_import/{iid}/rand/1/confirma-review", data={"csrf_token": csrf})
|
||||||
|
assert r.status_code == 200, r.text
|
||||||
|
|
||||||
|
assert _get_reviewed(iid, 1) == 1, "randul confirmat rapid trebuie sa fie reviewed=1"
|
||||||
|
assert _get_reviewed(iid, 0) == 0, "confirmul per-rand NU trebuie sa atinga alte randuri"
|
||||||
|
assert _get_reviewed(iid, 2) == 0, "confirmul per-rand NU trebuie sa atinga alte randuri"
|
||||||
|
|
||||||
|
|
||||||
def test_confirma_review_cere_reincarcarea_preview(client):
|
def test_confirma_review_cere_reincarcarea_preview(client):
|
||||||
"""Contractul nou (dogfood 5.13): confirma-review NU mai depinde de scriptul updateN
|
"""Contractul nou (dogfood 5.13): confirma-review NU mai depinde de scriptul updateN
|
||||||
din payload (care, cu OOB pe <tr> rupt, lasa randul stale). Acum cere reincarcaPreview,
|
din payload (care, cu OOB pe <tr> rupt, lasa randul stale). Acum cere reincarcaPreview,
|
||||||
|
|||||||
Reference in New Issue
Block a user