feat(partner): detect and resync partner mismatches on already-imported orders

Detects PF↔PJ transitions and CUI changes after import; auto-resyncs
uninvoiced orders (max 5/cycle) and shows visual alert for invoiced ones.
- SQLite: partner_mismatch column + batch helpers
- sync_service: detection loop + _resync_partner_for_order
- dashboard: red dot + attention card indicator
- modal: alert with contextual message and resync button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-08 16:19:26 +00:00
parent bf194eb088
commit 89c3d1d07f
11 changed files with 639 additions and 44 deletions

View File

@@ -544,6 +544,7 @@ async def order_detail(order_number: str):
"anaf_cod_fiscal_adjusted": order.get("anaf_cod_fiscal_adjusted") == 1,
"anaf_denumire_mismatch": order.get("anaf_denumire_mismatch") == 1,
"denumire_anaf": order.get("denumire_anaf"),
"partner_mismatch": order.get("partner_mismatch") == 1,
}
# Parse JSON address strings
for key in ("adresa_livrare_gomag", "adresa_facturare_gomag",
@@ -579,6 +580,80 @@ async def retry_order(order_number: str):
return result
@router.post("/api/orders/{order_number}/resync-partner")
async def resync_partner(order_number: str):
"""Manual partner resync for invoiced orders with partner_mismatch=1.
Auto-resync handles uninvoiced orders during sync loop.
This endpoint is for edge case: operator wants to fix an already-invoiced order.
"""
detail = await sqlite_service.get_order_detail(order_number)
if not detail:
raise HTTPException(status_code=404, detail="Comanda nu a fost gasita")
order_data = detail["order"]
if not order_data.get("partner_mismatch"):
return {"success": False, "message": "Comanda nu are mismatch de partener"}
if sync_service._sync_lock.locked():
return {"success": False, "message": "Sync in curs — asteapta finalizarea"}
stored = {
"id_comanda": order_data.get("id_comanda"),
"id_partener": order_data.get("id_partener"),
"denumire_roa": order_data.get("denumire_roa"),
"cod_fiscal_gomag": order_data.get("cod_fiscal_gomag"),
"factura_numar": order_data.get("factura_numar"),
}
# Download order from GoMag to get current data
import tempfile
from ..services import order_reader, gomag_client
app_settings = await sqlite_service.get_app_settings()
gomag_key = app_settings.get("gomag_api_key") or None
gomag_shop = app_settings.get("gomag_api_shop") or None
from datetime import datetime, timedelta
order_date_str = order_data.get("order_date", "")
try:
order_date = datetime.fromisoformat(order_date_str.replace("Z", "+00:00")).date()
except (ValueError, AttributeError):
order_date = datetime.now().date() - timedelta(days=1)
with tempfile.TemporaryDirectory() as tmp_dir:
try:
days_back = (datetime.now().date() - order_date).days + 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:
return {"success": False, "message": f"Eroare download GoMag: {e}"}
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"}
run_id = f"resync_{order_number}"
try:
await sync_service._resync_partner_for_order(
order=target_order,
stored=stored,
app_settings=app_settings,
run_id=run_id,
)
return {"success": True, "message": "Partener actualizat in ROA"}
except Exception as e:
logger.error(f"Manual resync failed for {order_number}: {e}")
return {"success": False, "message": str(e)}
@router.get("/api/orders/by-sku/{sku}/pending")
async def get_pending_orders_for_sku(sku: str):
"""Get SKIPPED orders that contain the given SKU."""