""" Reconcile ordre draft sync lifecycle. Promotes sync_status based on known economic references on ordre_drafts: - pending/failed + economic_order_number -> exported - exported + economic_invoice_number -> posted """ import logging from typing import Any, Dict, List from app.core.database import execute_query, get_db_connection, release_db_connection from app.services.economic_service import get_economic_service from psycopg2.extras import RealDictCursor logger = logging.getLogger(__name__) async def reconcile_ordre_drafts_sync_status(apply_changes: bool = True) -> Dict[str, Any]: """Reconcile ordre_drafts sync statuses and optionally persist changes.""" drafts = execute_query( """ SELECT id, sync_status, economic_order_number, economic_invoice_number FROM ordre_drafts ORDER BY id ASC """, (), ) or [] changes: List[Dict[str, Any]] = [] invoice_status_cache: Dict[str, str] = {} economic_service = get_economic_service() for draft in drafts: current = (draft.get("sync_status") or "pending").strip().lower() target = current if current in {"pending", "failed"} and draft.get("economic_order_number"): target = "exported" if target == "exported" and draft.get("economic_invoice_number"): target = "posted" invoice_number = str(draft.get("economic_invoice_number") or "").strip() if invoice_number: if invoice_number not in invoice_status_cache: invoice_status_cache[invoice_number] = await economic_service.get_invoice_lifecycle_status(invoice_number) lifecycle = invoice_status_cache[invoice_number] if lifecycle == "paid": target = "paid" elif lifecycle in {"booked", "unpaid"} and target in {"pending", "failed", "exported"}: target = "posted" if target != current: changes.append( { "draft_id": draft.get("id"), "from": current, "to": target, "economic_invoice_number": invoice_number or None, } ) if apply_changes and changes: conn = get_db_connection() try: with conn.cursor(cursor_factory=RealDictCursor) as cursor: for change in changes: cursor.execute( """ UPDATE ordre_drafts SET sync_status = %s, last_sync_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP, last_exported_at = CASE WHEN %s IN ('exported', 'posted', 'paid') THEN CURRENT_TIMESTAMP ELSE last_exported_at END WHERE id = %s """, (change["to"], change["to"], change["draft_id"]), ) cursor.execute( """ INSERT INTO ordre_draft_sync_events ( draft_id, event_type, from_status, to_status, event_payload, created_by_user_id ) VALUES (%s, %s, %s, %s, %s::jsonb, NULL) """, ( change["draft_id"], "sync_status_reconcile", change["from"], change["to"], '{"source":"reconcile_job"}', ), ) conn.commit() except Exception: conn.rollback() raise finally: release_db_connection(conn) logger.info( "✅ Reconciled ordre draft sync status: %s changes (%s)", len(changes), "applied" if apply_changes else "preview", ) return { "status": "applied" if apply_changes else "preview", "change_count": len(changes), "changes": changes, }