fix(import): resolve duplicate article + order_items repopulation on retry

Two production bugs from VENDING (order 485224762, 2026-04-22):

1. Oracle: ORA-20000 when a GoMag order contains a kit SKU whose expansion
   includes CODMAT X plus a second item with SKU=X. Two article-insert
   call-sites in PACK_IMPORT_COMENZI bypassed merge_or_insert_articol —
   line 622 (NOM_ARTICOLE fallback) and line 538 (kit discount line).
   Both now use merge_or_insert_articol for consistent dedup semantics.
   Regression test added in test_complete_import.py covering the exact
   kit-plus-direct scenario.

2. SQLite: retry_service._download_and_reimport refreshed orders row but
   never repopulated order_items. Combined with mark_order_deleted_in_roa
   (which wipes items), any retry/resync left the UI showing "Niciun
   articol" despite successful Oracle import. Retry now rebuilds items
   from the fresh GoMag download on both success and error paths,
   mirroring sync_service.

Includes scripts/backfill_order_items.py — one-shot recovery for orders
already in this bad state. Reads settings, re-fetches from GoMag,
rewrites order_items without touching Oracle or order status.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-22 06:58:20 +00:00
parent b8a9480784
commit 819af221d8
4 changed files with 252 additions and 19 deletions

View File

@@ -13,7 +13,7 @@ async def _download_and_reimport(order_number: str, order_date_str: str, custome
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
from . import sqlite_service, gomag_client, import_service, order_reader, validation_service
# Parse order date for narrow download window
try:
@@ -75,6 +75,28 @@ async def _download_and_reimport(order_number: str, order_date_str: str, custome
)
return {"success": False, "message": f"Eroare import: {e}"}
# Build order_items data from fresh GoMag download (mirrors sync_service:882-891).
# Resolves ARTICOLE_TERTI mapping so UI shows mapped/direct badge.
try:
skus = {item.sku for item in target_order.items if item.sku}
validation = await asyncio.to_thread(
validation_service.validate_skus, skus, None, id_gestiuni
) if skus else {"mapped": set(), "direct": set()}
except Exception as e:
logger.warning(f"Retry: validate_skus failed for {order_number}, defaulting mapping_status=direct: {e}")
validation = {"mapped": set(), "direct": set()}
order_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_order.items
]
if result.get("success"):
await sqlite_service.upsert_order(
sync_run_id="retry",
@@ -92,7 +114,8 @@ async def _download_and_reimport(order_number: str, order_date_str: str, custome
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")
await sqlite_service.add_order_items(order_number, order_items_data)
logger.info(f"Retry successful for order {order_number} → IMPORTED ({len(order_items_data)} items)")
return {"success": True, "message": "Comanda reimportata cu succes", "status": "IMPORTED"}
else:
error = result.get("error", "Unknown error")
@@ -104,6 +127,7 @@ async def _download_and_reimport(order_number: str, order_date_str: str, custome
status="ERROR",
error_message=f"Retry: {error}",
)
await sqlite_service.add_order_items(order_number, order_items_data)
return {"success": False, "message": f"Import esuat: {error}", "status": "ERROR"}