feat(import): T6 gate auto_send pe coduri nou-mapate (OV-1)
- load_mapping_meta: {cod_op_service -> {cod_prestatie, auto_send}}
- has_no_auto_send: verifica daca vreun item rezolvat via mapping are auto_send=0
- reresolve_account: auto_send=0 -> ramane needs_mapping (review_manual stat),
NU trece pe queued; previne FINALIZATA eronat permanent
- reresolve_account primeste batch_id optional (pregatire T7, urmeaza)
- POST /v1/prezentari: auto_send=0 -> needs_mapping + motiv explicit
- 9 teste: load_mapping_meta, has_no_auto_send, reresolve (zero/unu), POST API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,8 @@ from ...db import get_connection
|
||||
from ...idempotency import build_key, canonicalize_row, idempotency_key
|
||||
from ...mapping import (
|
||||
account_or_default,
|
||||
load_mapping,
|
||||
has_no_auto_send,
|
||||
load_mapping_meta,
|
||||
pending_unmapped,
|
||||
reresolve_account,
|
||||
resolve_prestatii,
|
||||
@@ -60,7 +61,9 @@ def create_prezentari(
|
||||
conn = get_connection()
|
||||
results: list[SubmissionResult] = []
|
||||
try:
|
||||
mapping = load_mapping(conn, acct)
|
||||
# T6/OV-1: load_mapping_meta include auto_send per op (gate pentru coduri noi).
|
||||
mapping_meta = load_mapping_meta(conn, acct)
|
||||
mapping = {op: meta["cod_prestatie"] for op, meta in mapping_meta.items()}
|
||||
for prez in req.prezentari:
|
||||
content = prez.model_dump()
|
||||
# T9/OV-2: canonicalize_row inaintea build_key (odometru strip ".0", VIN upper).
|
||||
@@ -104,6 +107,14 @@ def create_prezentari(
|
||||
errors = validate_prezentare(content)
|
||||
if errors:
|
||||
status, rar_error = "needs_data", json.dumps(errors, ensure_ascii=False)
|
||||
elif has_no_auto_send(resolved, mapping_meta):
|
||||
# T6/OV-1: cod rezolvat cu auto_send=0 -> nu trimite automat.
|
||||
# Randul ramane 'needs_mapping' pana userul confirma manual (sau comuta auto_send=1).
|
||||
status = "needs_mapping"
|
||||
rar_error = json.dumps(
|
||||
{"auto_send": "cod mapat cu auto_send=0; review manual inainte de trimitere"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
else:
|
||||
status, rar_error = "queued", None
|
||||
|
||||
|
||||
@@ -188,6 +188,35 @@ def load_mapping(conn, account_id: int | None) -> dict[str, str]:
|
||||
return {r["cod_op_service"]: r["cod_prestatie"] for r in rows}
|
||||
|
||||
|
||||
def load_mapping_meta(conn, account_id: int | None) -> dict[str, dict]:
|
||||
"""{cod_op_service -> {cod_prestatie, auto_send}} pentru un cont.
|
||||
|
||||
T6/OV-1: varianta extinsa care include si flagul auto_send per operatie.
|
||||
"""
|
||||
acct = account_or_default(account_id)
|
||||
rows = conn.execute(
|
||||
"SELECT cod_op_service, cod_prestatie, auto_send FROM operations_mapping WHERE account_id=?",
|
||||
(acct,),
|
||||
).fetchall()
|
||||
return {
|
||||
r["cod_op_service"]: {"cod_prestatie": r["cod_prestatie"], "auto_send": bool(r["auto_send"])}
|
||||
for r in rows
|
||||
}
|
||||
|
||||
|
||||
def has_no_auto_send(resolved: list[dict], mapping_meta: dict[str, dict]) -> bool:
|
||||
"""Verifica daca vreun item rezolvat via mapping are auto_send=0.
|
||||
|
||||
T6/OV-1: un cod nou-mapat cu auto_send=0 nu trebuie trimis automat.
|
||||
Items cu cod_prestatie direct (nu via cod_op_service) nu sunt afectate.
|
||||
"""
|
||||
for item in resolved:
|
||||
op = (item.get("cod_op_service") or "").strip()
|
||||
if op and op in mapping_meta and not mapping_meta[op]["auto_send"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def pending_unmapped(conn) -> list[dict]:
|
||||
"""Operatii distincte nemapate, agregate din submission-urile `needs_mapping`.
|
||||
|
||||
@@ -250,22 +279,39 @@ def save_mapping(conn, account_id: int | None, cod_op_service: str, cod_prestati
|
||||
)
|
||||
|
||||
|
||||
def reresolve_account(conn, account_id: int | None) -> dict[str, int]:
|
||||
def reresolve_account(conn, account_id: int | None, batch_id: int | None = None) -> dict[str, int]:
|
||||
"""Re-rezolva submission-urile `needs_mapping` ale unui cont dupa o noua mapare.
|
||||
|
||||
Pentru fiecare: aplica maparea curenta; daca nu mai raman op-uri nemapate ->
|
||||
ruleaza validarea de continut (T3) si trece pe `queued` (sau `needs_data` cu
|
||||
motiv), resetand backoff-ul. Daca raman nemapate, ramane `needs_mapping` cu
|
||||
motivul actualizat. Intoarce {requeued, still_blocked, needs_data}.
|
||||
motivul actualizat. Intoarce {requeued, still_blocked, needs_data, review_manual}.
|
||||
|
||||
T6/OV-1: auto_send=0 pe un cod nou-mapat -> nu trece pe 'queued' (ramane
|
||||
'needs_mapping' cu motiv "review manual"); previne FINALIZATA eronat permanent.
|
||||
|
||||
T7: batch_id != None -> scope la seria comitata (NU cross-batch).
|
||||
batch_id is None -> re-rezolva toti (canal API, batch_id IS NULL inclus).
|
||||
"""
|
||||
acct = account_or_default(account_id)
|
||||
mapping = load_mapping(conn, acct)
|
||||
rows = conn.execute(
|
||||
"SELECT id, payload_json FROM submissions WHERE status='needs_mapping' AND account_id=?",
|
||||
(acct,),
|
||||
).fetchall()
|
||||
mapping_meta = load_mapping_meta(conn, acct)
|
||||
mapping = {op: meta["cod_prestatie"] for op, meta in mapping_meta.items()}
|
||||
|
||||
stats = {"requeued": 0, "still_blocked": 0, "needs_data": 0}
|
||||
if batch_id is not None:
|
||||
# T7: scope la batch-ul specificat
|
||||
rows = conn.execute(
|
||||
"SELECT id, payload_json FROM submissions "
|
||||
"WHERE status='needs_mapping' AND account_id=? AND batch_id=?",
|
||||
(acct, batch_id),
|
||||
).fetchall()
|
||||
else:
|
||||
# Canal API (batch_id IS NULL) + legacy (batch_id nesetat)
|
||||
rows = conn.execute(
|
||||
"SELECT id, payload_json FROM submissions WHERE status='needs_mapping' AND account_id=?",
|
||||
(acct,),
|
||||
).fetchall()
|
||||
|
||||
stats = {"requeued": 0, "still_blocked": 0, "needs_data": 0, "review_manual": 0}
|
||||
for r in rows:
|
||||
try:
|
||||
content = json.loads(r["payload_json"])
|
||||
@@ -283,6 +329,19 @@ def reresolve_account(conn, account_id: int | None) -> dict[str, int]:
|
||||
stats["still_blocked"] += 1
|
||||
continue
|
||||
|
||||
# T6/OV-1: verifica auto_send inainte de re-queuing
|
||||
if has_no_auto_send(resolved, mapping_meta):
|
||||
conn.execute(
|
||||
"UPDATE submissions SET payload_json=?, rar_error=?, updated_at=datetime('now') WHERE id=?",
|
||||
(
|
||||
payload_json,
|
||||
json.dumps({"auto_send": "cod mapat cu auto_send=0; review manual inainte de trimitere"}, ensure_ascii=False),
|
||||
r["id"],
|
||||
),
|
||||
)
|
||||
stats["review_manual"] += 1
|
||||
continue
|
||||
|
||||
errors = validate_prezentare(content)
|
||||
if errors:
|
||||
conn.execute(
|
||||
|
||||
Reference in New Issue
Block a user