feat(telegram): bot bonuri fiscale — OCR → preview → Oracle write

- US-001: mută queue_client.py în data_entry/services/ocr/
- US-002/003/004: oracle_receipt_writer + oracle_server_id în DB
- US-005: receipt_handlers.py (PDF/photo/callback flow)
- US-006: wire handlers în main.py, per-schema connect, seq_cod.nextval
- US-007: .gitignore secrets/*.oracle_pass
- US-008/009/010: teste unit + integration + E2E
- setup-secrets.sh helper + template
- docs/telegram/README.md actualizat cu arhitectura nouă

Testat E2E pe DB live (MARIUSM_AUTO). COD din seq_cod.nextval.
pypdfium2 fallback pentru PDF decode (fără poppler).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 09:26:58 +00:00
parent 8234103884
commit e257fa5d5f
35 changed files with 4531 additions and 227 deletions

View File

@@ -8,10 +8,10 @@ import json
import shutil
import sys
import uuid
import oracledb
from datetime import datetime, timedelta
from pathlib import Path
from decimal import Decimal
from backend.modules.data_entry.services.oracle_receipt_writer import write_receipt
# OCR Queue paths
QUEUE_DIR = Path("/workspace/roa2web/backend/data/ocr_queue")
@@ -25,16 +25,6 @@ ORACLE_CONFIG = {
"dsn": "10.0.20.121:1521/ROA"
}
CUI_TO_CONT = {
"11201891": "6022",
"1590082": "6022",
"14991381": "6022",
"10562600": "6021",
}
def get_cont(cui: str) -> str:
return CUI_TO_CONT.get(cui.upper().replace("RO", "").strip(), "6028")
async def submit_ocr_job(file_path: Path) -> str:
import aiosqlite
job_id = str(uuid.uuid4())
@@ -43,13 +33,14 @@ async def submit_ocr_job(file_path: Path) -> str:
shutil.copy(file_path, dest_path)
mime_type = "application/pdf" if file_path.suffix.lower() == ".pdf" else "image/jpeg"
now = datetime.now()
async with aiosqlite.connect(str(DB_PATH), timeout=5.0) as db:
await db.execute("""
INSERT INTO ocr_jobs (id, status, file_path, mime_type, engine, created_at, original_filename, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (job_id, "pending", str(dest_path), mime_type, "doctr_plus",
datetime.now().isoformat(), file_path.name,
(datetime.now() + timedelta(hours=24)).isoformat()))
now.isoformat(), file_path.name,
(now + timedelta(hours=24)).isoformat()))
await db.commit()
return job_id
@@ -72,71 +63,20 @@ async def wait_for_result(job_id: str, timeout: int = 120) -> dict:
return {"success": False, "error": "Timeout"}
def save_to_oracle(ocr_result: dict, do_commit: bool = False) -> dict:
conn = oracledb.connect(**ORACLE_CONFIG)
cursor = conn.cursor()
try:
# Parse date
date_str = ocr_result.get("receipt_date")
if date_str:
receipt_date = datetime.strptime(date_str[:10], "%Y-%m-%d").date()
else:
receipt_date = datetime.now().date()
an, luna = receipt_date.year, receipt_date.month
# Init
cursor.callproc('PACK_CONTAFIN.INITIALIZEAZA_SCRIERE_ACT_RUL', [0, datetime.now(), an, luna, 0, 0, 0, 0])
# Get COD
cursor.execute("SELECT NVL(MAX(COD), 0) + 1 FROM ACT WHERE AN = :an AND LUNA = :luna", an=an, luna=luna)
cod = cursor.fetchone()[0]
# Partner
cui_clean = (ocr_result.get("cui") or "").upper().replace("RO", "").strip()
cursor.execute("SELECT ID_PART FROM NOM_PARTENERI WHERE COD_FISCAL = :cui OR COD_FISCAL = :cui2",
cui=cui_clean, cui2="RO"+cui_clean)
row = cursor.fetchone()
id_part = row[0] if row else 0
# Amounts
total = float(ocr_result.get("amount") or 0)
tva = float(ocr_result.get("tva_total") or 0)
fara_tva = total - tva
nract = int(ocr_result.get("receipt_number") or 0) if str(ocr_result.get("receipt_number", "")).isdigit() else 0
cont = get_cont(ocr_result.get("cui") or "")
expl = f"OCR: {ocr_result.get('partner_name') or 'N/A'}"
# Insert lines
lines = [
(cont, "401", fara_tva, expl, id_part, 0),
("401", "5311", total, f"Plata {expl}", 0, id_part),
]
if tva > 0:
lines.insert(1, ("4426", "401", tva, f"TVA {expl}", id_part, 0))
for scd, scc, suma, e, id_partc, id_partd in lines:
cursor.execute("""
INSERT INTO ACT_TEMP (LUNA, AN, COD, DATAIREG, DATAACT, NRACT, EXPLICATIA, SCD, SCC, SUMA, ID_PARTC, ID_PARTD, ID_UTIL, DATAORA)
VALUES (:luna, :an, :cod, TRUNC(SYSDATE), :dataact, :nract, :expl, :scd, :scc, :suma, :id_partc, :id_partd, 0, SYSDATE)
""", luna=luna, an=an, cod=cod, dataact=receipt_date, nract=nract, expl=e, scd=scd, scc=scc, suma=suma, id_partc=id_partc, id_partd=id_partd)
# Finalize
mesaj = cursor.var(oracledb.STRING, 4000)
cursor.callproc('PACK_CONTAFIN.FINALIZEAZA_SCRIERE_ACT_RUL', [0, cod, 0, 0, 0, mesaj])
if do_commit:
conn.commit()
return {"success": True, "cod": cod, "luna": luna, "an": an, "saved": True}
else:
conn.rollback()
return {"success": True, "cod": cod, "luna": luna, "an": an, "saved": False}
receipt_date = (
datetime.strptime(date_str[:10], "%Y-%m-%d").date() if date_str else None
)
effective_date = receipt_date or datetime.now().date()
an, luna = effective_date.year, effective_date.month
receipt_dict = {**ocr_result, "receipt_date": receipt_date}
cod, _mesaj = write_receipt(receipt_dict, ORACLE_CONFIG, commit=do_commit)
return {"success": True, "cod": cod, "luna": luna, "an": an, "saved": do_commit}
except Exception as e:
conn.rollback()
return {"success": False, "error": str(e)}
finally:
cursor.close()
conn.close()
async def process_whatsapp_file(file_path: Path, do_save: bool = False):
print(f"📄 Procesez: {file_path.name}")