#!/usr/bin/env python3 """One-shot recovery: re-populate SQLite `order_items` for orders where the table was wiped (e.g. DELETED_IN_ROA → retry flow, before the retry items fix). Reads settings from SQLite, downloads orders from GoMag API for a ~14-day window around the order date, finds the target order, rebuilds the items rows. Does NOT touch Oracle. Does NOT change order status / id_comanda. Usage (inside the venv, on the prod server): python scripts/backfill_order_items.py 485224762 python scripts/backfill_order_items.py 485224762 485224763 # multiple """ import asyncio import os import sys import tempfile from datetime import datetime, timedelta sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) from api.app.services import sqlite_service, gomag_client, order_reader, validation_service from api.app import database async def _backfill_one(order_number: str, app_settings: dict, use_oracle: bool) -> dict: detail = await sqlite_service.get_order_detail(order_number) if not detail: return {"ok": False, "msg": f"#{order_number}: nu e in SQLite"} order_data = detail["order"] existing_items = len(detail["items"]) order_date_str = order_data.get("order_date") or "" try: order_date = datetime.fromisoformat(order_date_str.replace("Z", "+00:00")).date() except (ValueError, AttributeError): order_date = datetime.now().date() - timedelta(days=1) days_back = max((datetime.now().date() - order_date).days + 2, 2) with tempfile.TemporaryDirectory() as tmp: await gomag_client.download_orders( tmp, days_back=days_back, api_key=app_settings.get("gomag_api_key"), api_shop=app_settings.get("gomag_api_shop"), limit=200, ) orders, _ = order_reader.read_json_orders(json_dir=tmp) target = next((o for o in orders if str(o.number) == str(order_number)), None) if not target: return {"ok": False, "msg": f"#{order_number}: nu e in GoMag (fereastra {days_back}z)"} validation = {"mapped": set(), "direct": set()} if use_oracle: skus = {item.sku for item in target.items if item.sku} id_gestiune = app_settings.get("id_gestiune", "") id_gestiuni = [int(g.strip()) for g in id_gestiune.split(",") if g.strip()] if id_gestiune else None try: validation = await asyncio.to_thread( validation_service.validate_skus, skus, None, id_gestiuni ) except Exception as e: print(f" [WARN] validate_skus a esuat, mapping_status default='direct': {e}") items_data = [ { "sku": item.sku, "product_name": item.name, "quantity": item.quantity, "price": item.price, "baseprice": item.baseprice, "vat": item.vat, "mapping_status": "mapped" if item.sku in validation["mapped"] else "direct", "codmat": None, "id_articol": None, "cantitate_roa": None, } for item in target.items ] await sqlite_service.add_order_items(order_number, items_data) return { "ok": True, "msg": f"#{order_number}: {len(items_data)} items scrise (era {existing_items})", } async def main(order_numbers: list[str]): app_settings = await sqlite_service.get_app_settings() use_oracle = False try: database.init_oracle() use_oracle = True print("Oracle conectat — mapping_status va fi calculat corect.") except Exception as e: print(f"Oracle indisponibil ({e}) — mapping_status default 'direct'.") try: for on in order_numbers: result = await _backfill_one(on, app_settings, use_oracle) tag = "OK " if result["ok"] else "FAIL" print(f"[{tag}] {result['msg']}") finally: if use_oracle: try: database.close_oracle() except Exception: pass if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python scripts/backfill_order_items.py [...]") sys.exit(1) asyncio.run(main(sys.argv[1:]))