test(address): oracle integration tests + verify script for PJ/PF rule

- test_address_rules_oracle.py: E2E tests import synthetic PJ+PF orders
  and verify id_facturare/id_livrare in Oracle; regression tests check
  SQLite orders imported after fix date
- verify_address_rules.py: standalone script to audit PJ/PF address
  compliance on existing SQLite orders (--days N / --all / --status)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-08 16:36:30 +00:00
parent 07df807719
commit b2f1687920
2 changed files with 517 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
#!/usr/bin/env python3
"""
Verifică regula adrese PJ/PF pe comenzile importate din SQLite.
Logica:
PF (cod_fiscal_gomag IS NULL): id_adresa_facturare = id_adresa_livrare
PJ (cod_fiscal_gomag IS NOT NULL): adresa_facturare_roa se potriveste cu GoMag billing
(nu cu GoMag shipping)
Rulare:
python3 scripts/verify_address_rules.py
python3 scripts/verify_address_rules.py --days 7 # ultimele 7 zile
python3 scripts/verify_address_rules.py --all # toate comenzile
python3 scripts/verify_address_rules.py --status IMPORTED
"""
import argparse
import json
import os
import sqlite3
import sys
from pathlib import Path
# Add api/ to path for app imports
_repo_root = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(_repo_root / "api"))
from dotenv import load_dotenv
load_dotenv(_repo_root / "api" / ".env")
from app.services.sync_service import _addr_match
def main():
parser = argparse.ArgumentParser(description="Verifică regula adrese PJ/PF în SQLite")
parser.add_argument("--days", type=int, default=30,
help="Număr de zile în urmă (default: 30)")
parser.add_argument("--all", action="store_true",
help="Toate comenzile, indiferent de dată")
parser.add_argument("--status", default=None,
help="Filtrează după status (ex: IMPORTED)")
args = parser.parse_args()
_raw_path = os.environ.get("SQLITE_DB_PATH", "data/import.db")
db_path = _raw_path if os.path.isabs(_raw_path) else str(_repo_root / "api" / _raw_path)
if not Path(db_path).exists():
print(f"EROARE: SQLite DB nu există: {db_path}")
sys.exit(1)
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
# Build query
where_clauses = ["id_adresa_facturare IS NOT NULL", "id_adresa_livrare IS NOT NULL"]
params = []
if not args.all:
where_clauses.append("first_seen_at >= datetime('now', ?)")
params.append(f"-{args.days} days")
if args.status:
where_clauses.append("status = ?")
params.append(args.status)
where_sql = " AND ".join(where_clauses)
rows = conn.execute(f"""
SELECT order_number, status, cod_fiscal_gomag,
id_adresa_facturare, id_adresa_livrare,
adresa_facturare_gomag, adresa_livrare_gomag,
adresa_facturare_roa, adresa_livrare_roa,
first_seen_at
FROM orders
WHERE {where_sql}
ORDER BY first_seen_at DESC
""", params).fetchall()
conn.close()
if not rows:
scope = "toate comenzile" if args.all else f"ultimele {args.days} zile"
print(f"Nicio comandă cu adrese populate ({scope}).")
sys.exit(0)
pf_ok = pf_err = pj_ok = pj_err = pj_skip = 0
violations = []
for r in rows:
is_pj = bool(r["cod_fiscal_gomag"])
id_fact = r["id_adresa_facturare"]
id_livr = r["id_adresa_livrare"]
order = r["order_number"]
date = (r["first_seen_at"] or "")[:10]
if not is_pj:
# PF: id_facturare trebuie = id_livrare
if id_fact == id_livr:
pf_ok += 1
else:
pf_err += 1
violations.append({
"order": order, "date": date, "type": "PF",
"issue": f"id_fact={id_fact} != id_livr={id_livr}",
"detail": None,
})
else:
# PJ: adresa_facturare_roa trebuie sa se potriveasca cu GoMag billing
fact_roa = r["adresa_facturare_roa"]
fact_gomag = r["adresa_facturare_gomag"]
livr_gomag = r["adresa_livrare_gomag"]
if not fact_roa or not fact_gomag:
pj_skip += 1
continue
# Check 1: billing ROA matches GoMag billing
billing_match = _addr_match(fact_gomag, fact_roa)
# Check 2: billing ROA does NOT match GoMag shipping (wrong old behavior)
shipping_match = _addr_match(livr_gomag, fact_roa) if livr_gomag else False
if billing_match:
pj_ok += 1
else:
pj_err += 1
detail = "billing_ROA matches shipping GoMag" if shipping_match else "billing_ROA mismatch"
violations.append({
"order": order, "date": date, "type": "PJ",
"issue": detail,
"detail": f"billing_gomag={_short(fact_gomag)} | fact_roa={fact_roa}",
})
# Output
total = len(rows)
print(f"\n{'='*60}")
scope = "toate" if args.all else f"ultimele {args.days} zile"
print(f" Verificare adrese PJ/PF ({scope}, {total} comenzi cu adrese)")
print(f"{'='*60}")
print(f" PF (fara CUI): {pf_ok:4d} OK | {pf_err:4d} ERORI")
print(f" PJ (cu CUI): {pj_ok:4d} OK | {pj_err:4d} ERORI | {pj_skip:4d} skip (date lipsa)")
print(f"{'='*60}")
if not violations:
print(" ✓ Toate comenzile respecta regula PJ/PF.\n")
else:
print(f"\n VIOLARI ({len(violations)}):\n")
for v in violations[:20]:
print(f" [{v['date']}] {v['order']:25s} {v['type']} {v['issue']}")
if v["detail"]:
print(f" {v['detail']}")
if len(violations) > 20:
print(f" ... si inca {len(violations)-20} violari.")
print()
sys.exit(1 if violations else 0)
def _short(json_str):
"""Returnează un rezumat scurt al unui JSON de adresă."""
if not json_str:
return "(null)"
try:
d = json.loads(json_str)
return f"{d.get('address','?')}, {d.get('city','?')}"
except Exception:
return json_str[:40]
if __name__ == "__main__":
main()