120 lines
4.2 KiB
Python
120 lines
4.2 KiB
Python
"""
|
|
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,
|
|
}
|