"""Retry service — re-import individual failed/skipped orders.""" import asyncio import logging import tempfile from datetime import datetime, timedelta logger = logging.getLogger(__name__) async def _download_and_reimport(order_number: str, order_date_str: str, customer_name: str, app_settings: dict) -> dict: """Download order from GoMag and re-import it into Oracle. Does NOT check status guard — caller is responsible. Returns: {"success": bool, "message": str, "status": str|None} """ from . import sqlite_service, gomag_client, import_service, order_reader # Parse order date for narrow download window try: order_date = datetime.fromisoformat(order_date_str.replace("Z", "+00:00")).date() except (ValueError, AttributeError): order_date = datetime.now().date() - timedelta(days=1) gomag_key = app_settings.get("gomag_api_key") or None gomag_shop = app_settings.get("gomag_api_shop") or None with tempfile.TemporaryDirectory() as tmp_dir: try: today = datetime.now().date() days_back = (today - order_date).days + 1 if days_back < 2: days_back = 2 await gomag_client.download_orders( tmp_dir, days_back=days_back, api_key=gomag_key, api_shop=gomag_shop, limit=200, ) except Exception as e: logger.error(f"Retry download failed for {order_number}: {e}") return {"success": False, "message": f"Eroare download GoMag: {e}"} # Find the specific order in downloaded data target_order = None orders, _ = order_reader.read_json_orders(json_dir=tmp_dir) for o in orders: if str(o.number) == str(order_number): target_order = o break if not target_order: return {"success": False, "message": f"Comanda {order_number} nu a fost gasita in GoMag API"} # Import the order id_pol = int(app_settings.get("id_pol") or 0) id_sectie = int(app_settings.get("id_sectie") or 0) 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: result = await asyncio.to_thread( import_service.import_single_order, target_order, id_pol=id_pol, id_sectie=id_sectie, app_settings=app_settings, id_gestiuni=id_gestiuni ) except Exception as e: logger.error(f"Retry import failed for {order_number}: {e}") await sqlite_service.upsert_order( sync_run_id="retry", order_number=order_number, order_date=order_date_str, customer_name=customer_name, status="ERROR", error_message=f"Retry failed: {e}", ) return {"success": False, "message": f"Eroare import: {e}"} if result.get("success"): await sqlite_service.upsert_order( sync_run_id="retry", order_number=order_number, order_date=order_date_str, customer_name=customer_name, status="IMPORTED", id_comanda=result.get("id_comanda"), id_partener=result.get("id_partener"), error_message=None, ) if result.get("id_adresa_facturare") or result.get("id_adresa_livrare"): await sqlite_service.update_import_order_addresses( order_number=order_number, id_adresa_facturare=result.get("id_adresa_facturare"), id_adresa_livrare=result.get("id_adresa_livrare"), ) logger.info(f"Retry successful for order {order_number} → IMPORTED") return {"success": True, "message": "Comanda reimportata cu succes", "status": "IMPORTED"} else: error = result.get("error", "Unknown error") await sqlite_service.upsert_order( sync_run_id="retry", order_number=order_number, order_date=order_date_str, customer_name=customer_name, status="ERROR", error_message=f"Retry: {error}", ) return {"success": False, "message": f"Import esuat: {error}", "status": "ERROR"} async def retry_single_order(order_number: str, app_settings: dict) -> dict: """Re-download and re-import a single order from GoMag. Steps: 1. Read order from SQLite to get order_date / customer_name 2. Check sync lock (no retry during active sync) 3. Download narrow date range from GoMag (order_date ± 1 day) 4. Find the specific order in downloaded data 5. Run import_single_order() 6. Update status in SQLite Returns: {"success": bool, "message": str, "status": str|None} """ from . import sqlite_service, sync_service # Check sync lock if sync_service._sync_lock.locked(): return {"success": False, "message": "Sync in curs — asteapta finalizarea"} # Get order from SQLite detail = await sqlite_service.get_order_detail(order_number) if not detail: return {"success": False, "message": "Comanda nu a fost gasita"} order_data = detail["order"] status = order_data.get("status", "") if status not in ("ERROR", "SKIPPED", "DELETED_IN_ROA"): return {"success": False, "message": f"Retry permis doar pentru ERROR/SKIPPED/DELETED_IN_ROA (status actual: {status})"} order_date_str = order_data.get("order_date", "") customer_name = order_data.get("customer_name", "") return await _download_and_reimport(order_number, order_date_str, customer_name, app_settings) async def resync_single_order(order_number: str, app_settings: dict) -> dict: """Soft-delete an imported order from Oracle then re-import it from GoMag. Steps: 1. Check sync lock 2. Load order from SQLite 3. Validate status is IMPORTED/ALREADY_IMPORTED with id_comanda 4. Invoice safety gate (check Oracle for invoices) 5. Soft-delete from Oracle 6. Mark DELETED_IN_ROA in SQLite 7. Re-import via _download_and_reimport Returns: {"success": bool, "message": str, "status": str|None} """ from . import sqlite_service, sync_service, import_service, invoice_service from .. import database # Check sync lock if sync_service._sync_lock.locked(): return {"success": False, "message": "Sync in curs — asteapta finalizarea"} # Get order from SQLite detail = await sqlite_service.get_order_detail(order_number) if not detail: return {"success": False, "message": "Comanda nu a fost gasita"} order_data = detail["order"] status = order_data.get("status", "") id_comanda = order_data.get("id_comanda") if status not in ("IMPORTED", "ALREADY_IMPORTED") or not id_comanda: return {"success": False, "message": f"Resync permis doar pentru IMPORTED/ALREADY_IMPORTED cu id_comanda (status actual: {status})"} # Invoice safety gate if database.pool is None: return {"success": False, "message": "Oracle indisponibil"} if order_data.get("factura_numar"): return {"success": False, "message": "Comanda este facturata"} try: invoice_result = await asyncio.to_thread( invoice_service.check_invoices_for_orders, [id_comanda] ) except Exception as e: logger.error(f"Invoice check failed for {order_number}: {e}") return {"success": False, "message": "Nu se poate verifica factura — Oracle indisponibil"} if invoice_result.get(id_comanda): return {"success": False, "message": "Comanda este facturata"} # Soft-delete from Oracle try: delete_result = await asyncio.to_thread( import_service.soft_delete_order_in_roa, id_comanda ) if not delete_result.get("success"): return {"success": False, "message": f"Eroare stergere din Oracle: {delete_result.get('error', 'Unknown')}"} except Exception as e: logger.error(f"Soft-delete failed for {order_number} (id_comanda={id_comanda}): {e}") return {"success": False, "message": f"Eroare stergere din Oracle: {e}"} # Mark deleted in SQLite await sqlite_service.mark_order_deleted_in_roa(order_number) order_date_str = order_data.get("order_date", "") customer_name = order_data.get("customer_name", "") # Re-import reimport_result = await _download_and_reimport(order_number, order_date_str, customer_name, app_settings) if not reimport_result.get("success"): logger.warning(f"Resync: order {order_number} deleted from Oracle but reimport failed") return { "success": False, "message": "Comanda stearsa din Oracle dar reimportul a esuat — foloseste Reimporta pentru a reincerca", } return reimport_result async def delete_single_order(order_number: str) -> dict: """Soft-delete an imported order from Oracle without re-importing. Same invoice safety gate as resync_single_order. Returns: {"success": bool, "message": str} """ from . import sqlite_service, sync_service, import_service, invoice_service from .. import database # Check sync lock if sync_service._sync_lock.locked(): return {"success": False, "message": "Sync in curs — asteapta finalizarea"} # Get order from SQLite detail = await sqlite_service.get_order_detail(order_number) if not detail: return {"success": False, "message": "Comanda nu a fost gasita"} order_data = detail["order"] status = order_data.get("status", "") id_comanda = order_data.get("id_comanda") if status not in ("IMPORTED", "ALREADY_IMPORTED") or not id_comanda: return {"success": False, "message": f"Stergere permisa doar pentru IMPORTED/ALREADY_IMPORTED cu id_comanda (status actual: {status})"} # Invoice safety gate if database.pool is None: return {"success": False, "message": "Oracle indisponibil"} if order_data.get("factura_numar"): return {"success": False, "message": "Comanda este facturata"} try: invoice_result = await asyncio.to_thread( invoice_service.check_invoices_for_orders, [id_comanda] ) except Exception as e: logger.error(f"Invoice check failed for {order_number}: {e}") return {"success": False, "message": "Nu se poate verifica factura — Oracle indisponibil"} if invoice_result.get(id_comanda): return {"success": False, "message": "Comanda este facturata"} # Soft-delete from Oracle try: delete_result = await asyncio.to_thread( import_service.soft_delete_order_in_roa, id_comanda ) if not delete_result.get("success"): return {"success": False, "message": f"Eroare stergere din Oracle: {delete_result.get('error', 'Unknown')}"} except Exception as e: logger.error(f"Soft-delete failed for {order_number} (id_comanda={id_comanda}): {e}") return {"success": False, "message": f"Eroare stergere din Oracle: {e}"} # Mark deleted in SQLite await sqlite_service.mark_order_deleted_in_roa(order_number) logger.info(f"Order {order_number} (id_comanda={id_comanda}) deleted from ROA") return {"success": True, "message": "Comanda stearsa din ROA"}