"""Teste regula "exclude de la declarare" (operations_mapping.exclus). O operatie exclusa nu se declara la RAR: randurile de import cu toate operatiile excluse devin 'excluded' (Nedeclarat) si nu se comit; operatiile excluse dispar din panoul de mapare; submission-urile API blocate trec pe needs_data cu motiv. Un cod ales explicit pe rand bate regula de excludere. """ from __future__ import annotations import json import os import tempfile import pytest from fastapi.testclient import TestClient from tests.test_web_preview_edit import ( # helpers reutilizate _csv_bytes, _get_csrf, _upload_and_preview, ) @pytest.fixture() def client(monkeypatch): tmp = tempfile.mkdtemp() monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "excl.db")) monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false") from app.config import get_settings get_settings.cache_clear() from app.crypto import reset_cache reset_cache() from app.main import app with TestClient(app) as c: yield c get_settings.cache_clear() reset_cache() _ROWS_ITP = [ { "VIN": "WVWZZZ3CZ9E123456", "Nr": "TM789BC", "Data": "2026-05-15", "KM": "82500", "Operatie": "OP-ITP", }, { "VIN": "WVWZZZ1KZAW000123", "Nr": "B001TST", "Data": "2026-06-10", "KM": "123456", "Operatie": "OP-1", }, ] def _seed(account_id: int = 1) -> None: """Nomenclator + mapare OP-1 -> R-FRANE (OP-ITP ramane nemapat).""" from app.db import get_connection conn = get_connection() try: for cod, nume in (("R-FRANE", "Reparatie frane"), ("OE-2", "Verificare")): conn.execute( "INSERT OR REPLACE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)", (cod, nume), ) conn.execute( "INSERT OR IGNORE INTO operations_mapping (account_id, cod_op_service, cod_prestatie, auto_send) " "VALUES (?, 'OP-1', 'R-FRANE', 1)", (account_id,), ) conn.commit() finally: conn.close() def _row_status(iid: int, row_index: int) -> str: from app.db import get_connection from app.web.routes import _preview_one_row conn = get_connection() try: result, row = _preview_one_row(conn, iid, 1, row_index) assert row is not None and not isinstance(result, str) return row["resolved_status"] finally: conn.close() def test_panoul_de_mapare_ofera_optiunea_nedeclarat(client): _seed() iid = _upload_and_preview(client, rows=_ROWS_ITP) r = client.get(f"/_import/{iid}/preview") assert r.status_code == 200 assert '__NEDECLARAT__' in r.text, "Selectul de mapare trebuie sa ofere 'Nu se declara la RAR'" def test_exclude_din_preview_marcheaza_randul_nedeclarat(client): _seed() iid = _upload_and_preview(client, rows=_ROWS_ITP) assert _row_status(iid, 0) == "needs_mapping" csrf = _get_csrf(client) r = client.post(f"/_import/{iid}/mapare-operatii", data={ "cod_op_service": "OP-ITP", "cod_prestatie": "__NEDECLARAT__", "csrf_token": csrf, }) assert r.status_code == 200, r.text assert "Excluse de la declarare: OP-ITP" in r.text assert "Nedeclarat" in r.text assert _row_status(iid, 0) == "excluded" assert _row_status(iid, 1) == "ok" # Operatia exclusa nu mai apare in panoul de mapat assert "Operatii de mapat" not in r.text or "OP-ITP" not in r.text.split("Operatii de mapat")[1][:2000] # Regula persistata from app.db import get_connection conn = get_connection() try: rule = conn.execute( "SELECT cod_prestatie, exclus FROM operations_mapping WHERE account_id=1 AND cod_op_service='OP-ITP'" ).fetchone() finally: conn.close() assert rule is not None and rule["exclus"] == 1 and rule["cod_prestatie"] == "" def test_commit_sare_randurile_excluse(client): _seed() iid = _upload_and_preview(client, rows=_ROWS_ITP) csrf = _get_csrf(client) r = client.post(f"/_import/{iid}/mapare-operatii", data={ "cod_op_service": "OP-ITP", "cod_prestatie": "__NEDECLARAT__", "csrf_token": csrf, }) assert r.status_code == 200 # Doar randul OP-1 e "gata de trimis" -> n_confirmat=1 csrf = _get_csrf(client) rc = client.post(f"/_import/{iid}/confirma", data={"n_confirmat": "1", "csrf_token": csrf}) assert rc.status_code == 200, rc.text from app.db import get_connection conn = get_connection() try: subs = conn.execute("SELECT payload_json FROM submissions WHERE batch_id=?", (iid,)).fetchall() finally: conn.close() assert len(subs) == 1, "Doar randul declarabil se enqueue-uieste" payload = json.loads(subs[0]["payload_json"]) assert payload["prestatii"][0]["cod_prestatie"] == "R-FRANE" def test_rand_mixt_exclude_doar_operatia_exclusa(client): """Rand cu 2 operatii (una exclusa, una mapata) -> ok; payload fara cea exclusa.""" _seed() rows = [{ "VIN": "WVWZZZ1KZAW000789", "Nr": "B003TST", "Data": "2026-06-12", "KM": "90000", "Operatie": "OP-ITP", }] iid = _upload_and_preview(client, rows=rows) csrf = _get_csrf(client) client.post(f"/_import/{iid}/mapare-operatii", data={ "cod_op_service": "OP-ITP", "cod_prestatie": "__NEDECLARAT__", "csrf_token": csrf, }) assert _row_status(iid, 0) == "excluded" # Editorul de rand: adauga explicit un cod suplimentar -> randul redevine declarabil r = client.post(f"/_import/{iid}/rand/0/editeaza", data={ "cod_prestatie": ["", "OE-2"], "chip_op_service": ["OP-ITP", ""], "chip_denumire": ["", ""], }) assert r.status_code == 200, r.text assert _row_status(iid, 0) == "ok" csrf = _get_csrf(client) rc = client.post(f"/_import/{iid}/confirma", data={"n_confirmat": "1", "csrf_token": csrf}) assert rc.status_code == 200, rc.text from app.db import get_connection conn = get_connection() try: sub = conn.execute("SELECT payload_json FROM submissions WHERE batch_id=?", (iid,)).fetchone() finally: conn.close() payload = json.loads(sub["payload_json"]) coduri = [p.get("cod_prestatie") for p in payload["prestatii"]] assert coduri == ["OE-2"], f"Operatia exclusa nu trebuie sa plece la RAR: {coduri}" def test_cod_explicit_pe_operatie_bate_regula_de_excludere(client): """Chips cu cod explicit PE operatia exclusa -> codul ales de user castiga.""" _seed() rows = [_ROWS_ITP[0]] iid = _upload_and_preview(client, rows=rows) csrf = _get_csrf(client) client.post(f"/_import/{iid}/mapare-operatii", data={ "cod_op_service": "OP-ITP", "cod_prestatie": "__NEDECLARAT__", "csrf_token": csrf, }) assert _row_status(iid, 0) == "excluded" r = client.post(f"/_import/{iid}/rand/0/editeaza", data={ "cod_prestatie": "OE-2", "chip_op_service": "OP-ITP", "chip_denumire": "Inspectie", }) assert r.status_code == 200, r.text assert _row_status(iid, 0) == "ok" def test_mapari_tab_exclude_si_rerezolva_submissions_api(client): """Excluderea din tab-ul Mapari trece submission-urile blocate (canal API) pe needs_data.""" _seed() # Submission blocat pe OP-XYZ (canal API, batch_id NULL) r = client.post("/v1/prezentari", json={ "rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [{ "vin": "WVWZZZ1KZAW000555", "nr_inmatriculare": "B055TST", "data_prestatie": "2026-06-20", "odometru_final": "50000", "prestatii": [{"cod_op_service": "OP-XYZ", "denumire": "Operatie interna"}], }], }) assert r.status_code == 200, r.text sid = r.json()["results"][0]["submission_id"] assert r.json()["results"][0]["status"] == "needs_mapping" # Operatia apare la mapat frag = client.get("/_fragments/mapari") assert "OP-XYZ" in frag.text csrf = _get_csrf(client) resp = client.post("/mapari", data={ "cod_op_service": "OP-XYZ", "cod_prestatie": "__NEDECLARAT__", "csrf_token": csrf, }) assert resp.status_code == 200, resp.text assert "exclus de la declarare" in resp.text from app.db import get_connection conn = get_connection() try: row = conn.execute("SELECT status, rar_error FROM submissions WHERE id=?", (sid,)).fetchone() # Operatia exclusa nu mai apare in pending from app.mapping import pending_unmapped pend = pending_unmapped(conn, 1) finally: conn.close() assert row["status"] == "needs_data" assert "excluse de la declarare" in (row["rar_error"] or "") assert all(e["cod_op_service"] != "OP-XYZ" for e in pend) def test_maparea_unei_operatii_excluse_reactiveaza_declararea(client): """save_mapping peste o regula de excludere reseteaza exclus=0.""" _seed() from app.db import get_connection from app.mapping import save_exclusion, load_excluded_ops, load_mapping conn = get_connection() try: save_exclusion(conn, 1, "OP-ITP") assert "OP-ITP" in load_excluded_ops(conn, 1) from app.mapping import save_mapping save_mapping(conn, 1, "OP-ITP", "OE-2", auto_send=False) assert "OP-ITP" not in load_excluded_ops(conn, 1) assert load_mapping(conn, 1)["OP-ITP"] == "OE-2" finally: conn.close() def test_ingestie_api_cu_operatie_exclusa_nu_blocheaza(client): """POST /v1/prezentari cu operatie deja exclusa -> needs_data cu motiv, nu needs_mapping.""" _seed() from app.db import get_connection from app.mapping import save_exclusion conn = get_connection() try: save_exclusion(conn, 1, "OP-ITP") conn.commit() finally: conn.close() r = client.post("/v1/prezentari", json={ "rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [{ "vin": "WVWZZZ1KZAW000777", "nr_inmatriculare": "B077TST", "data_prestatie": "2026-06-21", "odometru_final": "60000", "prestatii": [{"cod_op_service": "OP-ITP", "denumire": "ITP"}], }], }) assert r.status_code == 200, r.text rez = r.json()["results"][0] assert rez["status"] == "needs_data", rez