feat(address): ROA address cache refresh — 8-field format + manual refresh endpoint
Phase 5 address format upgrade (pre-existing working tree changes):
- import_service: extend vadrese_parteneri query to 8 fields (strada/numar/bloc/scara/apart/etaj/localitate/judet); strip trailing city name from address string passed to Oracle
- sync_service: extend _addr_match to compare bloc/scara/apart in addition to strada/numar
- 05_pack_import_parteneri.pck: updated PL/SQL package
New: address cache refresh mechanism:
- sqlite_service: add get_order_address_ids(), update_order_address_cache() (targeted 3-column update, no ANAF fields touched), get_orders_with_address_ids()
- sync.py: POST /api/orders/{order_number}/refresh-address endpoint (404/422/503/200); batch Oracle address refresh in refresh_invoices (single IN roundtrip, per-order mismatch recomputed)
- UI: refresh button (⟳) in ADRESE modal header (base.html); refreshOrderAddress() with loading state + toast (dashboard.js v43); window._detailOrderNumber global (shared.js v32)
- tests: TestRefreshOrderAddress — 4 tests (404, 422, 503, 200 with 8-field assert)
Oracle prod fix applied directly: ADRESE_PARTENERI id_adresa=4116 STRADA VASILE→VASILE GOLDIS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from fastapi import APIRouter, Request, BackgroundTasks
|
||||
from fastapi import APIRouter, HTTPException, Request, BackgroundTasks
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.responses import HTMLResponse
|
||||
from pydantic import BaseModel
|
||||
@@ -815,6 +815,55 @@ async def refresh_invoices():
|
||||
await sqlite_service.mark_order_deleted_in_roa(o["order_number"])
|
||||
orders_deleted += 1
|
||||
|
||||
# Cherry-pick A: Batch refresh Oracle addresses for all orders with stored address IDs
|
||||
addr_rows = await sqlite_service.get_orders_with_address_ids()
|
||||
if addr_rows:
|
||||
def _fetch_addresses(rows):
|
||||
unique_ids = list(
|
||||
{r["id_adresa_livrare"] for r in rows if r.get("id_adresa_livrare")}
|
||||
| {r["id_adresa_facturare"] for r in rows if r.get("id_adresa_facturare")}
|
||||
)
|
||||
conn = database.get_oracle_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
placeholders = ",".join([f":{i}" for i in range(len(unique_ids))])
|
||||
cur.execute(
|
||||
f"SELECT id_adresa, strada, numar, bloc, scara, apart, etaj, localitate, judet"
|
||||
f" FROM vadrese_parteneri WHERE id_adresa IN ({placeholders})",
|
||||
unique_ids,
|
||||
)
|
||||
return {row[0]: row for row in cur.fetchall()}
|
||||
finally:
|
||||
database.pool.release(conn)
|
||||
|
||||
try:
|
||||
addr_map = await asyncio.to_thread(_fetch_addresses, addr_rows)
|
||||
|
||||
def _row_to_dict(r):
|
||||
return {"strada": r[1], "numar": r[2], "bloc": r[3], "scara": r[4],
|
||||
"apart": r[5], "etaj": r[6], "localitate": r[7], "judet": r[8]}
|
||||
|
||||
addresses_refreshed = 0
|
||||
for row in addr_rows:
|
||||
livr_id = row.get("id_adresa_livrare")
|
||||
fact_id = row.get("id_adresa_facturare")
|
||||
livr_raw = addr_map.get(livr_id)
|
||||
fact_raw = addr_map.get(fact_id) if fact_id and fact_id != livr_id else livr_raw
|
||||
if not livr_raw:
|
||||
continue
|
||||
livr_roa = _row_to_dict(livr_raw)
|
||||
fact_roa = _row_to_dict(fact_raw) if fact_raw else livr_roa
|
||||
mismatch = not sync_service._addr_match(
|
||||
row.get("adresa_livrare_gomag"), json.dumps(livr_roa)
|
||||
)
|
||||
await sqlite_service.update_order_address_cache(
|
||||
row["order_number"], livr_roa, fact_roa, mismatch
|
||||
)
|
||||
addresses_refreshed += 1
|
||||
logger.info(f"refresh_invoices: refreshed {addresses_refreshed} order addresses from Oracle")
|
||||
except Exception as addr_err:
|
||||
logger.warning(f"refresh_invoices: address batch refresh failed: {addr_err}")
|
||||
|
||||
checked = len(uninvoiced) + len(invoiced) + len(all_imported)
|
||||
return {
|
||||
"checked": checked,
|
||||
@@ -826,6 +875,63 @@ async def refresh_invoices():
|
||||
return {"error": str(e), "invoices_added": 0}
|
||||
|
||||
|
||||
@router.post("/api/orders/{order_number}/refresh-address")
|
||||
async def refresh_order_address(order_number: str):
|
||||
"""Re-fetch ROA address from Oracle for an existing order and update SQLite cache."""
|
||||
row = await sqlite_service.get_order_address_ids(order_number)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Order not found")
|
||||
|
||||
id_livr = row.get("id_adresa_livrare")
|
||||
id_fact = row.get("id_adresa_facturare")
|
||||
|
||||
if not id_livr and not id_fact:
|
||||
raise HTTPException(status_code=422, detail="Order has no Oracle address IDs")
|
||||
|
||||
def _fetch():
|
||||
conn = database.get_oracle_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
def fetch_one(id_adresa):
|
||||
if not id_adresa:
|
||||
return None
|
||||
cur.execute(
|
||||
"SELECT strada, numar, bloc, scara, apart, etaj, localitate, judet"
|
||||
" FROM vadrese_parteneri WHERE id_adresa = :1",
|
||||
[id_adresa],
|
||||
)
|
||||
r = cur.fetchone()
|
||||
if not r:
|
||||
return None
|
||||
return {"strada": r[0], "numar": r[1], "bloc": r[2], "scara": r[3],
|
||||
"apart": r[4], "etaj": r[5], "localitate": r[6], "judet": r[7]}
|
||||
|
||||
livr = fetch_one(id_livr)
|
||||
fact = fetch_one(id_fact) if id_fact and id_fact != id_livr else livr
|
||||
return livr, fact
|
||||
finally:
|
||||
database.pool.release(conn)
|
||||
|
||||
try:
|
||||
livr_roa, fact_roa = await asyncio.to_thread(_fetch)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=503, detail=f"Oracle unavailable: {e}")
|
||||
|
||||
old_livr = row.get("adresa_livrare_roa")
|
||||
mismatch = not sync_service._addr_match(
|
||||
row.get("adresa_livrare_gomag"), json.dumps(livr_roa)
|
||||
) if livr_roa else True
|
||||
|
||||
if livr_roa:
|
||||
old_strada = json.loads(old_livr or "{}").get("strada", "?")
|
||||
logger.info(
|
||||
f"refresh_address: {order_number} strada {old_strada!r}→{livr_roa['strada']!r} mismatch→{mismatch}"
|
||||
)
|
||||
|
||||
await sqlite_service.update_order_address_cache(order_number, livr_roa, fact_roa, mismatch)
|
||||
return {"adresa_livrare_roa": livr_roa, "adresa_facturare_roa": fact_roa, "address_mismatch": mismatch}
|
||||
|
||||
|
||||
@router.put("/api/sync/schedule")
|
||||
async def update_schedule(config: ScheduleConfig):
|
||||
"""Update scheduler configuration."""
|
||||
|
||||
Reference in New Issue
Block a user