- Move ROA_API_PASSWORD and ORACLE_PASSWORD to .env - Update process_bon.py to use python-dotenv - chmod 600 on .env and credentials/* - Install python-dotenv dependency
594 lines
22 KiB
Python
594 lines
22 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Procesare bon fiscal: PDF → OCR API → SQLite API → Oracle
|
||
|
||
Usage:
|
||
python process_bon.py <pdf_path> [--save]
|
||
|
||
--save Salvează efectiv în Oracle (altfel dry run)
|
||
|
||
Fluxul:
|
||
1. OCR extract via API (http://10.0.20.171:8000/api/data-entry/ocr/extract)
|
||
2. Save receipt via API (http://10.0.20.171:8000/api/data-entry/receipts/) - TOATE datele
|
||
3. Save to Oracle:
|
||
- Verifică/creează partener
|
||
- Verifică TVA la încasare (CALENDAR.TVA_INCASARE)
|
||
- Generează note contabile corecte
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import json
|
||
import time
|
||
import argparse
|
||
from pathlib import Path
|
||
from datetime import datetime
|
||
from decimal import Decimal
|
||
|
||
import requests
|
||
import oracledb
|
||
from dotenv import load_dotenv
|
||
|
||
# Load .env from parent directory
|
||
load_dotenv(Path(__file__).parent.parent / ".env")
|
||
|
||
# === CONFIG ===
|
||
API_BASE = "http://10.0.20.171:8000"
|
||
API_USER = "MARIUS M"
|
||
API_PASS = os.getenv("ROA_API_PASSWORD", "")
|
||
SERVER_ID = "central"
|
||
COMPANY_ID = 110 # MARIUSM AUTO
|
||
|
||
ORACLE_CONFIG = {
|
||
"user": "MARIUSM_AUTO",
|
||
"password": os.getenv("ORACLE_PASSWORD", ""),
|
||
"dsn": "10.0.20.121:1521/ROA"
|
||
}
|
||
|
||
# Mapare CUI → cont cheltuială
|
||
CUI_TO_CONT = {
|
||
"11201891": "6022", # MOL
|
||
"1590082": "6022", # OMV Petrom
|
||
"14991381": "6022", # Rompetrol
|
||
"10562600": "6021", # Dedeman / Five Holding (Brick)
|
||
"1879865": "6021", # Five Holding
|
||
}
|
||
|
||
# Mapare cotă TVA → (ID_JTVA baza, ID_JTVA tva, TAXCODE, TAXCODE_TVAI)
|
||
# Pentru achiziții interne neexigibile (TVA la încasare)
|
||
JTVA_NEEX = {
|
||
21: (210, 211, 301104, 301305), # ACH. INT. NEEX. 21%
|
||
19: (188, 189, 301101, 301301), # ACH. INT. NEEX. 19%
|
||
11: (214, 215, 301105, 301306), # ACH. INT. NEEX. 11%
|
||
9: (172, 173, 301102, 301302), # ACH. INT. NEEX. 9%
|
||
5: (174, 175, 301103, 301303), # ACH. INT. NEEX. 5%
|
||
}
|
||
|
||
# Pentru achiziții interne normale (fără TVA la încasare)
|
||
JTVA_NORMAL = {
|
||
21: (208, 209, 301104, 301305), # ACH. INT. 21%
|
||
19: (None, None, 301101, 301301),
|
||
9: (None, None, 301102, 301302),
|
||
}
|
||
|
||
|
||
def get_cont(cui: str) -> str:
|
||
"""Mapare CUI → cont cheltuială."""
|
||
cui_clean = (cui or "").upper().replace("RO", "").strip()
|
||
return CUI_TO_CONT.get(cui_clean, "6028") # 6028 = alte cheltuieli
|
||
|
||
|
||
class APIClient:
|
||
"""Client pentru roa2web API."""
|
||
|
||
def __init__(self, base_url: str):
|
||
self.base_url = base_url.rstrip("/")
|
||
self.token = None
|
||
self.session = requests.Session()
|
||
|
||
def login(self, username: str, password: str, server_id: str) -> bool:
|
||
"""Login și obține token."""
|
||
r = self.session.post(
|
||
f"{self.base_url}/api/auth/login",
|
||
json={"username": username, "password": password, "server_id": server_id}
|
||
)
|
||
if r.status_code == 200:
|
||
data = r.json()
|
||
self.token = data.get("access_token")
|
||
self.session.headers["Authorization"] = f"Bearer {self.token}"
|
||
return True
|
||
print(f"Login failed: {r.status_code} - {r.text}")
|
||
return False
|
||
|
||
def ocr_extract(self, file_path: Path) -> dict:
|
||
"""Submit OCR job și așteaptă rezultatul."""
|
||
# Determine mime type
|
||
suffix = file_path.suffix.lower()
|
||
if suffix == ".pdf":
|
||
mime_type = "application/pdf"
|
||
elif suffix in (".jpg", ".jpeg"):
|
||
mime_type = "image/jpeg"
|
||
elif suffix == ".png":
|
||
mime_type = "image/png"
|
||
else:
|
||
# Try to detect from content
|
||
with open(file_path, "rb") as f:
|
||
header = f.read(8)
|
||
if header[:4] == b'%PDF':
|
||
mime_type = "application/pdf"
|
||
suffix = ".pdf"
|
||
elif header[:3] == b'\xff\xd8\xff':
|
||
mime_type = "image/jpeg"
|
||
suffix = ".jpg"
|
||
elif header[:8] == b'\x89PNG\r\n\x1a\n':
|
||
mime_type = "image/png"
|
||
suffix = ".png"
|
||
else:
|
||
mime_type = "application/pdf" # default
|
||
suffix = ".pdf"
|
||
|
||
# Use proper filename with extension
|
||
filename = file_path.stem + suffix if not file_path.suffix else file_path.name
|
||
|
||
# Submit
|
||
with open(file_path, "rb") as f:
|
||
r = self.session.post(
|
||
f"{self.base_url}/api/data-entry/ocr/extract",
|
||
files={"file": (filename, f, mime_type)}
|
||
)
|
||
if r.status_code != 200:
|
||
return {"success": False, "error": f"OCR submit failed: {r.text}"}
|
||
|
||
job_id = r.json().get("job_id")
|
||
print(f" OCR job: {job_id}")
|
||
|
||
# Wait for result (max 60s per request, retry if pending)
|
||
for _ in range(4): # Max 4 retries = ~240s total
|
||
r = self.session.get(
|
||
f"{self.base_url}/api/data-entry/ocr/jobs/{job_id}/wait",
|
||
params={"timeout": 60, "wait_for_terminal": "true"},
|
||
timeout=70
|
||
)
|
||
if r.status_code != 200:
|
||
return {"success": False, "error": f"OCR wait failed: {r.text}"}
|
||
|
||
data = r.json()
|
||
status = data.get("status")
|
||
|
||
if status == "completed":
|
||
return {"success": True, "result": data.get("result"), "time_ms": data.get("processing_time_ms")}
|
||
elif status == "failed":
|
||
return {"success": False, "error": data.get("error") or "OCR failed"}
|
||
# Still pending/processing - will retry
|
||
|
||
return {"success": False, "error": "OCR timeout"}
|
||
|
||
def create_receipt(self, ocr_result: dict, company_id: int) -> dict:
|
||
"""Creează receipt în SQLite via API cu TOATE datele."""
|
||
# Parse date
|
||
date_str = ocr_result.get("receipt_date")
|
||
if date_str:
|
||
receipt_date = date_str[:10] # YYYY-MM-DD
|
||
else:
|
||
receipt_date = datetime.now().strftime("%Y-%m-%d")
|
||
|
||
# Build TVA breakdown from OCR
|
||
tva_breakdown = []
|
||
for tva_entry in (ocr_result.get("tva_entries") or []):
|
||
tva_breakdown.append({
|
||
"code": tva_entry.get("code"),
|
||
"percent": tva_entry.get("percent"),
|
||
"amount": float(tva_entry.get("amount") or 0)
|
||
})
|
||
|
||
# Build payment methods from OCR
|
||
payment_methods = []
|
||
for pm in (ocr_result.get("payment_methods") or []):
|
||
payment_methods.append({
|
||
"method": pm.get("method"),
|
||
"amount": float(pm.get("amount") or 0)
|
||
})
|
||
|
||
# Determine payment mode
|
||
payment_mode = ocr_result.get("suggested_payment_mode") or "casa"
|
||
# If has CARD payment, it's "banca"
|
||
if any(pm.get("method", "").upper() == "CARD" for pm in payment_methods):
|
||
payment_mode = "banca"
|
||
elif any(pm.get("method", "").upper() == "NUMERAR" for pm in payment_methods):
|
||
payment_mode = "casa"
|
||
|
||
payload = {
|
||
"receipt_type": "bon_fiscal",
|
||
"direction": "cheltuiala",
|
||
"receipt_number": ocr_result.get("receipt_number"),
|
||
"receipt_series": ocr_result.get("receipt_series"),
|
||
"receipt_date": receipt_date,
|
||
"amount": float(ocr_result.get("amount") or 0),
|
||
"partner_name": ocr_result.get("partner_name"),
|
||
"cui": ocr_result.get("cui"),
|
||
"tva_total": float(ocr_result.get("tva_total") or 0),
|
||
"tva_breakdown": tva_breakdown if tva_breakdown else None,
|
||
"payment_methods": payment_methods if payment_methods else None,
|
||
"payment_mode": payment_mode,
|
||
"company_id": company_id,
|
||
"vendor_address": ocr_result.get("address"),
|
||
"items_count": ocr_result.get("items_count"),
|
||
"ocr_raw_text": ocr_result.get("raw_text"),
|
||
}
|
||
|
||
# Remove None values
|
||
payload = {k: v for k, v in payload.items() if v is not None}
|
||
|
||
self.session.headers["X-Selected-Company"] = str(company_id)
|
||
r = self.session.post(
|
||
f"{self.base_url}/api/data-entry/receipts/",
|
||
json=payload
|
||
)
|
||
|
||
if r.status_code in (200, 201):
|
||
return {"success": True, "receipt": r.json()}
|
||
else:
|
||
return {"success": False, "error": f"Create receipt failed: {r.text}"}
|
||
|
||
|
||
def get_or_create_partner(cursor, cui: str, name: str, address: str = None) -> int:
|
||
"""Găsește sau creează partener în Oracle. Returnează ID_PART."""
|
||
cui_clean = (cui or "").upper().replace("RO", "").strip()
|
||
|
||
if not cui_clean:
|
||
return 0 # No CUI, no partner
|
||
|
||
# Try to find existing partner
|
||
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()
|
||
|
||
if row:
|
||
return row[0] # Found existing partner
|
||
|
||
# Create new partner
|
||
cursor.execute("SELECT SEQ_NOM_PARTENERI.NEXTVAL FROM DUAL")
|
||
new_id = cursor.fetchone()[0]
|
||
|
||
# Clean name
|
||
partner_name = (name or f"PARTENER {cui_clean}")[:100]
|
||
partner_address = (address or "")[:200]
|
||
|
||
cursor.execute("""
|
||
INSERT INTO NOM_PARTENERI (ID_PART, NUME, COD_FISCAL, ADRESA, STERS, INACTIV)
|
||
VALUES (:id_part, :nume, :cui, :adresa, 0, 0)
|
||
""", id_part=new_id, nume=partner_name, cui=cui_clean, adresa=partner_address)
|
||
|
||
print(f" ➕ Partener nou creat: ID={new_id}, CUI={cui_clean}, Nume={partner_name}")
|
||
return new_id
|
||
|
||
|
||
def check_tva_incasare(cursor, an: int, luna: int) -> bool:
|
||
"""Verifică dacă firma e plătitoare de TVA la încasare în perioada dată."""
|
||
cursor.execute("""
|
||
SELECT NVL(TVA_INCASARE, 0) FROM CALENDAR
|
||
WHERE AN = :an AND LUNA = :luna
|
||
""", an=an, luna=luna)
|
||
row = cursor.fetchone()
|
||
return row[0] == 1 if row else False
|
||
|
||
|
||
def save_to_oracle(ocr_result: dict, do_commit: bool = False) -> dict:
|
||
"""Salvează nota contabilă în Oracle cu toate regulile."""
|
||
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
|
||
|
||
# 1. Get or create partner
|
||
id_part = get_or_create_partner(
|
||
cursor,
|
||
ocr_result.get("cui"),
|
||
ocr_result.get("partner_name"),
|
||
ocr_result.get("address")
|
||
)
|
||
print(f" Partner ID: {id_part}")
|
||
|
||
# 2. Check TVA la încasare
|
||
tva_incasare = check_tva_incasare(cursor, an, luna)
|
||
cont_tva = "4428" if tva_incasare else "4426"
|
||
print(f" TVA la încasare: {'DA (4428)' if tva_incasare else 'NU (4426)'}")
|
||
|
||
# 3. Determine payment type
|
||
payment_methods = ocr_result.get("payment_methods") or []
|
||
has_cash = any(pm.get("method", "").upper() == "NUMERAR" for pm in payment_methods)
|
||
has_card = any(pm.get("method", "").upper() == "CARD" for pm in payment_methods)
|
||
|
||
# If no payment info, assume cash
|
||
if not payment_methods:
|
||
has_cash = True
|
||
|
||
print(f" Plată: {'NUMERAR' if has_cash else ''}{' + ' if has_cash and has_card else ''}{'CARD' if has_card else ''}")
|
||
|
||
# 4. Init PACK_CONTAFIN
|
||
cursor.callproc('PACK_CONTAFIN.INITIALIZEAZA_SCRIERE_ACT_RUL',
|
||
[0, datetime.now(), an, luna, 0, 0, 0, 0])
|
||
|
||
# 5. Get next 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]
|
||
|
||
# 6. Calculate amounts
|
||
total = float(ocr_result.get("amount") or 0)
|
||
tva = float(ocr_result.get("tva_total") or 0)
|
||
fara_tva = total - tva
|
||
|
||
nract = ocr_result.get("receipt_number", "")
|
||
nract = int(nract) if str(nract).isdigit() else 0
|
||
|
||
cont_cheltuiala = get_cont(ocr_result.get("cui") or "")
|
||
expl = f"OCR: {ocr_result.get('partner_name') or 'N/A'}"[:100]
|
||
|
||
print(f" COD: {cod}, Cont: {cont_cheltuiala}")
|
||
print(f" Total: {total}, Bază: {fara_tva}, TVA: {tva}")
|
||
|
||
# 7. Process TVA entries from OCR (pot fi mai multe cote TVA)
|
||
tva_entries = ocr_result.get("tva_entries") or []
|
||
|
||
# 8. Build accounting lines
|
||
lines = []
|
||
|
||
# Calculate base for each TVA rate
|
||
if tva_entries:
|
||
# Process each TVA entry separately
|
||
for tva_entry in tva_entries:
|
||
tva_rate = tva_entry.get("percent") or 21
|
||
tva_amount = float(tva_entry.get("amount") or 0)
|
||
|
||
if tva_amount <= 0:
|
||
continue
|
||
|
||
# Calculate base for this TVA rate
|
||
base_amount = tva_amount / (tva_rate / 100)
|
||
|
||
# Get ID_JTVA_COLOANA and TAXCODE based on TVA rate and TVA la încasare
|
||
if tva_incasare:
|
||
jtva_data = JTVA_NEEX.get(tva_rate, (210, 211, 301104, 301305))
|
||
else:
|
||
jtva_data = JTVA_NORMAL.get(tva_rate, (208, 209, 301104, 301305))
|
||
|
||
jtva_baza, jtva_tva, taxcode_normal, taxcode_tvai = jtva_data
|
||
taxcode = taxcode_tvai if tva_incasare else taxcode_normal
|
||
|
||
print(f" TVA {tva_rate}%: baza={base_amount:.2f}, tva={tva_amount:.2f}, JTVA=({jtva_baza},{jtva_tva}), TAXCODE={taxcode}")
|
||
|
||
# Linia cheltuială pentru această cotă
|
||
lines.append({
|
||
"scd": cont_cheltuiala, "scc": "401",
|
||
"suma": base_amount, "expl": expl,
|
||
"id_partc": id_part, "id_partd": 0,
|
||
"id_jtva": jtva_baza,
|
||
"taxcode": taxcode
|
||
})
|
||
|
||
# Linia TVA pentru această cotă
|
||
proc_tva = 1 + tva_rate / 100 # 1.21, 1.19, etc.
|
||
lines.append({
|
||
"scd": cont_tva, "scc": "401",
|
||
"suma": tva_amount, "expl": f"TVA {tva_rate}% {expl}"[:100],
|
||
"id_partc": id_part, "id_partd": 0,
|
||
"proc_tva": proc_tva,
|
||
"id_jtva": jtva_tva,
|
||
"taxcode": taxcode
|
||
})
|
||
else:
|
||
# Fallback: use total amounts if no tva_entries
|
||
if fara_tva > 0:
|
||
tva_rate = round(tva / fara_tva * 100) if fara_tva > 0 else 21
|
||
else:
|
||
tva_rate = 21
|
||
|
||
if tva_incasare:
|
||
jtva_data = JTVA_NEEX.get(tva_rate, (210, 211, 301104, 301305))
|
||
else:
|
||
jtva_data = JTVA_NORMAL.get(tva_rate, (208, 209, 301104, 301305))
|
||
|
||
jtva_baza, jtva_tva, taxcode_normal, taxcode_tvai = jtva_data
|
||
taxcode = taxcode_tvai if tva_incasare else taxcode_normal
|
||
|
||
print(f" TVA {tva_rate}% (estimat): JTVA=({jtva_baza},{jtva_tva}), TAXCODE={taxcode}")
|
||
|
||
lines.append({
|
||
"scd": cont_cheltuiala, "scc": "401",
|
||
"suma": fara_tva, "expl": expl,
|
||
"id_partc": id_part, "id_partd": 0,
|
||
"id_jtva": jtva_baza,
|
||
"taxcode": taxcode
|
||
})
|
||
|
||
if tva > 0:
|
||
proc_tva = 1 + tva_rate / 100
|
||
lines.append({
|
||
"scd": cont_tva, "scc": "401",
|
||
"suma": tva, "expl": f"TVA {tva_rate}% {expl}"[:100],
|
||
"id_partc": id_part, "id_partd": 0,
|
||
"proc_tva": proc_tva,
|
||
"id_jtva": jtva_tva,
|
||
"taxcode": taxcode
|
||
})
|
||
|
||
# Linia plată din casă (DOAR dacă plată numerar)
|
||
if has_cash and not has_card:
|
||
lines.append({
|
||
"scd": "401", "scc": "5311",
|
||
"suma": total, "expl": f"Plata {expl}"[:100],
|
||
"id_partc": 0, "id_partd": id_part,
|
||
"id_jtva": None, # Nu are JTVA pentru plată
|
||
"taxcode": None
|
||
})
|
||
# Dacă plată CARD - nu se face nota 401=5311 (se face la extras bancar)
|
||
|
||
# ID_FDOC = 17 pentru BON FISCAL
|
||
id_fdoc = 17
|
||
|
||
# 9. Insert lines
|
||
for line in lines:
|
||
proc_tva = line.get("proc_tva") or 0 # Default 0 for non-TVA lines
|
||
id_jtva = line.get("id_jtva") # Poate fi None pentru plăți
|
||
taxcode = line.get("taxcode") # Poate fi None pentru plăți
|
||
cursor.execute("""
|
||
INSERT INTO ACT_TEMP (
|
||
LUNA, AN, COD, DATAIREG, DATAACT, NRACT,
|
||
EXPLICATIA, SCD, SCC, SUMA, PROC_TVA,
|
||
ID_PARTC, ID_PARTD, ID_FDOC, ID_JTVA_COLOANA, TAXCODE, ID_UTIL, DATAORA
|
||
) VALUES (
|
||
:luna, :an, :cod, TRUNC(SYSDATE), :dataact, :nract,
|
||
:expl, :scd, :scc, :suma, :proc_tva,
|
||
:id_partc, :id_partd, :id_fdoc, :id_jtva, :taxcode, 0, SYSDATE
|
||
)
|
||
""",
|
||
luna=luna, an=an, cod=cod, dataact=receipt_date, nract=nract,
|
||
expl=line["expl"], scd=line["scd"], scc=line["scc"],
|
||
suma=line["suma"], proc_tva=proc_tva,
|
||
id_partc=line["id_partc"], id_partd=line["id_partd"],
|
||
id_fdoc=id_fdoc, id_jtva=id_jtva, taxcode=taxcode
|
||
)
|
||
jtva_info = f" [JTVA={id_jtva}]" if id_jtva else ""
|
||
taxcode_info = f" [TAX={taxcode}]" if taxcode else ""
|
||
print(f" {line['scd']} = {line['scc']}: {line['suma']:.2f}{jtva_info}{taxcode_info}")
|
||
|
||
# 9. 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,
|
||
"id_part": id_part, "tva_incasare": tva_incasare}
|
||
else:
|
||
conn.rollback()
|
||
return {"success": True, "cod": cod, "luna": luna, "an": an, "saved": False,
|
||
"id_part": id_part, "tva_incasare": tva_incasare}
|
||
|
||
except Exception as e:
|
||
conn.rollback()
|
||
import traceback
|
||
return {"success": False, "error": str(e), "traceback": traceback.format_exc()}
|
||
finally:
|
||
cursor.close()
|
||
conn.close()
|
||
|
||
|
||
def process_bon(file_path: Path, do_save: bool = False, company_id: int = COMPANY_ID,
|
||
api_user: str = API_USER, api_pass: str = API_PASS):
|
||
"""Procesează un bon fiscal: OCR → SQLite → Oracle."""
|
||
print("=" * 60)
|
||
print(f"📄 Procesez: {file_path.name}")
|
||
print("=" * 60)
|
||
|
||
# 1. Login
|
||
print("\n🔑 Login API...")
|
||
client = APIClient(API_BASE)
|
||
if not client.login(api_user, api_pass, SERVER_ID):
|
||
print("❌ Login failed!")
|
||
return None
|
||
print(" ✅ OK")
|
||
|
||
# 2. OCR
|
||
print("\n🔍 OCR extract...")
|
||
ocr_result = client.ocr_extract(file_path)
|
||
if not ocr_result["success"]:
|
||
print(f" ❌ {ocr_result['error']}")
|
||
return None
|
||
|
||
ocr = ocr_result["result"]
|
||
print(f" ✅ OK ({ocr_result.get('time_ms', '?')}ms)")
|
||
print(f" CUI: {ocr.get('cui')}")
|
||
print(f" Partner: {ocr.get('partner_name')}")
|
||
print(f" Data: {ocr.get('receipt_date')}")
|
||
print(f" Total: {ocr.get('amount')} RON")
|
||
print(f" TVA: {ocr.get('tva_total')} RON")
|
||
|
||
# Show payment methods
|
||
payment_methods = ocr.get("payment_methods") or []
|
||
if payment_methods:
|
||
pm_str = ", ".join(f"{pm.get('method')}: {pm.get('amount')}" for pm in payment_methods)
|
||
print(f" Plăți: {pm_str}")
|
||
|
||
# Show TVA breakdown
|
||
tva_entries = ocr.get("tva_entries") or []
|
||
if tva_entries:
|
||
tva_str = ", ".join(f"{t.get('code')}({t.get('percent')}%): {t.get('amount')}" for t in tva_entries)
|
||
print(f" TVA detaliat: {tva_str}")
|
||
|
||
# 3. SQLite (via API) - cu TOATE datele
|
||
print("\n💾 Save SQLite (via API)...")
|
||
sqlite_result = client.create_receipt(ocr, company_id)
|
||
if not sqlite_result["success"]:
|
||
print(f" ❌ {sqlite_result['error']}")
|
||
return None
|
||
|
||
receipt = sqlite_result["receipt"]
|
||
print(f" ✅ Receipt ID: {receipt.get('id')}")
|
||
print(f" Payment mode: {receipt.get('payment_mode')}")
|
||
|
||
# 4. Oracle (direct)
|
||
mode = "SAVE" if do_save else "DRY RUN"
|
||
print(f"\n🗄️ Save Oracle ({mode})...")
|
||
oracle_result = save_to_oracle(ocr, do_commit=do_save)
|
||
|
||
if oracle_result["success"]:
|
||
if oracle_result["saved"]:
|
||
print(f" ✅ SALVAT: COD={oracle_result['cod']}, {oracle_result['luna']:02d}/{oracle_result['an']}")
|
||
else:
|
||
print(f" ⚠️ DRY RUN: ar fi COD={oracle_result['cod']}")
|
||
else:
|
||
print(f" ❌ {oracle_result.get('error')}")
|
||
if oracle_result.get("traceback"):
|
||
print(oracle_result["traceback"])
|
||
|
||
print("\n" + "=" * 60)
|
||
return {
|
||
"ocr": ocr,
|
||
"sqlite_receipt_id": receipt.get("id"),
|
||
"oracle": oracle_result
|
||
}
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="Procesare bon fiscal: OCR → SQLite → Oracle")
|
||
parser.add_argument("file", help="Path către PDF sau imagine")
|
||
parser.add_argument("--save", action="store_true", help="Salvează efectiv în Oracle")
|
||
parser.add_argument("--company", type=int, default=COMPANY_ID, help="Company ID")
|
||
parser.add_argument("--user", default=API_USER, help="API username")
|
||
parser.add_argument("--password", default=API_PASS, help="API password")
|
||
|
||
args = parser.parse_args()
|
||
|
||
file_path = Path(args.file)
|
||
if not file_path.exists():
|
||
print(f"❌ File not found: {file_path}")
|
||
sys.exit(1)
|
||
|
||
result = process_bon(file_path, do_save=args.save, company_id=args.company,
|
||
api_user=args.user, api_pass=args.password)
|
||
|
||
if result:
|
||
print("\n✅ Done!")
|
||
else:
|
||
print("\n❌ Failed!")
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|