"""Lifecycle trimiteri blocate: sterge / re-pune in coada (PRD 5.6 US-009). Inchide lacuna descoperita live: un rand `error` (creds RAR gresite) ramane altfel permanent si nereparabil. Aceste helpere adauga DOUA tranzitii controlate — stergere de randuri ne-sent si `blocate -> queued` (re-clasificat) — fara a atinge logica de trimitere a worker-ului. Invariante (decizii §2 + /autoplan #20): - Opereaza DOAR pe `error`/`needs_data`/`needs_mapping`. `sent` (dovada de trimitere la RAR, audit) si `sending` (lease worker in zbor) sunt INTERZISE. - Scope-ul (apartenenta la cont) se evalueaza INAINTEA starii: un rand inexistent SAU al altui cont -> SubmissionNotFound (404, nu confirmam existenta, B3). Doar pe randuri proprii in stare gresita -> SubmissionStateConflict (409). - Ambele emit eveniment in jurnal (US-003): `submission_sters` / `submission_repus`. Functii cu `conn` (persistenta). Apelate din API (US-010) si din web (US-011). """ from __future__ import annotations import json from .mapping import ( account_or_default, account_scope_clause, classify_prezentare, load_mapping_meta, ) from .observ import log_event # Stari pe care le putem sterge / re-pune in coada (ne-sent, ne-in-zbor). _GESTIONABILE = ("error", "needs_data", "needs_mapping") class SubmissionNotFound(Exception): """Randul nu exista SAU apartine altui cont (acelasi mesaj — nu confirmam existenta).""" class SubmissionStateConflict(Exception): """Randul exista si e al contului, dar e intr-o stare protejata (sent/sending).""" def __init__(self, status: str): super().__init__(f"stare protejata: {status}") self.status = status def _fetch_scoped(conn, account_id: int, sid: int): scope_sql, scope_params = account_scope_clause(account_id) return conn.execute( f"SELECT * FROM submissions WHERE id=? AND {scope_sql}", [sid] + scope_params, ).fetchone() def delete_submission(conn, account_id: int, sid: int) -> dict: """Sterge un rand ne-sent al contului. Ridica SubmissionNotFound / SubmissionStateConflict. Intoarce {"submission_id", "status_anterior"} la succes. """ row = _fetch_scoped(conn, account_id, sid) if row is None: raise SubmissionNotFound() status = row["status"] if status not in _GESTIONABILE: raise SubmissionStateConflict(status) conn.execute("DELETE FROM submissions WHERE id=?", (sid,)) log_event( "submission_sters", account_id=account_or_default(account_id), mesaj=f"trimitere #{sid} stearsa din {status}", context={"submission_id": sid, "status_anterior": status}, conn=conn, ) return {"submission_id": sid, "status_anterior": status} def requeue_submission(conn, account_id: int, sid: int) -> dict: """Re-pune in coada un rand blocat al contului: re-ruleaza classify pe payload. `error -> queued` (cand continutul e valid) sau ramane `needs_data`/`needs_mapping` daca clasificarea o cere. Reseteaza retry_count/next_attempt_at/sending_since si CURATA `purge_after` (randul redevine activ, nu mai e candidat la purjare — US-013). Ridica SubmissionNotFound / SubmissionStateConflict. Intoarce {"submission_id", "status_anterior", "status_nou"}. """ row = _fetch_scoped(conn, account_id, sid) if row is None: raise SubmissionNotFound() status = row["status"] if status not in _GESTIONABILE: raise SubmissionStateConflict(status) try: content = json.loads(row["payload_json"]) if row["payload_json"] else {} if not isinstance(content, dict): content = {} except (ValueError, TypeError): content = {} mapping_meta = load_mapping_meta(conn, account_id) mapping = {op: m["cod_prestatie"] for op, m in mapping_meta.items()} cl = classify_prezentare(content, mapping, mapping_meta) conn.execute( "UPDATE submissions SET status=?, payload_json=?, rar_error=?, retry_count=0, " "next_attempt_at=NULL, sending_since=NULL, purge_after=NULL, updated_at=datetime('now') " "WHERE id=?", (cl["status"], json.dumps(cl["content"], ensure_ascii=False), cl["rar_error"], sid), ) log_event( "submission_repus", account_id=account_or_default(account_id), mesaj=f"trimitere #{sid} re-pusa: {status} -> {cl['status']}", context={"submission_id": sid, "status_anterior": status, "status_nou": cl["status"]}, conn=conn, ) return {"submission_id": sid, "status_anterior": status, "status_nou": cl["status"]}