Eliminat zgomotul de trasabilitate (US-xxx, PRD x.x, Rn, OV-x, Tn, decizii/naratiune istorica) din 41 fisiere app/ + template-uri. Pastrate comentariile care documenteaza invarianti si logica ne-evidenta (idempotenta/hash, reconciliere anti-duplicat, RAR 500 esec definitiv, creds per cont, WAF User-Agent, 422 fara echo de parola, scope NULL->1), curatate doar de tokeni. Verificare: pentru cele 27 module .py curatate, structura de cod (tokeni non-comentariu/ non-string) e IDENTICA fata de HEAD -> doar comentarii/docstring-uri schimbate. Singura schimbare de cod e in tests/test_web_responsive.py (scos 3 assert pe markeri US-006/007/008, inlocuite de asertiunile structurale alaturate). 0 tokeni US/PRD reziduali in app/. Regresie: 896 passed, 1 deselected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
120 lines
4.5 KiB
Python
120 lines
4.5 KiB
Python
"""Lifecycle trimiteri blocate: sterge / re-pune in coada.
|
|
|
|
Inchide lacuna: un rand `error` (creds RAR gresite) ar 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:
|
|
- 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). Doar pe randuri
|
|
proprii in stare gresita -> SubmissionStateConflict (409).
|
|
- Ambele emit eveniment in jurnal: `submission_sters` / `submission_repus`.
|
|
|
|
Functii cu `conn` (persistenta).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from .mapping import (
|
|
account_or_default,
|
|
account_scope_clause,
|
|
classify_prezentare,
|
|
load_mapping_meta,
|
|
load_nomenclator_codes,
|
|
)
|
|
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).
|
|
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()}
|
|
valid_codes = load_nomenclator_codes(conn) or None
|
|
cl = classify_prezentare(content, mapping, mapping_meta, valid_codes)
|
|
|
|
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"]}
|